ferencd@0: require 'net/protocol' ferencd@0: require 'parslet' ferencd@0: ferencd@0: module Net ferencd@0: class HTTP < Protocol ferencd@0: module Server ferencd@0: # ferencd@0: # Inspired by: ferencd@0: # ferencd@0: # * [Thin](https://github.com/macournoyer/thin/blob/master/ext/thin_parser/common.rl) ferencd@0: # * [Unicorn](https://github.com/defunkt/unicorn/blob/master/ext/unicorn_http/unicorn_http_common.rl) ferencd@0: # * [RFC 2616](http://www.w3.org/Protocols/rfc2616/rfc2616.html) ferencd@0: # ferencd@0: class Parser < Parslet::Parser ferencd@0: ferencd@0: # ferencd@0: # Character Classes ferencd@0: # ferencd@0: rule(:digit) { match['0-9'] } ferencd@0: rule(:digits) { digit.repeat(1) } ferencd@0: rule(:xdigit) { digit | match['a-fA-F'] } ferencd@0: rule(:upper) { match['A-Z'] } ferencd@0: rule(:lower) { match['a-z'] } ferencd@0: rule(:alpha) { upper | lower } ferencd@0: rule(:alnum) { alpha | digit } ferencd@0: rule(:cntrl) { match['\x00-\x1f'] } ferencd@0: rule(:ascii) { match['\x00-\x7f'] } ferencd@0: ferencd@0: rule(:lws) { match[" \t"] } ferencd@0: rule(:crlf) { str("\r\n") } ferencd@0: ferencd@0: rule(:ctl) { cntrl | str("\x7f") } ferencd@0: rule(:text) { lws | (ctl.absnt? >> ascii) } ferencd@0: ferencd@0: rule(:safe) { charset('$', '-', '_', '.') } ferencd@0: rule(:extra) { charset('!', '*', "'", '(', ')', ',') } ferencd@0: rule(:reserved) { charset(';', '/', '?', ':', '@', '&', '=', '+') } ferencd@0: rule(:sorta_safe) { charset('"', '<', '>') } ferencd@0: ferencd@0: rule(:unsafe) { ctl | charset(' ', '#', '%') | sorta_safe } ferencd@0: rule(:national) { ferencd@0: (alpha | digit | reserved | extra | safe | unsafe).absnt? >> any ferencd@0: } ferencd@0: ferencd@0: rule(:unreserved) { alpha | digit | safe | extra | national } ferencd@0: rule(:uescape) { str("%u") >> xdigit >> xdigit >> xdigit >> xdigit } ferencd@0: rule(:escape) { str("%") >> xdigit >> xdigit } ferencd@0: rule(:uchar) { unreserved | uescape | escape | sorta_safe } ferencd@0: rule(:pchar) { uchar | charset(':', '@', '&', '=', '+') } ferencd@0: rule(:separators) { ferencd@0: lws | charset( ferencd@0: '(', ')', '<', '>', '@', ',', ';', ':', "\\", '"', '/', '[', ']', ferencd@0: '?', '=', '{', '}' ferencd@0: ) ferencd@0: } ferencd@0: ferencd@0: # ferencd@0: # Elements ferencd@0: # ferencd@0: rule(:token) { (ctl | separators).absnt? >> ascii } ferencd@0: ferencd@0: rule(:comment_text) { (str('(') | str(')')).absnt? >> text } ferencd@0: rule(:comment) { str('(') >> comment_text.repeat >> str(')') } ferencd@0: ferencd@0: rule(:quoted_pair) { str("\\") >> ascii } ferencd@0: rule(:quoted_text) { quoted_pair | str('"').absnt? >> text } ferencd@0: rule(:quoted_string) { str('"') >> quoted_text >> str('"') } ferencd@0: ferencd@0: # ferencd@0: # URI Elements ferencd@0: # ferencd@0: rule(:scheme) { ferencd@0: (alpha | digit | charset('+', '-', '.')).repeat ferencd@0: } ferencd@0: rule(:host_name) { ferencd@0: (alnum | charset('-', '_', '.')).repeat(1) ferencd@0: } ferencd@0: rule(:user_info) { ferencd@0: ( ferencd@0: unreserved | escape | charset(';', ':', '&', '=', '+') ferencd@0: ).repeat(1) ferencd@0: } ferencd@0: ferencd@0: rule(:path) { pchar.repeat(1) >> (str('/') >> pchar.repeat).repeat } ferencd@0: rule(:query_string) { (uchar | reserved).repeat } ferencd@0: rule(:param) { (pchar | str('/')).repeat } ferencd@0: rule(:params) { param >> (str(';') >> param).repeat } ferencd@0: rule(:frag) { (uchar | reserved).repeat } ferencd@0: ferencd@0: rule(:uri_path) { ferencd@0: (str('/').maybe >> path.maybe).as(:path) >> ferencd@0: (str(';') >> params.as(:params)).maybe >> ferencd@0: (str('?') >> query_string.as(:query)).maybe >> ferencd@0: (str('#') >> frag.as(:fragment)).maybe ferencd@0: } ferencd@0: ferencd@0: rule(:uri) { ferencd@0: scheme.as(:scheme) >> str(':') >> str('//').maybe >> ferencd@0: (user_info.as(:user_info) >> str('@')).maybe >> ferencd@0: host_name.as(:host) >> ferencd@0: (str(':') >> digits.as(:port)).maybe >> ferencd@0: uri_path ferencd@0: } ferencd@0: ferencd@0: rule(:request_uri) { str('*') | uri | uri_path } ferencd@0: ferencd@0: # ferencd@0: # HTTP Elements ferencd@0: # ferencd@0: rule(:request_method) { upper.repeat(1,20) | token.repeat(1) } ferencd@0: ferencd@0: rule(:version_number) { digits >> str('.') >> digits } ferencd@0: rule(:http_version) { str('HTTP/') >> version_number.as(:version) } ferencd@0: rule(:request_line) { ferencd@0: request_method.as(:method) >> str(' ') >> ferencd@0: request_uri.as(:uri) >> str(' ') >> ferencd@0: http_version ferencd@0: } ferencd@0: ferencd@0: rule(:header_name) { (str(':').absnt? >> token).repeat(1) } ferencd@0: rule(:header_value) { ferencd@0: (text | token | separators | quoted_string).repeat(1) ferencd@0: } ferencd@0: ferencd@0: rule(:header) { ferencd@0: header_name.as(:name) >> str(':') >> lws.repeat(1) >> ferencd@0: header_value.as(:value) >> crlf ferencd@0: } ferencd@0: rule(:request) { ferencd@0: request_line >> crlf >> ferencd@0: header.repeat.as(:headers) >> crlf ferencd@0: } ferencd@0: ferencd@0: root :request ferencd@0: ferencd@0: protected ferencd@0: ferencd@0: # ferencd@0: # Creates a matcher for the given characters. ferencd@0: # ferencd@0: # @param [Array] chars ferencd@0: # The characters to match. ferencd@0: # ferencd@0: def charset(*chars) ferencd@0: match[chars.map { |c| Regexp.escape(c) }.join] ferencd@0: end ferencd@0: ferencd@0: end ferencd@0: end ferencd@0: end ferencd@0: end