Exemplo n.º 1
0
  /** Handle an incoming connection. Blocking, obviously. */
  public static void handle(Socket sock, ToadletContainer container, PageMaker pageMaker) {
    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;
        }

        boolean logMINOR = Logger.shouldLog(Logger.MINOR, ToadletContextImpl.class);

        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);

        if (!split[2].startsWith("HTTP/1."))
          throw new ParseException("Unrecognized protocol " + split[2]);

        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");
          }
          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);
        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);
            try {
              String methodName = "handleMethod" + method;
              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();
              }
            } 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 {
        sendError(sock.getOutputStream(), 500, "Internal Error", t.toString(), true, null);
      } catch (IOException e1) {
        // ignore and return
      }
    }
  }
  /** Handle an incoming connection. Blocking, obviously. */
  public static void handle(Socket sock, ToadletContainer container, PageMaker pageMaker) {
    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;
        }

        boolean logMINOR = Logger.shouldLog(Logger.MINOR, ToadletContextImpl.class);

        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);

        if (!split[2].startsWith("HTTP/1."))
          throw new ParseException("Unrecognized protocol " + split[2]);

        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");
          }
          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);
        ctx.shouldDisconnect = disconnect;

        /*
         * if we're handling a POST, copy the data into a bucket now,
         * before we go into the redirect loop
         */

        Bucket data;

        if (method.equals("POST")) {
          String slen = headers.get("content-length");
          if (slen == null) {
            sendError(
                sock.getOutputStream(),
                400,
                "Bad Request",
                l10n("noContentLengthInPOST"),
                true,
                null);
            return;
          }
          long len;
          try {
            len = Integer.parseInt(slen);
            if (len < 0) throw new NumberFormatException("content-length less than 0");
          } catch (NumberFormatException e) {
            sendError(
                sock.getOutputStream(),
                400,
                "Bad Request",
                l10n("cannotParseContentLengthWithError", "error", e.toString()),
                true,
                null);
            return;
          }
          if (allowPost && ((!container.publicGatewayMode()) || ctx.isAllowedFullAccess())) {

            data = bf.makeBucket(len);
            BucketTools.copyFrom(data, is, len);
          } else {
            FileUtil.skipFully(is, len);
            ctx.sendMethodNotAllowed("POST", true);
            ctx.close();
            return;
          }
        } else {
          // we're not doing to use it, but we have to keep
          // the compiler happy
          data = null;
        }

        // 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;
            }

            HTTPRequestImpl req = new HTTPRequestImpl(uri, data, ctx, method);
            try {
              if (method.equals("GET")) {
                ctx.setActiveToadlet(t);
                t.handleGet(uri, req, ctx);
                ctx.close();
              } else if (method.equals("POST")) {
                ctx.setActiveToadlet(t);
                t.handlePost(uri, req, ctx);
              } else {
                ctx.sendMethodNotAllowed(method, ctx.shouldDisconnect);
                ctx.close();
              }
            } 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 {
        sendError(sock.getOutputStream(), 500, "Internal Error", t.toString(), true, null);
      } catch (IOException e1) {
        // ignore and return
      }
    }
  }