public void sendReplyHeaders( int replyCode, String replyDescription, MultiValueTable<String, String> mvt, String mimeType, long contentLength) throws ToadletContextClosedException, IOException { sendReplyHeaders(replyCode, replyDescription, mvt, mimeType, contentLength, null); }
/** * 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); os.write(messageBytes); }
public void sendReplyHeaders( int replyCode, String replyDescription, MultiValueTable<String, String> mvt, String mimeType, long contentLength, Date mTime) throws ToadletContextClosedException, IOException { if (closed) throw new ToadletContextClosedException(); if (sentReplyHeaders) { throw new IllegalStateException("Already sent headers!"); } sentReplyHeaders = true; sendReplyHeaders( sockOutputStream, replyCode, replyDescription, mvt, mimeType, contentLength, mTime, shouldDisconnect); }
/** 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 } } }