Package org.knopflerfish.bundle.http

Source Code of org.knopflerfish.bundle.http.NoBodyOutputStream

/*
* Copyright (c) 2003-2011, KNOPFLERFISH project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
*   notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
*   copyright notice, this list of conditions and the following
*   disclaimer in the documentation and/or other materials
*   provided with the distribution.
*
* - Neither the name of the KNOPFLERFISH project nor the names of its
*   contributors may be used to endorse or promote products derived
*   from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.knopflerfish.bundle.http;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Stack;
import java.util.Vector;
import java.util.zip.GZIPOutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.Locale;
import java.util.Enumeration;

import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.SingleThreadModel;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Cookie;

import org.osgi.service.http.HttpContext;

import org.knopflerfish.service.log.LogRef;

public class RequestDispatcherImpl
  implements RequestDispatcher
{
  // private constants

  private final static Dictionary threadStacks = new Hashtable();

  // private fields

  private final String servletPath;

  private final Servlet servlet;

  private final HttpContext httpContext;

  private final ServletConfig config;

  private String uri = null;

  private String queryString = null;

  private String pathInfo = null;

  /**
   * HACK CSM
   */
  private long lastModificationDate;


  // constructors

  /**
   * HACK CSM
   */
  RequestDispatcherImpl(final String servletPath,
                        final Servlet servlet,
                        final HttpContext httpContext,
                        long newDate)
  {
    this(servletPath, servlet, httpContext, null, newDate);
  }

  /**
   * HACK CSM
   */
  RequestDispatcherImpl(final String servletPath,
                        final Servlet servlet,
                        final HttpContext httpContext,
                        final ServletConfig config,
                        long newDate)
  {
    this.servletPath = servletPath;
    this.servlet = servlet;
    this.httpContext = httpContext;
    this.config = config;
    lastModificationDate = newDate;
  }

  // private methods

  private void service(HttpServletRequest request,
                       HttpServletResponse response)
    throws IOException, ServletException
  {
    if (httpContext.handleSecurity(request, response)) {

      Thread t = Thread.currentThread();
      Stack usedURIStack = (Stack) threadStacks.get(t);
      if (usedURIStack == null) {
        usedURIStack = new Stack();
        threadStacks.put(t, usedURIStack);
      }
      String uri = (String) request
        .getAttribute("javax.servlet.include.request_uri");
      if (uri == null)
        uri = request.getRequestURI();
      if (usedURIStack.contains(uri))
        throw new ServletException("Recursive include of \"" + uri
                                   + "\"");

      usedURIStack.push(uri);
      try {
        if (servlet instanceof SingleThreadModel) {
          synchronized (servlet) {
            if (config == null)
              servlet.service(request, response);
            else
              serviceResource(request, response, config);
          }
        } else {
          if (config == null)
            servlet.service(request, response);
          else
            serviceResource(request, response, config);
        }
      } finally {
        usedURIStack.pop();
        if (usedURIStack.empty())
          threadStacks.remove(t);
      }

    }
  }

  private void serviceResource(HttpServletRequest request,
                               HttpServletResponse response,
                               ServletConfig config)
    throws IOException, ServletException
  {

    String method = request.getMethod();
    if ("HEAD".equalsIgnoreCase(method)) {
      NoBodyResponse nb_response = new NoBodyResponse(response);
      serviceGet(request, nb_response, config);
      nb_response.setContentLength();
    }
    else if ("GET".equalsIgnoreCase(method)) {
      serviceGet(request, response, config);
    }
    else if ("TRACE".equalsIgnoreCase(method)) {
      serviceTrace(request, response);
    }
    else { // unsupported
      response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
      return;
    }
  }
 

  private void serviceGet(HttpServletRequest request, HttpServletResponse response, ServletConfig config) throws IOException
  {
    String uri = (String) request
      .getAttribute("javax.servlet.include.request_uri");
    if (uri == null) {
      uri = request.getRequestURI();
    }

    final boolean useGzip = HttpUtil.useGZIPEncoding(request);

    if (uri.endsWith(".shtml")) {
      serviceSSIResource(uri, response, config, useGzip);
    } else {
      final String target = HttpUtil.makeTarget(uri, servletPath);
      final ServletContext context = config.getServletContext();
      final URL url = context.getResource(target);
      //HACK CSM
      final long date = getLastModified(url);
      if (date > -1) {
        try {
          long if_modified = request.getDateHeader("If-Modified-Since");
          if (if_modified>0 && date/1000<=if_modified/1000) {
            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            return;
          }
        } catch (IllegalArgumentException iae){
          // An 'If-Modified-Since' header is present but the value
          // can not be parsed; ignore it.
          final LogRef log = Activator.log;
          if (null!=log && log.doDebug()) {
            log.debug("Ignoring broken 'If-Modified-Since' header: "
                      +iae.getMessage(), iae);
          }
        }
        response.setDateHeader("Last-Modified", date);
      }
      //END HACK CSM
      final URLConnection resource = url.openConnection();

      String contentType = context.getMimeType(target);
      if (contentType == null)
        contentType = resource.getContentType();
      if (contentType != null) {
        final String encoding = resource.getContentEncoding();
        if (encoding != null)
          contentType += "; charset=" + encoding;
        response.setContentType(contentType);
      }

      // NOTE: When useGzip is true (Content-Encoding gzip) buffering
      //       is required, since the actual content-length is not
      //       available untill after gzip:ing the contents.

      // HACK CSM for linux: contentLength will allways be 0.  To
      //       handle that, we must also buffer the data read from
      //       the URL-connection.

      int totalBytesRead = 0;
      // Output stream to write to when buffering is needed.
      OutputStream buos = null;
      // The buffer.
      ByteArrayOutputStream baos = null;
      // Filter on top of the buffer when gzip:ing.
      GZIPOutputStream gzos = null;

      final int contentLength = resource.getContentLength();
      if (contentLength > 0 && !useGzip) {
        response.setContentLength(contentLength);
      } else {
        final int size = contentLength>0 ? contentLength+25 : 512;
        buos = baos = new ByteArrayOutputStream(size);
        if (useGzip) {
          gzos = new GZIPOutputStream(baos);
          buos = gzos;
        }
      }

      final InputStream is = resource.getInputStream();
      final OutputStream os = response.getOutputStream();

      int bytesRead = 0;
      final byte buffer[] = new byte[512];
      while((bytesRead = is.read(buffer)) != -1) {
        if (null==buos) {
          os.write(buffer, 0, bytesRead);
        } else {
          // Buffer data to be able to compute the true content length
          buos.write(buffer, 0, bytesRead);
          totalBytesRead += bytesRead;
        }
      }
      if (null!=buos) {
        if (null!=gzos) { //Content-Encoding: gzip
          gzos.finish();
          response.addHeader(HeaderBase.CONTENT_ENCODING, "gzip");
          response.setContentLength(baos.size());
          baos.writeTo(os);
        } else { // Initially unknown content length.
          if(totalBytesRead > 0) {
            response.setContentLength(totalBytesRead);
            baos.writeTo(os);
          }
        }
      }
      is.close();
    }
  }

  private void serviceTrace(HttpServletRequest req, HttpServletResponse resp)
    throws IOException
  {
    int responseLength;
 
    String CRLF = "\r\n";
    String responseString = "TRACE "+ req.getRequestURI()+
      " " + req.getProtocol();
 
    Enumeration reqHeaderEnum = req.getHeaderNames();
   
    while( reqHeaderEnum.hasMoreElements() ) {
      String headerName = (String)reqHeaderEnum.nextElement();
      responseString += CRLF + headerName + ": " +
  req.getHeader(headerName);
    }
 
    responseString += CRLF;
 
    responseLength = responseString.length();
 
    resp.setContentType("message/http");
    resp.setContentLength(responseLength);
    ServletOutputStream out = resp.getOutputStream();
    out.print(responseString)
    out.close();
  }


  private void serviceSSIResource(final String uri,
                                  final HttpServletResponse response,
                                  final ServletConfig config,
                                  final boolean useGzip)
    throws IOException
  {
    final String target = HttpUtil.makeTarget(uri, servletPath);
    final ServletContext context = config.getServletContext();

    final String contentType = context.getMimeType(target);
    if (contentType != null)
      response.setContentType(contentType);

    ServletOutputStream os = response.getOutputStream();
    if (useGzip) {
      os = new GZIPServletOutputStreamImpl(os);
      response.addHeader(HeaderBase.CONTENT_ENCODING, "gzip");
    }
    try {
      parseHtml(target, context, os, new Stack());
    } catch (IOException ioe) {
      throw ioe;
    } catch (Exception e) {
      os.print("<b><font color=\"red\">SSI Error: " + e + "</font></b>");
    }
    // An explicit flush is needed here, since flushing the
    // underlaying servlet output stream will not finish the gzip
    // process! Note flush() should only be called once on a
    // GZIPServletOutputStreamImpl.
    os.flush();
  }

  private void parseHtml(final String uri,
                         final ServletContext context,
                         final ServletOutputStream os,
                         final Stack usedFiles)
    throws IOException
  {
    if (usedFiles.contains(uri)) {
      os.print("<b><font color=\"red\">SSI Error: Recursive include: "
               + uri + "</font></b>");
      return;
    }
    usedFiles.push(uri);
    InputStream raw;
    try {
      raw = context.getResourceAsStream(uri);
    } catch (Exception e) {
      raw = null;
    }
    if (raw == null) {
      os.print("<b><font color=\"red\">SSI Error: Error reading file: "
               + uri + "</font></b>");
      return;
    }
    final InputStream is = new BufferedInputStream(raw);

    byte c;
    boolean tagBegin = false;
    final StringBuffer buf = new StringBuffer(20);
    while ((c = (byte) is.read()) != -1) {
      if (c == '<') {
        buf.setLength(0);
        tagBegin = true;
      } else if (tagBegin && c == '>') {
        String restOfTag = buf.toString();

        final String ssi_pattern = "!--#";
        if (restOfTag.length() > ssi_pattern.length()
            && restOfTag.startsWith(ssi_pattern)) { // is this an
          // ssi tag?
          restOfTag = restOfTag.substring(ssi_pattern.length());

          final String include_pattern = "include";
          if (restOfTag.length() > include_pattern.length()
              && restOfTag.startsWith(include_pattern)) { // is
            // this
            // an
            // include
            // directive?
            restOfTag = restOfTag.substring(include_pattern
                                            .length());

            final String file_pattern = "file=\"";
            int index = restOfTag.indexOf(file_pattern);
            if (index > 0
                && Character.isWhitespace(restOfTag.charAt(0))) {
              restOfTag = restOfTag.substring(index
                                              + file_pattern.length());
              String file = restOfTag.substring(0, restOfTag
                                                .indexOf('\"'));
              parseHtml(uri
                        .substring(0, uri.lastIndexOf("/") + 1)
                        + file, context, os, usedFiles);
            } else {
              os
                .print("<b><font color=\"red\">SSI Error: Unsupported directive</font></b>");
            }
          } else {
            os
              .print("<b><font color=\"red\">SSI Error: Unsupported directive</font></b>");
          }
        } else {
          os.print('<');
          os.print(restOfTag);
          os.print('>');
        }

        tagBegin = false;
      } else if (tagBegin) {
        buf.append((char) c);
      } else {
        os.write(c);
      }
    }

    is.close();
    usedFiles.pop();
  }

  // public methods

  public String getServletPath() {
    return servletPath;
  }

  public Servlet getServlet() {
    return servlet;
  }

  public HttpContext getHttpContext() {
    return httpContext;
  }

  public void setURI(String uri) {

    int index = uri.indexOf('?');
    if (index != -1) {
      this.uri = uri.substring(0, index);
      this.queryString = uri.substring(index + 1);
    } else {
      this.uri = uri;
      this.queryString = null;
    }

    String decodedURI = HttpUtil.decodeURLEncoding(uri);
    if (decodedURI != null && decodedURI.length() > servletPath.length()
        && decodedURI.startsWith(servletPath))
      this.pathInfo = HttpUtil.makeTarget(decodedURI, servletPath);
    else
      this.pathInfo = null;
  }

  // implements RequestDispatcher

  public void forward(ServletRequest request, ServletResponse response)
    throws IOException, ServletException {

    if (!(request instanceof HttpServletRequest && response instanceof HttpServletResponse))
      throw new ServletException("Must be http request");

    if (response.isCommitted()) {
      throw new IllegalStateException(
                                      "Cannot forward request after response is committed");
    }
    response.reset();

    service((HttpServletRequest) request, (HttpServletResponse) response);

    response.flushBuffer();
  }

  public void include(ServletRequest request, ServletResponse response)
    throws IOException, ServletException {

    if (!(request instanceof HttpServletRequest && response instanceof HttpServletResponse))
      throw new ServletException("Must be http request");

    HttpServletRequest wrappedRequest = new RequestWrapper(
                                                           (HttpServletRequest) request);
    HttpServletResponse wrappedResponse = new ResponseWrapper(
                                                              (HttpServletResponse) response);

    wrappedRequest.setAttribute("javax.servlet.include.request_uri", uri);
    wrappedRequest.setAttribute("javax.servlet.include.context_path", "");
    wrappedRequest.setAttribute("javax.servlet.include.servlet_path",
                                servletPath);
    if (pathInfo != null)
      wrappedRequest.setAttribute("javax.servlet.include.path_info",
                                  pathInfo);
    if (queryString != null)
      wrappedRequest.setAttribute("javax.servlet.include.query_string",
                                  queryString);

    service(wrappedRequest, wrappedResponse);

    response.flushBuffer();
  }

  // HACK CSM
  public long getLastModificationDate() {
    return lastModificationDate;
  }

  // HACK CSM
  /**
   * Gets the last modified value for file modification detection.
   * Aids in "conditional get" and intermediate proxy/node cacheing.
   *
   * Approach used follows that used by Sun for JNLP handling to
   * workaround an apparent issue where file URLs do not correctly
   * return a last modified time.
   *
   */
  protected long getLastModified (URL resUrl)
  {
    long lastModified = 0;

    try {
      // Get last modified time
      URLConnection conn = resUrl.openConnection();
      lastModified = conn.getLastModified();
    } catch (Exception e) {
      // do nothing
    }

    if (lastModified == 0) {
      // Arguably a bug in the JRE will not set the lastModified
      // for file URLs, and always return 0. This is a
      // workaround for that problem.
      String filepath = resUrl.getPath();

      if (filepath != null) {
        File f = new File(filepath);
        if (f.exists()) {
          lastModified = f.lastModified();
        }
      }
    }

    if (lastModified == 0) {
      // HCK CSM we assume that the resource is in the bundle
      lastModified = lastModificationDate ;
    }

    return lastModified;
  }

} // RequestDispatcherImpl


