Package freenet.clients.http

Source Code of freenet.clients.http.ToadletContextImpl

package freenet.clients.http;

import static java.util.concurrent.TimeUnit.DAYS;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.Locale;
import java.util.TimeZone;

import freenet.clients.http.FProxyFetchInProgress.REFILTER_POLICY;
import freenet.clients.http.annotation.AllowData;
import freenet.clients.http.bookmark.BookmarkManager;
import freenet.l10n.NodeL10n;
import freenet.node.useralerts.UserAlertManager;
import freenet.support.HTMLEncoder;
import freenet.support.HTMLNode;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
import freenet.support.MultiValueTable;
import freenet.support.TimeUtil;
import freenet.support.URIPreEncoder;
import freenet.support.api.Bucket;
import freenet.support.api.BucketFactory;
import freenet.support.api.HTTPRequest;
import freenet.support.io.BucketTools;
import freenet.support.io.FileUtil;
import freenet.support.io.LineReadingInputStream;
import freenet.support.io.TooLongException;
/**
* ToadletContext implementation, including all the icky HTTP parsing etc.
* An actual ToadletContext object represents a request, after we have parsed the
* headers. It provides methods to send replies.
* @author root
*
*/
public class ToadletContextImpl implements ToadletContext {
 
  private static final Class<?> HANDLE_PARAMETERS[] = new Class[] {URI.class, HTTPRequest.class, ToadletContext.class};

  /* methods listed here are *not* configurable with
   * AllowData annotation
   */
  private static final String METHODS_MUST_HAVE_DATA = "POST";
  private static final String METHODS_CANNOT_HAVE_DATA = "GET";
  private static final String METHODS_RESTRICTED_MODE = "GET POST";
 
  private final MultiValueTable<String,String> headers;
  private ArrayList<ReceivedCookie> cookies; // Null until the first time the user queries us for a ReceivedCookie.
  private ArrayList<Cookie> replyCookies; // Null until the first time the user sets a Cookie.
  private final OutputStream sockOutputStream;
  private final PageMaker pagemaker;
  private final BucketFactory bf;
  private final ToadletContainer container;
  private final UserAlertManager userAlertManager;
  private final BookmarkManager bookmarkManager;
  private final InetAddress remoteAddr;
  private Exception firstReplySendingException;
  private volatile Toadlet activeToadlet;
 
  /** The unique id of the request*/
  private final String uniqueId;
 
  private URI uri;

