|
ferencd@0
|
1 require 'net/protocol'
|
|
ferencd@0
|
2 require 'parslet'
|
|
ferencd@0
|
3
|
|
ferencd@0
|
4 module Net
|
|
ferencd@0
|
5 class HTTP < Protocol
|
|
ferencd@0
|
6 module Server
|
|
ferencd@0
|
7 #
|
|
ferencd@0
|
8 # Inspired by:
|
|
ferencd@0
|
9 #
|
|
ferencd@0
|
10 # * [Thin](https://github.com/macournoyer/thin/blob/master/ext/thin_parser/common.rl)
|
|
ferencd@0
|
11 # * [Unicorn](https://github.com/defunkt/unicorn/blob/master/ext/unicorn_http/unicorn_http_common.rl)
|
|
ferencd@0
|
12 # * [RFC 2616](http://www.w3.org/Protocols/rfc2616/rfc2616.html)
|
|
ferencd@0
|
13 #
|
|
ferencd@0
|
14 class Parser < Parslet::Parser
|
|
ferencd@0
|
15
|
|
ferencd@0
|
16 #
|
|
ferencd@0
|
17 # Character Classes
|
|
ferencd@0
|
18 #
|
|
ferencd@0
|
19 rule(:digit) { match['0-9'] }
|
|
ferencd@0
|
20 rule(:digits) { digit.repeat(1) }
|
|
ferencd@0
|
21 rule(:xdigit) { digit | match['a-fA-F'] }
|
|
ferencd@0
|
22 rule(:upper) { match['A-Z'] }
|
|
ferencd@0
|
23 rule(:lower) { match['a-z'] }
|
|
ferencd@0
|
24 rule(:alpha) { upper | lower }
|
|
ferencd@0
|
25 rule(:alnum) { alpha | digit }
|
|
ferencd@0
|
26 rule(:cntrl) { match['\x00-\x1f'] }
|
|
ferencd@0
|
27 rule(:ascii) { match['\x00-\x7f'] }
|
|
ferencd@0
|
28
|
|
ferencd@0
|
29 rule(:lws) { match[" \t"] }
|
|
ferencd@0
|
30 rule(:crlf) { str("\r\n") }
|
|
ferencd@0
|
31
|
|
ferencd@0
|
32 rule(:ctl) { cntrl | str("\x7f") }
|
|
ferencd@0
|
33 rule(:text) { lws | (ctl.absnt? >> ascii) }
|
|
ferencd@0
|
34
|
|
ferencd@0
|
35 rule(:safe) { charset('$', '-', '_', '.') }
|
|
ferencd@0
|
36 rule(:extra) { charset('!', '*', "'", '(', ')', ',') }
|
|
ferencd@0
|
37 rule(:reserved) { charset(';', '/', '?', ':', '@', '&', '=', '+') }
|
|
ferencd@0
|
38 rule(:sorta_safe) { charset('"', '<', '>') }
|
|
ferencd@0
|
39
|
|
ferencd@0
|
40 rule(:unsafe) { ctl | charset(' ', '#', '%') | sorta_safe }
|
|
ferencd@0
|
41 rule(:national) {
|
|
ferencd@0
|
42 (alpha | digit | reserved | extra | safe | unsafe).absnt? >> any
|
|
ferencd@0
|
43 }
|
|
ferencd@0
|
44
|
|
ferencd@0
|
45 rule(:unreserved) { alpha | digit | safe | extra | national }
|
|
ferencd@0
|
46 rule(:uescape) { str("%u") >> xdigit >> xdigit >> xdigit >> xdigit }
|
|
ferencd@0
|
47 rule(:escape) { str("%") >> xdigit >> xdigit }
|
|
ferencd@0
|
48 rule(:uchar) { unreserved | uescape | escape | sorta_safe }
|
|
ferencd@0
|
49 rule(:pchar) { uchar | charset(':', '@', '&', '=', '+') }
|
|
ferencd@0
|
50 rule(:separators) {
|
|
ferencd@0
|
51 lws | charset(
|
|
ferencd@0
|
52 '(', ')', '<', '>', '@', ',', ';', ':', "\\", '"', '/', '[', ']',
|
|
ferencd@0
|
53 '?', '=', '{', '}'
|
|
ferencd@0
|
54 )
|
|
ferencd@0
|
55 }
|
|
ferencd@0
|
56
|
|
ferencd@0
|
57 #
|
|
ferencd@0
|
58 # Elements
|
|
ferencd@0
|
59 #
|
|
ferencd@0
|
60 rule(:token) { (ctl | separators).absnt? >> ascii }
|
|
ferencd@0
|
61
|
|
ferencd@0
|
62 rule(:comment_text) { (str('(') | str(')')).absnt? >> text }
|
|
ferencd@0
|
63 rule(:comment) { str('(') >> comment_text.repeat >> str(')') }
|
|
ferencd@0
|
64
|
|
ferencd@0
|
65 rule(:quoted_pair) { str("\\") >> ascii }
|
|
ferencd@0
|
66 rule(:quoted_text) { quoted_pair | str('"').absnt? >> text }
|
|
ferencd@0
|
67 rule(:quoted_string) { str('"') >> quoted_text >> str('"') }
|
|
ferencd@0
|
68
|
|
ferencd@0
|
69 #
|
|
ferencd@0
|
70 # URI Elements
|
|
ferencd@0
|
71 #
|
|
ferencd@0
|
72 rule(:scheme) {
|
|
ferencd@0
|
73 (alpha | digit | charset('+', '-', '.')).repeat
|
|
ferencd@0
|
74 }
|
|
ferencd@0
|
75 rule(:host_name) {
|
|
ferencd@0
|
76 (alnum | charset('-', '_', '.')).repeat(1)
|
|
ferencd@0
|
77 }
|
|
ferencd@0
|
78 rule(:user_info) {
|
|
ferencd@0
|
79 (
|
|
ferencd@0
|
80 unreserved | escape | charset(';', ':', '&', '=', '+')
|
|
ferencd@0
|
81 ).repeat(1)
|
|
ferencd@0
|
82 }
|
|
ferencd@0
|
83
|
|
ferencd@0
|
84 rule(:path) { pchar.repeat(1) >> (str('/') >> pchar.repeat).repeat }
|
|
ferencd@0
|
85 rule(:query_string) { (uchar | reserved).repeat }
|
|
ferencd@0
|
86 rule(:param) { (pchar | str('/')).repeat }
|
|
ferencd@0
|
87 rule(:params) { param >> (str(';') >> param).repeat }
|
|
ferencd@0
|
88 rule(:frag) { (uchar | reserved).repeat }
|
|
ferencd@0
|
89
|
|
ferencd@0
|
90 rule(:uri_path) {
|
|
ferencd@0
|
91 (str('/').maybe >> path.maybe).as(:path) >>
|
|
ferencd@0
|
92 (str(';') >> params.as(:params)).maybe >>
|
|
ferencd@0
|
93 (str('?') >> query_string.as(:query)).maybe >>
|
|
ferencd@0
|
94 (str('#') >> frag.as(:fragment)).maybe
|
|
ferencd@0
|
95 }
|
|
ferencd@0
|
96
|
|
ferencd@0
|
97 rule(:uri) {
|
|
ferencd@0
|
98 scheme.as(:scheme) >> str(':') >> str('//').maybe >>
|
|
ferencd@0
|
99 (user_info.as(:user_info) >> str('@')).maybe >>
|
|
ferencd@0
|
100 host_name.as(:host) >>
|
|
ferencd@0
|
101 (str(':') >> digits.as(:port)).maybe >>
|
|
ferencd@0
|
102 uri_path
|
|
ferencd@0
|
103 }
|
|
ferencd@0
|
104
|
|
ferencd@0
|
105 rule(:request_uri) { str('*') | uri | uri_path }
|
|
ferencd@0
|
106
|
|
ferencd@0
|
107 #
|
|
ferencd@0
|
108 # HTTP Elements
|
|
ferencd@0
|
109 #
|
|
ferencd@0
|
110 rule(:request_method) { upper.repeat(1,20) | token.repeat(1) }
|
|
ferencd@0
|
111
|
|
ferencd@0
|
112 rule(:version_number) { digits >> str('.') >> digits }
|
|
ferencd@0
|
113 rule(:http_version) { str('HTTP/') >> version_number.as(:version) }
|
|
ferencd@0
|
114 rule(:request_line) {
|
|
ferencd@0
|
115 request_method.as(:method) >> str(' ') >>
|
|
ferencd@0
|
116 request_uri.as(:uri) >> str(' ') >>
|
|
ferencd@0
|
117 http_version
|
|
ferencd@0
|
118 }
|
|
ferencd@0
|
119
|
|
ferencd@0
|
120 rule(:header_name) { (str(':').absnt? >> token).repeat(1) }
|
|
ferencd@0
|
121 rule(:header_value) {
|
|
ferencd@0
|
122 (text | token | separators | quoted_string).repeat(1)
|
|
ferencd@0
|
123 }
|
|
ferencd@0
|
124
|
|
ferencd@0
|
125 rule(:header) {
|
|
ferencd@0
|
126 header_name.as(:name) >> str(':') >> lws.repeat(1) >>
|
|
ferencd@0
|
127 header_value.as(:value) >> crlf
|
|
ferencd@0
|
128 }
|
|
ferencd@0
|
129 rule(:request) {
|
|
ferencd@0
|
130 request_line >> crlf >>
|
|
ferencd@0
|
131 header.repeat.as(:headers) >> crlf
|
|
ferencd@0
|
132 }
|
|
ferencd@0
|
133
|
|
ferencd@0
|
134 root :request
|
|
ferencd@0
|
135
|
|
ferencd@0
|
136 protected
|
|
ferencd@0
|
137
|
|
ferencd@0
|
138 #
|
|
ferencd@0
|
139 # Creates a matcher for the given characters.
|
|
ferencd@0
|
140 #
|
|
ferencd@0
|
141 # @param [Array<String>] chars
|
|
ferencd@0
|
142 # The characters to match.
|
|
ferencd@0
|
143 #
|
|
ferencd@0
|
144 def charset(*chars)
|
|
ferencd@0
|
145 match[chars.map { |c| Regexp.escape(c) }.join]
|
|
ferencd@0
|
146 end
|
|
ferencd@0
|
147
|
|
ferencd@0
|
148 end
|
|
ferencd@0
|
149 end
|
|
ferencd@0
|
150 end
|
|
ferencd@0
|
151 end
|