/*
* Methods below reused/copied from JSDK, HttpServlet.java
* Provided under a Apache-2 license
*/
  
/*
* A response that includes no body, for use in (dumb) "HEAD" support.
* This just swallows that body, counting the bytes in order to set
* the content length appropriately.  All other methods delegate directly
* to the HTTP Servlet Response object used to construct this one.
*/
// file private
class NoBodyResponse implements HttpServletResponse {
  private HttpServletResponse    resp;
  private NoBodyOutputStream    noBody;
  private PrintWriter      writer;
  private boolean      didSetContentLength;

  // file private
  NoBodyResponse(HttpServletResponse r) {
    resp = r;
    noBody = new NoBodyOutputStream();
  }

  // file private
  void setContentLength() {
    if (!didSetContentLength)
      resp.setContentLength(noBody.getContentLength());
  }

 
  // SERVLET RESPONSE interface methods

  public void setContentLength(int len) {
    resp.setContentLength(len);
    didSetContentLength = true;
  }

  public void setCharacterEncoding(String charset)
  { resp.setCharacterEncoding(charset); }

  public void setContentType(String type)
  { resp.setContentType(type); }

  public String getContentType()
  { return resp.getContentType(); }

  public ServletOutputStream getOutputStream() throws IOException
  { return noBody; }