        private static volatile boolean logMINOR;
        private static volatile boolean logDEBUG;
  static {
    Logger.registerLogThresholdCallback(new LogThresholdCallback(){
      @Override
      public void shouldUpdate(){
        logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
                                logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this);
      }
    });
  }
 
  /** Is the context closed? If so, don't allow any more writes. This is because there
   * may be later requests.
   */
  private boolean closed;
  private boolean shouldDisconnect;
 
  public ToadletContextImpl(Socket sock, MultiValueTable<String,String> headers, BucketFactory bf, PageMaker pageMaker, ToadletContainer container, UserAlertManager userAlertManager, BookmarkManager bookmarkManager, URI uri, long uniqueID) throws IOException {
    this.headers = headers;
    this.cookies = null;
    this.replyCookies = null;
    this.closed = false;
    this.uri=uri;
    sockOutputStream = sock.getOutputStream();
    remoteAddr = sock.getInetAddress();
    if(logDEBUG)
      Logger.debug(this, "Connection from "+remoteAddr);
    this.bf = bf;
    this.pagemaker = pageMaker;
    this.container = container;
    this.userAlertManager = userAlertManager;
    this.bookmarkManager = bookmarkManager;
    //Generate an unique id
    uniqueId=String.valueOf(Math.random());
  }
 
  private void close() {
    closed = true;
  }
 
  private void sendMethodNotAllowed(String method, boolean shouldDisconnect) throws ToadletContextClosedException, IOException {
    if(closed) throw new ToadletContextClosedException();
    MultiValueTable<String,String> mvt = new MultiValueTable<String,String>();
    mvt.put("Allow", "GET, PUT");
    sendError(sockOutputStream, 405, "Method Not Allowed", l10n("methodNotAllowed"), shouldDisconnect, mvt);
  }
 
  private static String l10n(String key) {
    return NodeL10n.getBase().getString("ToadletContextImpl."+key);
  }

  private static String l10n(String key, String pattern, String value) {
    return NodeL10n.getBase().getString("ToadletContextImpl."+key, new String[] { pattern }, new String[] { value });
  }

  /**
   * Send an error message. Caller provides the HTTP code, reason string, and a message, which
   * will become the title and the h1'ed contents of the error page.
   */
  private static void sendError(OutputStream os, int code, String httpReason, String message, boolean shouldDisconnect, MultiValueTable<String,String> mvt) throws IOException {
    sendHTMLError(os, code, httpReason, "<html><head><title>"+message+"</title></head><body><h1>"+message+"</h1></body>", shouldDisconnect, mvt);
  }
 
  /**
   * Send an error message, containing full HTML from a String.
   * @param os The OutputStream to send the message to.
   * @param code The HTTP status code.
   * @param httpReason The HTTP reason string for the HTTP status code. Do not make stuff up,
   * use the official reason string, or some browsers may break.
   * @param htmlMessage The HTML string to send.
   * @param disconnect Whether to disconnect from the client afterwards.
   * @param mvt Any additional headers.
   * @throws IOException If we could not send the error message.
   */
  private static void sendHTMLError(OutputStream os, int code, String httpReason, String htmlMessage, boolean disconnect, MultiValueTable<String,String> mvt) throws IOException {
    if(mvt == null) mvt = new MultiValueTable<String,String>();
    byte[] messageBytes = htmlMessage.getBytes("UTF-8");
    sendReplyHeaders(os, code, httpReason, mvt, "text/html; charset=UTF-8", messageBytes.length, null, disconnect, false, false);
    os.write(messageBytes);
  }
 
  private void sendNoToadletError(boolean shouldDisconnect) throws ToadletContextClosedException, IOException {
    if(closed) throw new ToadletContextClosedException();
    sendError(sockOutputStream, 404, "Not Found", l10n("noSuchToadlet"), shouldDisconnect, null);
  }
 
  private static void sendURIParseError(OutputStream os, boolean shouldDisconnect, Throwable e) throws IOException {
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);
    e.printStackTrace(pw);
    pw.close();
    String message = "<html><head><title>"+l10n("uriParseErrorTitle")+"</title></head><body><p>"+HTMLEncoder.encode(e.getMessage())+"</p><pre>\n"+sw.toString();
    sendHTMLError(os, 400, "Bad Request", message, shouldDisconnect, null);
  }

  public void sendReplyHeaders(int code, String desc, MultiValueTable<String,String> mvt, String mimeType, long length) throws ToadletContextClosedException, IOException {
      sendReplyHeaders(code, desc, mvt, mimeType, length, false);
  }
 
  public void sendReplyHeaders(int code, String desc, MultiValueTable<String,String> mvt, String mimeType, long length, boolean forceDisableJavascript) throws ToadletContextClosedException, IOException {
      boolean enableJavascript = (!forceDisableJavascript) && container.isFProxyJavascriptEnabled();
      sendReplyHeaders(code, desc, mvt, mimeType, length, null, false, false, enableJavascript);
  }

  @Deprecated
  public void sendReplyHeaders(int code, String desc, MultiValueTable<String,String> mvt, String mimeType, long length, Date mTime) throws ToadletContextClosedException, IOException {
      if(mTime != null)
          sendReplyHeadersStatic(code, desc, mvt, mimeType, length, mTime);
      else
          sendReplyHeaders(code, desc, mvt, mimeType, length);
  }
 
  public void sendReplyHeadersStatic(int replyCode, String replyDescription, MultiValueTable<String,String> mvt, String mimeType, long contentLength, Date mTime) throws ToadletContextClosedException, IOException {
      if(mTime == null) throw new IllegalArgumentException();
      sendReplyHeaders(replyCode, replyDescription, mvt, mimeType, contentLength, mTime, false, false, false);
  }
 
  @Override
  public void sendReplyHeadersFProxy(int replyCode, String replyDescription, MultiValueTable<String,String> mvt, String mimeType, long contentLength) throws ToadletContextClosedException, IOException {
      boolean enableJavascript = false;
      if(container.isFProxyWebPushingEnabled() && container.isFProxyJavascriptEnabled())
          enableJavascript = true;
      sendReplyHeaders(replyCode, replyDescription, mvt, mimeType, contentLength, null, false, true, enableJavascript);
  }
 
  private void sendReplyHeaders(int replyCode, String replyDescription, MultiValueTable<String,String> mvt, String mimeType, long contentLength, Date mTime, boolean isOutlinkConfirmationPage, boolean allowFrames, boolean enableJavascript) throws ToadletContextClosedException, IOException {
    if(closed) throw new ToadletContextClosedException();
    if(firstReplySendingException != null) {
      throw new IllegalStateException("Already sent headers!", firstReplySendingException);
    }
    firstReplySendingException = new Exception();
   
    if(replyCookies != null) {
      if (mvt == null) {
        mvt = new MultiValueTable<String,String>();
      }
     
      // We do NOT use "set-cookie2" even though we should according though RFC2965 - Firefox 3.0.14 ignores it for me!
     
      for(Cookie cookie : replyCookies) {
        final String cookieHeader = cookie.encodeToHeaderValue();
        mvt.put("set-cookie", cookieHeader);
        if(logMINOR)
          Logger.minor(this, "set-cookie: " + cookieHeader);
      }
    }
    sendReplyHeaders(sockOutputStream, replyCode, replyDescription, mvt, mimeType, contentLength, mTime, shouldDisconnect, enableJavascript, allowFrames);
  }
 
  @Override
  public PageMaker getPageMaker() {
    return pagemaker;
  }
 
  @Override
  public String getFormPassword() {
    return container.getFormPassword();
  }
 
  @Override
  public boolean checkFormPassword(HTTPRequest request)
      throws ToadletContextClosedException, IOException {
    return checkFormPassword(request, "/");
  }
 
  @Override
  public boolean checkFormPassword(HTTPRequest request, String redirectTo)
      throws ToadletContextClosedException, IOException {
    if (!hasFormPassword(request)) {
      MultiValueTable<String, String> headers = new MultiValueTable<String, String>();
      headers.put("Location", redirectTo);
      sendReplyHeaders(302, "Found", headers, null, 0);
      return false;
    } else {
      return true;
    }
  }
 
  /**
   * @see ToadletContext#checkFullAccess(Toadlet)
   */
  @Override
    public boolean checkFullAccess(Toadlet toadlet) throws ToadletContextClosedException, IOException {
        if(isAllowedFullAccess()) {
            return true;
        } else {
            toadlet.sendUnauthorizedPage(this);
            return false;
        }
    }
 
  @Override
  public boolean hasFormPassword(HTTPRequest request) throws IOException {
    String pass = request.getPartAsStringFailsafe("formPassword", 32);
    byte[] inputBytes = pass.getBytes("UTF-8");
    byte[] compareBytes = getFormPassword().getBytes("UTF-8");
    if(!MessageDigest.isEqual(inputBytes, compareBytes)) {
      if (logMINOR)
        Logger.minor(this, "Bad formPassword: " + pass);
      return false;
    } else return true;
  }
 
  @Override
  public UserAlertManager getAlertManager() {
    return userAlertManager;
  }
 
  @Override
  public BookmarkManager getBookmarkManager() {
    return bookmarkManager;
  }
 
  @Override
  public MultiValueTable<String,String> getHeaders() {
    return headers;
  }
 
  private void parseCookies() throws ParseException {
    if(cookies != null)
      return;
   
    int cookieAmount = headers.countAll("cookie");
   
    if(cookieAmount == 0)
      return;
   
    cookies = new ArrayList<ReceivedCookie>(cookieAmount + 1);
   
    for(String cookieHeader : headers.iterateAll("cookie")) {
      ArrayList<ReceivedCookie> parsedCookies = ReceivedCookie.parseHeader(cookieHeader);
      cookies.addAll(parsedCookies);
    }
  }
 
  @Override
  public ReceivedCookie getCookie(URI domain, URI path, String name) throws ParseException {
    parseCookies();
   
    if(cookies == null) // There are no cookies.
      return null;
   
    name = name.toLowerCase();
   
    //String stringDomain = domain==null ? null : domain.toString().toLowerCase();
    //String stringPath = path.toString();
   
    // RFC2965: Two cookies are equal if name and domain are equal with case-insensitive comparison and path is equal with case-sensitive comparison.
    //getName() / getDomain() returns lowercase and getPath() returns the original path.
   
    // UNFORTUNATELY firefox will ONLY give us the name and the value of the cookie, so we ignore everything else.
   
    for(ReceivedCookie cookie : cookies) {
      try {
      //if(stringDomain != null) {
      //  URI cookieDomain = cookie.getDomain();
      // 
      //  if(cookieDomain==null || !stringDomain.equals(cookieDomain.toString()))
      //    continue;
      //}
      //
      //if(cookie.getPath().toString().equals(stringPath) && cookie.getName().equals(name))
      //  return cookie;
       
        if(cookie.getName().equals(name))
          return cookie;
      }
      catch(RuntimeException e) {
        Logger.error(this, "Error in cookie", e);
      }
    }
   
    return null;
  }
 
  @Override
  public void setCookie(Cookie newCookie) {
    if(replyCookies == null)
      replyCookies = new ArrayList<Cookie>(4);
   
    replyCookies.add(newCookie);
  }
 
  static void sendReplyHeaders(OutputStream sockOutputStream, int replyCode, String replyDescription, MultiValueTable<String,String> mvt, String mimeType, long contentLength, Date mTime, boolean disconnect, boolean allowScripts, boolean allowFrames) throws IOException {
   
    // Construct headers
    if(mvt == null)
      mvt = new MultiValueTable<String,String>();
    if(mimeType != null)
      if(mimeType.equalsIgnoreCase("text/html")){
        mvt.put("content-type", mimeType+"; charset=UTF-8");
      }else{
        mvt.put("content-type", mimeType);
      }
    if(contentLength >= 0)
      mvt.put("content-length", Long.toString(contentLength));
   
    String expiresTime;
    if (mTime == null) {
      expiresTime = "Thu, 01 Jan 1970 00:00:00 GMT";
    } else {
      // use an expiry time of 1 day, somewhat arbitrarily
      expiresTime = TimeUtil.makeHTTPDate(mTime.getTime() + DAYS.toMillis(1));
    }
    mvt.put("expires", expiresTime);
   
    String nowString = TimeUtil.makeHTTPDate(System.currentTimeMillis());
    String lastModString;
    if (mTime == null) {
      lastModString = nowString;
    } else {
      lastModString = TimeUtil.makeHTTPDate(mTime.getTime());
    }
   
    mvt.put("last-modified", lastModString);
    mvt.put("date", nowString);
    if (mTime == null) {
      mvt.put("pragma", "no-cache");
      String cacheControl = "max-age=0, must-revalidate, no-cache, no-store, post-check=0, pre-check=0";
      mvt.put("cache-control", cacheControl);
    }
    if(disconnect)
      mvt.put("connection", "close");
    else
      mvt.put("connection", "keep-alive");
    String contentSecurityPolicy = generateCSP(allowScripts, allowFrames);
    mvt.put("content-security-policy", contentSecurityPolicy);
    mvt.put("x-content-security-policy", contentSecurityPolicy);
    mvt.put("x-webkit-csp", contentSecurityPolicy);
    StringBuilder buf = new StringBuilder(1024);
    buf.append("HTTP/1.1 ");
    buf.append(replyCode);
    buf.append(' ');
    buf.append(replyDescription);
    buf.append("\r\n");
    for(Enumeration<String> e = mvt.keys();e.hasMoreElements();) {
      String key = e.nextElement();
      Object[] list = mvt.getArray(key);
      key = fixKey(key);
      for(int i=0;i<list.length;i++) {
        String val = (String) list[i];
        buf.append(key);
        buf.append(": ");
        buf.append(val);
        buf.append("\r\n");
      }
    }
    buf.append("\r\n");
    sockOutputStream.write(buf.toString().getBytes("US-ASCII"));
  }
 
  private static String generateCSP(boolean allowScripts, boolean allowFrames) {
      StringBuilder sb = new StringBuilder();
      sb.append("default-src 'self'; script-src ");
      // "options inline-script" is old syntax needed for older Firefox's.
      sb.append(allowScripts ? "'self' 'unsafe-inline'; options inline-script" : "'none'");
      sb.append("; frame-src ");
        sb.append(allowFrames ? "'self'" : "'none'");
        sb.append("; object-src 'none'");
        // Always send unsafe-inline for CSS. This is safe given it can't use external stuff anyway.
        // It's only strictly needed for fproxy.
        sb.append("; style-src 'self' 'unsafe-inline'");
        return sb.toString();
    }

    static TimeZone TZ_UTC = TimeZone.getTimeZone("UTC");
 
  public static Date parseHTTPDate(String httpDate) throws java.text.ParseException{
    SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'",Locale.US);
    sdf.setTimeZone(TZ_UTC);
    return sdf.parse(httpDate);
  }
 
  /** Fix key case to be conformant to HTTP expectations.
   * Note that HTTP is case insensitive on header names, but we may as well
   * send something as close to the spec as possible in case of broken clients...
   */
  private static String fixKey(String key) {
    StringBuilder sb = new StringBuilder(key.length());
    char prev = 0;
    for(int i=0;i<key.length();i++) {
      char c = key.charAt(i);
      if((i == 0) || (prev == '-')) {
        c = Character.toUpperCase(c);
      }
      sb.append(c);
      prev = c;
    }
    return sb.toString();
  }
 
  /**
   * Handle an incoming connection. Blocking, obviously.
   */
  public static void handle(Socket sock, ToadletContainer container, PageMaker pageMaker, UserAlertManager userAlertManager, BookmarkManager bookmarkManager) {
    try {
      InputStream is = new BufferedInputStream(sock.getInputStream(), 4096);
     
      LineReadingInputStream lis = new LineReadingInputStream(is);
     
      while(true) {
       
        String firstLine = lis.readLine(32768, 128, false); // ISO-8859-1 or US-ASCII, _not_ UTF-8
        if (firstLine == null) {
          sock.close();
          return;
        } else if (firstLine.equals("")) {
          continue;
        }
       
        if(logMINOR)
          Logger.minor(ToadletContextImpl.class, "first line: "+firstLine);
       
        String[] split = firstLine.split(" ");
       
        if(split.length != 3)
          throw new ParseException("Could not parse request line (split.length="+split.length+"): "+firstLine, -1);
       
        if(!split[2].startsWith("HTTP/1."))
          throw new ParseException("Unrecognized protocol "+split[2], -1);
       
        URI uri;
        try {
          uri = URIPreEncoder.encodeURI(split[1]).normalize();
          if(logMINOR) Logger.minor(ToadletContextImpl.class, "URI: "+uri+" path "+uri.getPath()+" host "+uri.getHost()+" frag "+uri.getFragment()+" port "+uri.getPort()+" query "+uri.getQuery()+" scheme "+uri.getScheme());
        } catch (URISyntaxException e) {
          sendURIParseError(sock.getOutputStream(), true, e);
          return;
        }
        String method = split[0];
       
        MultiValueTable<String,String> headers = new MultiValueTable<String,String>();
       
        while(true) {
          String line = lis.readLine(32768, 128, false); // ISO-8859 or US-ASCII, not UTF-8
          if (line == null) {
            sock.close();
            return;
          }
          //System.out.println("Length="+line.length()+": "+line);
          if(line.length() == 0) break;
          int index = line.indexOf(':');
          if (index < 0) {
            throw new ParseException("Missing ':' in request header field", -1);
          }
          String before = line.substring(0, index).toLowerCase();
          String after = line.substring(index+1);
          after = after.trim();
          headers.put(before, after);
        }
       
        boolean disconnect = shouldDisconnectAfterHandled(split[2].equals("HTTP/1.0"), headers) || !container.enablePersistentConnections();

        boolean allowPost = container.allowPosts();
        BucketFactory bf = container.getBucketFactory();
       
        ToadletContextImpl ctx = new ToadletContextImpl(sock, headers, bf, pageMaker, container, userAlertManager, bookmarkManager, uri, container.generateUniqueID());
        ctx.shouldDisconnect = disconnect;
       
        /*
         * copy the data into a bucket now,
         * before we go into the redirect loop
         */
       
        Bucket data;

        boolean methodIsConfigurable = true;

        String slen = headers.get("content-length");

        if (METHODS_MUST_HAVE_DATA.contains(method)) {
          // <method> must have data
          methodIsConfigurable = false;
          if (slen == null) {
            ctx.shouldDisconnect = true;
            ctx.sendReplyHeaders(400, "Bad Request", null, null, -1);
            return;
          }
        } else if (METHODS_CANNOT_HAVE_DATA.contains(method)) {
          // <method> can not have data
          methodIsConfigurable = false;
          if (slen != null) {
            ctx.shouldDisconnect = true;
            ctx.sendReplyHeaders(400, "Bad Request", null, null, -1);
            return;
          }
        }

        if (slen != null) {
          long len;
          try {
            len = Integer.parseInt(slen);
            if(len < 0) throw new NumberFormatException("content-length less than 0");
          } catch (NumberFormatException e) {
            ctx.shouldDisconnect = true;
            ctx.sendReplyHeaders(400, "Bad Request", null, null, -1);
            return;
          }
          if(allowPost && ((!container.publicGatewayMode()) || ctx.isAllowedFullAccess())) {
            data = bf.makeBucket(len);
            BucketTools.copyFrom(data, is, len);
          } else {
            FileUtil.skipFully(is, len);
            if (method.equals("POST")) {
              ctx.sendMethodNotAllowed("POST", true);
            } else {
              sendError(sock.getOutputStream(), 403, "Forbidden", "Content not allowed in this configuration", true, null);
            }
            ctx.close();
            return;
          }
        } else {
          // we're not doing to use it, but we have to keep
          // the compiler happy
          data = null;
        }

        if (!container.enableExtendedMethodHandling()) {
          if (!METHODS_RESTRICTED_MODE.contains(method)) {
            sendError(sock.getOutputStream(), 403, "Forbidden", "Method not allowed in this configuration", true, null);
            return;
          }
        }

        // Handle it.
        try {
          boolean redirect = true;
          while (redirect) {
            // don't go around the loop unless set explicitly
            redirect = false;
           
            Toadlet t;
            try {
              t = container.findToadlet(uri);
            } catch (PermanentRedirectException e) {
              Toadlet.writePermanentRedirect(ctx, "Found elsewhere", e.newuri.toASCIIString());
              break;
            }
         
            if(t == null) {
              ctx.sendNoToadletError(ctx.shouldDisconnect);
              break;
            }

            // if the Toadlet does not support the method, we don't need to parse the data
            // also due this pre check a 'NoSuchMethodException' should never appear
            if (!(t.findSupportedMethods().contains(method))) {
              ctx.sendMethodNotAllowed(method, ctx.shouldDisconnect);
              break;
            }

            HTTPRequestImpl req = new HTTPRequestImpl(uri, data, ctx, method);
           
            // require form password if it's a POST, unless the toadlet requests otherwise
            if (method.equals("POST") && !t.allowPOSTWithoutPassword()) {
              if (!ctx.checkFormPassword(req, t.path())) {
                break;
              }
            }
           
            if(ctx.isAllowedFullAccess()) {
              ctx.getPageMaker().parseMode(req, container);
            }
           
            try {
              callToadletMethod(t, method, uri, req, ctx, data, sock, redirect);
            } catch (RedirectException re) {
              uri = re.newuri;
              redirect = true;
            } finally {
              req.freeParts();
            }
          }
          if(ctx.shouldDisconnect) {
            sock.close();
            return;
          }
        } finally {
          if(data != null) data.free();
        }
      }
     
    } catch (ParseException e) {
      try {
        sendError(sock.getOutputStream(), 400, "Bad Request", l10n("parseErrorWithError", "error", e.getMessage()), true, null);
      } catch (IOException e1) {
        // Ignore
      }
    } catch (TooLongException e) {
      try {
        sendError(sock.getOutputStream(), 400, "Bad Request", l10n("headersLineTooLong"), true, null);
      } catch (IOException e1) {
        // Ignore
      }
    } catch (IOException e) {
      // ignore and return
    } catch (ToadletContextClosedException e) {
      Logger.error(ToadletContextImpl.class, "ToadletContextClosedException while handling connection!");
    } catch (Throwable t) {
      Logger.error(ToadletContextImpl.class, "Caught error: "+t+" handling socket", t);
      try {
        String msg = "<html><head><title>"+NodeL10n.getBase().getString("Toadlet.internalErrorTitle")+
            "</title></head><body><h1>"+NodeL10n.getBase().getString("Toadlet.internalErrorPleaseReport")+"</h1><pre>";
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        t.printStackTrace(pw);
        pw.flush();
        msg = msg + sw.toString() + "</pre></body></html>";
        byte[] messageBytes = msg.getBytes("UTF-8");
        sendReplyHeaders(sock.getOutputStream(), 500, "Internal failure", null, "text/html; charset=UTF-8", messageBytes.length, null, true, false, false);
        sock.getOutputStream().write(messageBytes);
      } catch (IOException e1) {
        // ignore and return
      }
    }
  }
 
  private static void callToadletMethod(Toadlet t, String method, URI uri, HTTPRequestImpl req,
      ToadletContextImpl ctx, Bucket data, Socket sock, boolean methodIsConfigurable) throws Throwable {
    String methodName = Toadlet.HANDLE_METHOD_PREFIX + method;
    if("GET".equals(method)) {
      // Short cut the common case.
      if (data != null) {
        sendError(sock.getOutputStream(), 400, "Bad Request", "Content not allowed", true, null);
        ctx.close();
        return;
      }
      ctx.setActiveToadlet(t);
      t.handleMethodGET(uri, req, ctx);
      return;
    }
    try {
      Class<? extends Toadlet> c = t.getClass();
      Method m = c.getMethod(methodName, HANDLE_PARAMETERS);
      if (methodIsConfigurable) {
        AllowData anno = m.getAnnotation(AllowData.class);
        if (anno == null) {
          if (data != null) {
            sendError(sock.getOutputStream(), 400, "Bad Request", "Content not allowed", true, null);
            ctx.close();
            return;
          }
        } else if (anno.value()) {
          if (data == null) {
            sendError(sock.getOutputStream(), 400, "Bad Request", "Missing Content", true, null);
            ctx.close();
            return;
          }
        }
      }
      ctx.setActiveToadlet(t);
      Object arglist[] = new Object[] {uri, req, ctx};
      m.invoke(t, arglist);
    } catch (InvocationTargetException ite) {
      throw ite.getCause();
    }
  }

  private void setActiveToadlet(Toadlet t) {
    this.activeToadlet = t;
  }
 
  @Override
  public Toadlet activeToadlet() {
    return activeToadlet;
  }

  /**
   * Should the connection be closed after handling this request?
   * @param isHTTP10 Did the client specify HTTP/1.0?
   * @param headers Client headers.
   * @return True if the connection should be closed.
   */
  private static boolean shouldDisconnectAfterHandled(boolean isHTTP10, MultiValueTable<String,String> headers) {
    String connection = headers.get("connection");
    if(connection != null) {
      if(connection.equalsIgnoreCase("close"))
        return true;
     
      if(connection.equalsIgnoreCase("keep-alive"))
        return false;
    }
    if(isHTTP10 == true)
      return true;
    else
      // HTTP 1.1
      return false;
  }
 
  @Override
  public void writeData(byte[] data, int offset, int length) throws ToadletContextClosedException, IOException {
    if(closed) throw new ToadletContextClosedException();
    sockOutputStream.write(data, offset, length);
  }
 
  @Override
  public void writeData(byte[] data) throws ToadletContextClosedException, IOException {
    writeData(data, 0, data.length);
  }
 
  /**
   * @param data The Bucket which contains the reply data. This
   *        function assumes ownership of the Bucket, calling free()
   *        on it when done. If this behavior is undesired, callers
   *        can wrap their Bucket in a NoFreeBucket.
   *
   * @see freenet.support.io.NoFreeBucket
   */
  @Override
  public void writeData(Bucket data) throws ToadletContextClosedException, IOException {
    if(closed) throw new ToadletContextClosedException();
    BucketTools.copyTo(data, sockOutputStream, Long.MAX_VALUE);
    data.free();
  }
 
  @Override
  public BucketFactory getBucketFactory() {
    return bf;
  }

  @Override
  public HTMLNode addFormChild(HTMLNode parentNode, String target, String name) {
    return container.addFormChild(parentNode, target, name);
  }

  @Override
  public boolean isAllowedFullAccess() {
    return container.isAllowedFullAccess(remoteAddr);
  }
 
  @Override
  public boolean isAdvancedModeEnabled() {
    return container.isAdvancedModeEnabled();
  }

  @Override
  public boolean doRobots() {
    return container.doRobots();
  }

  @Override
  public void forceDisconnect() {
    this.shouldDisconnect = true;
  }

  @Override
  public ToadletContainer getContainer() {
    return container;
  }

  @Override
  public boolean disableProgressPage() {
    return container.disableProgressPage();
  }
 
  @Override
  public String getUniqueId(){
    return uniqueId;
  }
 
  @Override
  public URI getUri() {
    return uri;
  }

  @Override
  public REFILTER_POLICY getReFilterPolicy() {
    return container.getReFilterPolicy();
  }
}
TOP

Related Classes of freenet.clients.http.ToadletContextImpl

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.