package org.jruby.puma; import org.jruby.Ruby; import org.jruby.RubyClass; import org.jruby.RubyHash; import org.jruby.RubyModule; import org.jruby.RubyNumeric; import org.jruby.RubyObject; import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.exceptions.RaiseException; import org.jruby.util.ByteList; /** * @author Ola Bini * @author Charles Oliver Nutter */ public class Http11 extends RubyObject { public final static int MAX_FIELD_NAME_LENGTH = 256; public final static String MAX_FIELD_NAME_LENGTH_ERR = "HTTP element FIELD_NAME is longer than the 256 allowed length."; public final static int MAX_FIELD_VALUE_LENGTH = 80 * 1024; public final static String MAX_FIELD_VALUE_LENGTH_ERR = "HTTP element FIELD_VALUE is longer than the 81920 allowed length."; public final static int MAX_REQUEST_URI_LENGTH = 1024 * 12; public final static String MAX_REQUEST_URI_LENGTH_ERR = "HTTP element REQUEST_URI is longer than the 12288 allowed length."; public final static int MAX_FRAGMENT_LENGTH = 1024; public final static String MAX_FRAGMENT_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 1024 allowed length."; public final static int MAX_REQUEST_PATH_LENGTH = 8192; public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 8192 allowed length."; public final static int MAX_QUERY_STRING_LENGTH = 1024 * 10; public final static String MAX_QUERY_STRING_LENGTH_ERR = "HTTP element QUERY_STRING is longer than the 10240 allowed length."; public final static int MAX_HEADER_LENGTH = 1024 * (80 + 32); public final static String MAX_HEADER_LENGTH_ERR = "HTTP element HEADER is longer than the 114688 allowed length."; public static final ByteList CONTENT_TYPE_BYTELIST = new ByteList(ByteList.plain("CONTENT_TYPE")); public static final ByteList CONTENT_LENGTH_BYTELIST = new ByteList(ByteList.plain("CONTENT_LENGTH")); public static final ByteList HTTP_PREFIX_BYTELIST = new ByteList(ByteList.plain("HTTP_")); public static final ByteList COMMA_SPACE_BYTELIST = new ByteList(ByteList.plain(", ")); public static final ByteList REQUEST_METHOD_BYTELIST = new ByteList(ByteList.plain("REQUEST_METHOD")); public static final ByteList REQUEST_URI_BYTELIST = new ByteList(ByteList.plain("REQUEST_URI")); public static final ByteList FRAGMENT_BYTELIST = new ByteList(ByteList.plain("FRAGMENT")); public static final ByteList REQUEST_PATH_BYTELIST = new ByteList(ByteList.plain("REQUEST_PATH")); public static final ByteList QUERY_STRING_BYTELIST = new ByteList(ByteList.plain("QUERY_STRING")); public static final ByteList SERVER_PROTOCOL_BYTELIST = new ByteList(ByteList.plain("SERVER_PROTOCOL")); private static ObjectAllocator ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klass) { return new Http11(runtime, klass); } }; public static void createHttp11(Ruby runtime) { RubyModule mPuma = runtime.defineModule("Puma"); mPuma.defineClassUnder("HttpParserError",runtime.getClass("IOError"),runtime.getClass("IOError").getAllocator()); RubyClass cHttpParser = mPuma.defineClassUnder("HttpParser",runtime.getObject(),ALLOCATOR); cHttpParser.defineAnnotatedMethods(Http11.class); } private Ruby runtime; private Http11Parser hp; private RubyString body; public Http11(Ruby runtime, RubyClass clazz) { super(runtime,clazz); this.runtime = runtime; this.hp = new Http11Parser(); this.hp.parser.init(); } public static void validateMaxLength(Ruby runtime, int len, int max, String msg) { if(len>max) { throw newHTTPParserError(runtime, msg); } } private static RaiseException newHTTPParserError(Ruby runtime, String msg) { return runtime.newRaiseException(getHTTPParserError(runtime), msg); } private static RubyClass getHTTPParserError(Ruby runtime) { // Cheaper to look this up lazily than cache eagerly and consume a field, since it's rarely encountered return (RubyClass)runtime.getModule("Puma").getConstant("HttpParserError"); } public static void http_field(Ruby runtime, RubyHash req, ByteList buffer, int field, int flen, int value, int vlen) { RubyString f; IRubyObject v; validateMaxLength(runtime, flen, MAX_FIELD_NAME_LENGTH, MAX_FIELD_NAME_LENGTH_ERR); validateMaxLength(runtime, vlen, MAX_FIELD_VALUE_LENGTH, MAX_FIELD_VALUE_LENGTH_ERR); ByteList b = new ByteList(buffer,field,flen); for(int i = 0,j = b.length();i 0 && Character.isWhitespace(buffer.get(value + vlen - 1))) vlen--; if (b.equals(CONTENT_LENGTH_BYTELIST) || b.equals(CONTENT_TYPE_BYTELIST)) { f = RubyString.newString(runtime, b); } else { f = RubyString.newStringShared(runtime, HTTP_PREFIX_BYTELIST); f.cat(b); } b = new ByteList(buffer, value, vlen); v = req.fastARef(f); if (v == null || v.isNil()) { req.fastASet(f, RubyString.newString(runtime, b)); } else { RubyString vs = v.convertToString(); vs.cat(COMMA_SPACE_BYTELIST); vs.cat(b); } } public static void request_method(Ruby runtime, RubyHash req, ByteList buffer, int at, int length) { RubyString val = RubyString.newString(runtime,new ByteList(buffer,at,length)); req.fastASet(RubyString.newStringShared(runtime, REQUEST_METHOD_BYTELIST),val); } public static void request_uri(Ruby runtime, RubyHash req, ByteList buffer, int at, int length) { validateMaxLength(runtime, length, MAX_REQUEST_URI_LENGTH, MAX_REQUEST_URI_LENGTH_ERR); RubyString val = RubyString.newString(runtime,new ByteList(buffer,at,length)); req.fastASet(RubyString.newStringShared(runtime, REQUEST_URI_BYTELIST),val); } public static void fragment(Ruby runtime, RubyHash req, ByteList buffer, int at, int length) { validateMaxLength(runtime, length, MAX_FRAGMENT_LENGTH, MAX_FRAGMENT_LENGTH_ERR); RubyString val = RubyString.newString(runtime,new ByteList(buffer,at,length)); req.fastASet(RubyString.newStringShared(runtime, FRAGMENT_BYTELIST),val); } public static void request_path(Ruby runtime, RubyHash req, ByteList buffer, int at, int length) { validateMaxLength(runtime, length, MAX_REQUEST_PATH_LENGTH, MAX_REQUEST_PATH_LENGTH_ERR); RubyString val = RubyString.newString(runtime,new ByteList(buffer,at,length)); req.fastASet(RubyString.newStringShared(runtime, REQUEST_PATH_BYTELIST),val); } public static void query_string(Ruby runtime, RubyHash req, ByteList buffer, int at, int length) { validateMaxLength(runtime, length, MAX_QUERY_STRING_LENGTH, MAX_QUERY_STRING_LENGTH_ERR); RubyString val = RubyString.newString(runtime,new ByteList(buffer,at,length)); req.fastASet(RubyString.newStringShared(runtime, QUERY_STRING_BYTELIST),val); } public static void server_protocol(Ruby runtime, RubyHash req, ByteList buffer, int at, int length) { RubyString val = RubyString.newString(runtime,new ByteList(buffer,at,length)); req.fastASet(RubyString.newStringShared(runtime, SERVER_PROTOCOL_BYTELIST),val); } public void header_done(Ruby runtime, RubyHash req, ByteList buffer, int at, int length) { body = RubyString.newStringShared(runtime, new ByteList(buffer, at, length)); } @JRubyMethod public IRubyObject initialize() { this.hp.parser.init(); return this; } @JRubyMethod public IRubyObject reset() { this.hp.parser.init(); return runtime.getNil(); } @JRubyMethod public IRubyObject finish() { this.hp.finish(); return this.hp.is_finished() ? runtime.getTrue() : runtime.getFalse(); } @JRubyMethod public IRubyObject execute(IRubyObject req_hash, IRubyObject data, IRubyObject start) { int from = RubyNumeric.fix2int(start); ByteList d = ((RubyString)data).getByteList(); if(from >= d.length()) { throw newHTTPParserError(runtime, "Requested start is after data buffer end."); } else { Http11Parser hp = this.hp; Http11Parser.HttpParser parser = hp.parser; parser.data = (RubyHash) req_hash; hp.execute(runtime, this, d,from); validateMaxLength(runtime, parser.nread,MAX_HEADER_LENGTH, MAX_HEADER_LENGTH_ERR); if(hp.has_error()) { throw newHTTPParserError(runtime, "Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?"); } else { return runtime.newFixnum(parser.nread); } } } @JRubyMethod(name = "error?") public IRubyObject has_error() { return this.hp.has_error() ? runtime.getTrue() : runtime.getFalse(); } @JRubyMethod(name = "finished?") public IRubyObject is_finished() { return this.hp.is_finished() ? runtime.getTrue() : runtime.getFalse(); } @JRubyMethod public IRubyObject nread() { return runtime.newFixnum(this.hp.parser.nread); } @JRubyMethod public IRubyObject body() { return body; } }// Http11