  public String getCharacterEncoding()
  { return resp.getCharacterEncoding(); }

  public PrintWriter getWriter() throws UnsupportedEncodingException
  {
    if (writer == null) {
      OutputStreamWriter w;

      w = new OutputStreamWriter(noBody, getCharacterEncoding());
      writer = new PrintWriter(w);
    }
    return writer;
  }

  public void setBufferSize(int size) throws IllegalStateException
  { resp.setBufferSize(size); }

  public int getBufferSize()
  { return resp.getBufferSize(); }

  public void reset() throws IllegalStateException
  { resp.reset(); }
     
  public void resetBuffer() throws IllegalStateException
  { resp.resetBuffer(); }

  public boolean isCommitted()
  { return resp.isCommitted(); }

  public void flushBuffer() throws IOException
  { resp.flushBuffer(); }

  public void setLocale(Locale loc)
  { resp.setLocale(loc); }

  public Locale getLocale()
  { return resp.getLocale(); }


  // HTTP SERVLET RESPONSE interface methods

  public void addCookie(Cookie cookie)
  { resp.addCookie(cookie); }

  public boolean containsHeader(String name)
  { return resp.containsHeader(name); }

  /** @deprecated */
  public void setStatus(int sc, String sm)
  { resp.setStatus(sc, sm); }

  public void setStatus(int sc)
  { resp.setStatus(sc); }

  public void setHeader(String name, String value)
  { resp.setHeader(name, value); }

  public void setIntHeader(String name, int value)
  { resp.setIntHeader(name, value); }

  public void setDateHeader(String name, long date)
  { resp.setDateHeader(name, date); }

  public void sendError(int sc, String msg) throws IOException
  { resp.sendError(sc, msg); }

  public void sendError(int sc) throws IOException
  { resp.sendError(sc); }

  public void sendRedirect(String location) throws IOException
  { resp.sendRedirect(location); }
   
  public String encodeURL(String url)
  { return resp.encodeURL(url); }

  public String encodeRedirectURL(String url)
  { return resp.encodeRedirectURL(url); }
     
  public void addHeader(String name, String value)
  { resp.addHeader(name, value); }
     
  public void addDateHeader(String name, long value)
  { resp.addDateHeader(name, value); }
     
  public void addIntHeader(String name, int value)
  { resp.addIntHeader(name, value); }
     
    
  public String encodeUrl(String url)
  { return this.encodeURL(url); }
     
  public String encodeRedirectUrl(String url)
  { return this.encodeRedirectURL(url); }

}


/*
* Servlet output stream that gobbles up all its data.
*/
// file private
class NoBodyOutputStream extends ServletOutputStream {
 
  private int    contentLength = 0;

  // file private
  NoBodyOutputStream() {}

  // file private
  int getContentLength() {
    return contentLength;
  }

  public void write(int b) {
    contentLength++;
  }

  public void write(byte buf[], int offset, int len)
    throws IOException
  {
    if (len >= 0) {
      contentLength += len;
    } else {
      // XXX
      // isn't this really an IllegalArgumentException?
      throw new IOException("negative length");
    }
  }
}
TOP

Related Classes of org.knopflerfish.bundle.http.NoBodyOutputStream

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.