ebb 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README +3 -5
- data/VERSION +1 -1
- data/bin/ebb_rails +9 -18
- data/ruby_lib/daemonizable.rb +34 -70
- data/ruby_lib/ebb.rb +16 -6
- data/src/ebb.c +82 -70
- data/src/ebb.h +29 -18
- data/src/ebb_ruby.c +69 -61
- data/src/extconf.rb +2 -1
- data/src/parser.c +871 -538
- data/src/parser.h +2 -2
- data/{benchmark/test.rb → test/basic_test.rb} +72 -19
- data/test/echo_server.rb +16 -0
- data/test/env_test.rb +114 -0
- metadata +12 -11
- data/src/parser.rl +0 -200
data/src/parser.h
CHANGED
@@ -17,6 +17,7 @@ typedef void (*field_cb)(void *data, const char *field, size_t flen, const char
|
|
17
17
|
|
18
18
|
typedef struct http_parser {
|
19
19
|
int cs;
|
20
|
+
int overflow_error;
|
20
21
|
size_t body_start;
|
21
22
|
int content_len;
|
22
23
|
size_t nread;
|
@@ -36,10 +37,9 @@ typedef struct http_parser {
|
|
36
37
|
element_cb http_version;
|
37
38
|
element_cb header_done;
|
38
39
|
element_cb content_length;
|
39
|
-
|
40
40
|
} http_parser;
|
41
41
|
|
42
|
-
|
42
|
+
void http_parser_init(http_parser *parser);
|
43
43
|
int http_parser_finish(http_parser *parser);
|
44
44
|
size_t http_parser_execute(http_parser *parser, const char *data, size_t len, size_t off);
|
45
45
|
int http_parser_has_error(http_parser *parser);
|
@@ -1,13 +1,17 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../ruby_lib/ebb'
|
2
2
|
require 'test/unit'
|
3
3
|
require 'net/http'
|
4
|
-
require '
|
4
|
+
require 'socket'
|
5
|
+
require 'rubygems'
|
6
|
+
require 'json'
|
5
7
|
|
8
|
+
PORT = 4044
|
6
9
|
|
7
10
|
class EbbTest < Test::Unit::TestCase
|
8
11
|
def setup
|
9
12
|
@pid = fork do
|
10
|
-
server = Ebb::Server.new(self, :port =>
|
13
|
+
server = Ebb::Server.new(self, :port => PORT)
|
14
|
+
STDOUT.reopen "/dev/null", "a"
|
11
15
|
server.start
|
12
16
|
end
|
13
17
|
sleep 0.5
|
@@ -19,11 +23,11 @@ class EbbTest < Test::Unit::TestCase
|
|
19
23
|
end
|
20
24
|
|
21
25
|
def get(path)
|
22
|
-
Net::HTTP.get_response(URI.parse("http://0.0.0.0
|
26
|
+
Net::HTTP.get_response(URI.parse("http://0.0.0.0:#{PORT}#{path}"))
|
23
27
|
end
|
24
28
|
|
25
29
|
def post(path, data)
|
26
|
-
Net::HTTP.post_form(URI.parse("http://0.0.0.0
|
30
|
+
Net::HTTP.post_form(URI.parse("http://0.0.0.0:#{PORT}#{path}"), data)
|
27
31
|
end
|
28
32
|
|
29
33
|
@@responses = {}
|
@@ -35,12 +39,6 @@ class EbbTest < Test::Unit::TestCase
|
|
35
39
|
raise "bytes called with n <= 0" if n <= 0
|
36
40
|
body = @@responses[n] || "C"*n
|
37
41
|
status = 200
|
38
|
-
|
39
|
-
elsif commands.include?('env')
|
40
|
-
env.delete('rack.input') # delete this because it's hard to marshal
|
41
|
-
env.delete('rack.errors')
|
42
|
-
body = Base64.encode64(Marshal.dump(env))
|
43
|
-
status = 200
|
44
42
|
|
45
43
|
elsif commands.include?('test_post_length')
|
46
44
|
input_body = ""
|
@@ -89,7 +87,7 @@ class EbbTest < Test::Unit::TestCase
|
|
89
87
|
|
90
88
|
# this is rough but does detect major problems
|
91
89
|
def test_ab
|
92
|
-
r = %x{ab -n 1000 -c 50 -q http://0.0.0.0
|
90
|
+
r = %x{ab -n 1000 -c 50 -q http://0.0.0.0:#{PORT}/bytes/123}
|
93
91
|
assert r =~ /Requests per second:\s*(\d+)/, r
|
94
92
|
assert $1.to_i > 100, r
|
95
93
|
end
|
@@ -100,29 +98,84 @@ class EbbTest < Test::Unit::TestCase
|
|
100
98
|
assert_equal 200, response.code.to_i, response.body
|
101
99
|
end
|
102
100
|
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class EnvTest < Test::Unit::TestCase
|
104
|
+
def call(env)
|
105
|
+
env.delete('rack.input')
|
106
|
+
env.delete('rack.errors')
|
107
|
+
[200, {'Content-Type' => 'text/json'}, env.to_json]
|
108
|
+
end
|
103
109
|
|
104
|
-
def
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
110
|
+
def setup
|
111
|
+
@pid = fork do
|
112
|
+
server = Ebb::Server.new(self, :port => PORT)
|
113
|
+
STDOUT.reopen "/dev/null", "a"
|
114
|
+
server.start
|
115
|
+
end
|
116
|
+
sleep 0.5
|
117
|
+
end
|
118
|
+
|
119
|
+
def teardown
|
120
|
+
Process.kill('KILL', @pid)
|
121
|
+
sleep 0.5
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
### Tests from Mongrel http11
|
126
|
+
|
127
|
+
def request(request_string)
|
128
|
+
socket = TCPSocket.new("0.0.0.0", PORT)
|
129
|
+
socket.write(request_string)
|
130
|
+
lines = []
|
131
|
+
socket.read(500000).each_line { |l| lines << l }
|
132
|
+
env = JSON.parse(lines.last)
|
133
|
+
end
|
134
|
+
|
135
|
+
def assert_drops_connection(request_string)
|
136
|
+
socket = TCPSocket.new("0.0.0.0", PORT)
|
137
|
+
socket.write(request_string)
|
138
|
+
assert socket.closed?
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
def test_parse_simple
|
143
|
+
env = request("GET / HTTP/1.1\r\n\r\n")
|
109
144
|
assert_equal 'HTTP/1.1', env['SERVER_PROTOCOL']
|
145
|
+
assert_equal '/', env['REQUEST_PATH']
|
146
|
+
assert_equal 'HTTP/1.1', env['HTTP_VERSION']
|
147
|
+
assert_equal '/', env['REQUEST_URI']
|
110
148
|
assert_equal 'CGI/1.2', env['GATEWAY_INTERFACE']
|
111
|
-
assert_equal '
|
112
|
-
|
113
|
-
|
149
|
+
assert_equal 'GET', env['REQUEST_METHOD']
|
150
|
+
assert_nil env['FRAGMENT']
|
151
|
+
assert_nil env['QUERY_STRING']
|
114
152
|
end
|
115
153
|
end
|
116
154
|
|
155
|
+
|
117
156
|
class EbbRailsTest < Test::Unit::TestCase
|
118
157
|
# just to make sure there isn't some load error
|
119
158
|
def test_ebb_rails_version
|
120
159
|
out = %x{ruby #{Ebb::LIBDIR}/../bin/ebb_rails -v}
|
121
160
|
assert_match %r{Ebb #{Ebb::VERSION}}, out
|
122
161
|
end
|
162
|
+
|
163
|
+
# def get(path)
|
164
|
+
# Net::HTTP.get_response(URI.parse("http://0.0.0.0:4043#{path}"))
|
165
|
+
# end
|
166
|
+
#
|
167
|
+
# def test_starting_with_many_options
|
168
|
+
# %x{cd /tmp && rails ebb_test && ruby #{Ebb::LIBDIR}/../bin/ebb_rails start -d -e development -c /tmp/ebb_test -u www -g www -P /tmp/ebb.1.pid -p 4043 &}
|
169
|
+
# response = get('/')
|
170
|
+
# assert_equal 200, response.code
|
171
|
+
# ensure
|
172
|
+
# Process.kill('KILL', %x{cat /tmp/ebb.1.pid})
|
173
|
+
# end
|
123
174
|
end
|
124
175
|
|
125
176
|
|
177
|
+
|
178
|
+
|
126
179
|
#
|
127
180
|
# class SocketTest < Test::Unit::TestCase
|
128
181
|
# def test_socket_creation
|
data/test/echo_server.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'json'
|
3
|
+
require File.dirname(__FILE__) + '/../ruby_lib/ebb'
|
4
|
+
|
5
|
+
|
6
|
+
class EchoApp
|
7
|
+
def call(env)
|
8
|
+
env['rack.input'] = env['rack.input'].read(1000000)
|
9
|
+
env.delete('rack.errors')
|
10
|
+
[200, {'Content-Type' => 'text/json'}, env.to_json]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
server = Ebb::Server.new(EchoApp.new, :port => 4037)
|
16
|
+
server.start
|
data/test/env_test.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'json'
|
4
|
+
require 'test/unit'
|
5
|
+
|
6
|
+
PORT = 4037
|
7
|
+
|
8
|
+
# This test depends on echo_server running at port 4037. I do this so that
|
9
|
+
# I can run a Python server at that port with a similar application and reuse
|
10
|
+
# these tests.
|
11
|
+
|
12
|
+
def send_request(request_string)
|
13
|
+
socket = TCPSocket.new("0.0.0.0", PORT)
|
14
|
+
socket.write(request_string)
|
15
|
+
lines = []
|
16
|
+
out = socket.read(5000000)
|
17
|
+
raise "Connection Closed on #{request_string.inspect}" if out.nil?
|
18
|
+
out.each_line { |l| lines << l }
|
19
|
+
env = JSON.parse(lines.last)
|
20
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EPIPE
|
21
|
+
return :fail
|
22
|
+
rescue RuntimeError => e
|
23
|
+
if e.message =~ /Connection Closed/
|
24
|
+
return :fail
|
25
|
+
else
|
26
|
+
raise e
|
27
|
+
end
|
28
|
+
rescue => e
|
29
|
+
puts "unknown exception: #{e.class}"
|
30
|
+
raise e
|
31
|
+
ensure
|
32
|
+
socket.close unless socket.nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
def drops_request?(request_string)
|
36
|
+
:fail == send_request(request_string)
|
37
|
+
end
|
38
|
+
|
39
|
+
class HttpParserTest < Test::Unit::TestCase
|
40
|
+
|
41
|
+
def test_parse_simple
|
42
|
+
env = send_request("GET / HTTP/1.1\r\n\r\n")
|
43
|
+
|
44
|
+
assert_equal 'HTTP/1.1', env['SERVER_PROTOCOL']
|
45
|
+
assert_equal '/', env['REQUEST_PATH']
|
46
|
+
assert_equal 'HTTP/1.1', env['HTTP_VERSION']
|
47
|
+
assert_equal '/', env['REQUEST_URI']
|
48
|
+
assert_equal 'CGI/1.2', env['GATEWAY_INTERFACE']
|
49
|
+
assert_equal 'GET', env['REQUEST_METHOD']
|
50
|
+
assert_nil env['FRAGMENT']
|
51
|
+
assert_nil env['QUERY_STRING']
|
52
|
+
assert_nil env['rack.input']
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_parse_dumbfuck_headers
|
56
|
+
should_be_good = "GET / HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n"
|
57
|
+
env = send_request(should_be_good)
|
58
|
+
assert_equal "++++++++++", env["HTTP_AAAAAAAAAAAAA"]
|
59
|
+
assert_nil env['rack.input']
|
60
|
+
|
61
|
+
nasty_pound_header = "GET / HTTP/1.1\r\nX-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgEBBAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n\tRA==\r\n\t-----END CERTIFICATE-----\r\n\r\n"
|
62
|
+
assert drops_request?(nasty_pound_header) # Correct?
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_parse_error
|
66
|
+
assert drops_request?("GET / SsUTF/1.1")
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_fragment_in_uri
|
70
|
+
env = send_request("GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n")
|
71
|
+
assert_equal '/forums/1/topics/2375?page=1', env['REQUEST_URI']
|
72
|
+
assert_equal 'posts-17408', env['FRAGMENT']
|
73
|
+
assert_nil env['rack.input']
|
74
|
+
end
|
75
|
+
|
76
|
+
# lame random garbage maker
|
77
|
+
def rand_data(min, max, readable=true)
|
78
|
+
count = min + ((rand(max)+1) *10).to_i
|
79
|
+
res = count.to_s + "/"
|
80
|
+
|
81
|
+
if readable
|
82
|
+
res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40)
|
83
|
+
else
|
84
|
+
res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20)
|
85
|
+
end
|
86
|
+
|
87
|
+
return res
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_horrible_queries
|
91
|
+
10.times do |c|
|
92
|
+
req = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
|
93
|
+
assert drops_request?(req), "large header names are caught"
|
94
|
+
end
|
95
|
+
|
96
|
+
# then that large mangled field values are caught
|
97
|
+
10.times do |c|
|
98
|
+
req = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
|
99
|
+
assert drops_request?(req), "large mangled field values are caught"
|
100
|
+
### XXX this is broken! fix me. this test should drop the request.
|
101
|
+
end
|
102
|
+
|
103
|
+
# then large headers are rejected too
|
104
|
+
req = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
|
105
|
+
req << "X-Test: test\r\n" * (80 * 1024)
|
106
|
+
assert drops_request?(req), "large headers are rejected"
|
107
|
+
|
108
|
+
# finally just that random garbage gets blocked all the time
|
109
|
+
10.times do |c|
|
110
|
+
req = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
|
111
|
+
assert drops_request?(req), "random garbage gets blocked all the time"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ebb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ry dahl
|
@@ -9,12 +9,12 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-
|
12
|
+
date: 2008-03-03 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
16
16
|
description: ""
|
17
|
-
email: ry
|
17
|
+
email: ry at tiny clouds dot org
|
18
18
|
executables:
|
19
19
|
- ebb_rails
|
20
20
|
extensions:
|
@@ -22,13 +22,10 @@ extensions:
|
|
22
22
|
extra_rdoc_files: []
|
23
23
|
|
24
24
|
files:
|
25
|
-
- src/parser.rl
|
26
25
|
- src/ebb.c
|
27
|
-
- src/ebb_ruby.c
|
28
|
-
- src/parser.c
|
29
26
|
- src/ebb.h
|
27
|
+
- src/parser.c
|
30
28
|
- src/parser.h
|
31
|
-
- src/extconf.rb
|
32
29
|
- libev/ev.c
|
33
30
|
- libev/ev.h
|
34
31
|
- libev/ev_epoll.c
|
@@ -39,6 +36,10 @@ files:
|
|
39
36
|
- libev/ev_vars.h
|
40
37
|
- libev/ev_win32.c
|
41
38
|
- libev/ev_wrap.h
|
39
|
+
- VERSION
|
40
|
+
- README
|
41
|
+
- src/ebb_ruby.c
|
42
|
+
- src/extconf.rb
|
42
43
|
- ruby_lib/daemonizable.rb
|
43
44
|
- ruby_lib/ebb.rb
|
44
45
|
- ruby_lib/rack
|
@@ -47,12 +48,12 @@ files:
|
|
47
48
|
- benchmark/application.rb
|
48
49
|
- benchmark/bench_results.rb
|
49
50
|
- benchmark/server_test.rb
|
50
|
-
- benchmark/test.rb
|
51
51
|
- bin/ebb_rails
|
52
|
-
-
|
53
|
-
-
|
52
|
+
- test/basic_test.rb
|
53
|
+
- test/echo_server.rb
|
54
|
+
- test/env_test.rb
|
54
55
|
has_rdoc: false
|
55
|
-
homepage: http://
|
56
|
+
homepage: http://ebb.rubyforge.org
|
56
57
|
post_install_message:
|
57
58
|
rdoc_options: []
|
58
59
|
|
data/src/parser.rl
DELETED
@@ -1,200 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Copyright (c) 2005 Zed A. Shaw
|
3
|
-
* You can redistribute it and/or modify it under the same terms as Ruby.
|
4
|
-
*/
|
5
|
-
#include "parser.h"
|
6
|
-
#include <stdio.h>
|
7
|
-
#include <assert.h>
|
8
|
-
#include <stdlib.h>
|
9
|
-
#include <ctype.h>
|
10
|
-
#include <string.h>
|
11
|
-
|
12
|
-
#define LEN(AT, FPC) (FPC - buffer - parser->AT)
|
13
|
-
#define MARK(M,FPC) (parser->M = (FPC) - buffer)
|
14
|
-
#define PTR_TO(F) (buffer + parser->F)
|
15
|
-
|
16
|
-
/** machine **/
|
17
|
-
%%{
|
18
|
-
machine http_parser;
|
19
|
-
|
20
|
-
action mark {MARK(mark, fpc); }
|
21
|
-
|
22
|
-
|
23
|
-
action start_field { MARK(field_start, fpc); }
|
24
|
-
action write_field {
|
25
|
-
parser->field_len = LEN(field_start, fpc);
|
26
|
-
}
|
27
|
-
|
28
|
-
action start_value { MARK(mark, fpc); }
|
29
|
-
action write_value {
|
30
|
-
if(parser->http_field != NULL) {
|
31
|
-
parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, fpc));
|
32
|
-
}
|
33
|
-
}
|
34
|
-
|
35
|
-
action content_length {
|
36
|
-
if(parser->content_length != NULL) {
|
37
|
-
parser->content_length(parser->data, PTR_TO(mark), LEN(mark, fpc));
|
38
|
-
}
|
39
|
-
}
|
40
|
-
|
41
|
-
action request_method {
|
42
|
-
if(parser->request_method != NULL)
|
43
|
-
parser->request_method(parser->data, PTR_TO(mark), LEN(mark, fpc));
|
44
|
-
}
|
45
|
-
action request_uri {
|
46
|
-
if(parser->request_uri != NULL)
|
47
|
-
parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, fpc));
|
48
|
-
}
|
49
|
-
|
50
|
-
action start_query {MARK(query_start, fpc); }
|
51
|
-
action query_string {
|
52
|
-
if(parser->query_string != NULL)
|
53
|
-
parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, fpc));
|
54
|
-
}
|
55
|
-
|
56
|
-
action http_version {
|
57
|
-
if(parser->http_version != NULL)
|
58
|
-
parser->http_version(parser->data, PTR_TO(mark), LEN(mark, fpc));
|
59
|
-
}
|
60
|
-
|
61
|
-
action request_path {
|
62
|
-
if(parser->request_path != NULL)
|
63
|
-
parser->request_path(parser->data, PTR_TO(mark), LEN(mark,fpc));
|
64
|
-
}
|
65
|
-
|
66
|
-
action done {
|
67
|
-
parser->body_start = fpc - buffer + 1;
|
68
|
-
if(parser->header_done != NULL)
|
69
|
-
parser->header_done(parser->data, fpc + 1, pe - fpc - 1);
|
70
|
-
fbreak;
|
71
|
-
}
|
72
|
-
|
73
|
-
|
74
|
-
#### HTTP PROTOCOL GRAMMAR
|
75
|
-
# line endings
|
76
|
-
CRLF = "\r\n";
|
77
|
-
|
78
|
-
# character types
|
79
|
-
CTL = (cntrl | 127);
|
80
|
-
safe = ("$" | "-" | "_" | ".");
|
81
|
-
extra = ("!" | "*" | "'" | "(" | ")" | ",");
|
82
|
-
reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+");
|
83
|
-
unsafe = (CTL | " " | "\"" | "#" | "%" | "<" | ">");
|
84
|
-
national = any -- (alpha | digit | reserved | extra | safe | unsafe);
|
85
|
-
unreserved = (alpha | digit | safe | extra | national);
|
86
|
-
escape = ("%" xdigit xdigit);
|
87
|
-
uchar = (unreserved | escape);
|
88
|
-
pchar = (uchar | ":" | "@" | "&" | "=" | "+");
|
89
|
-
tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");
|
90
|
-
|
91
|
-
# elements
|
92
|
-
token = (ascii -- (CTL | tspecials));
|
93
|
-
|
94
|
-
# URI schemes and absolute paths
|
95
|
-
scheme = ( alpha | digit | "+" | "-" | "." )* ;
|
96
|
-
absolute_uri = (scheme ":" (uchar | reserved )*);
|
97
|
-
|
98
|
-
path = (pchar+ ( "/" pchar* )*) ;
|
99
|
-
query = ( uchar | reserved )* %query_string ;
|
100
|
-
param = ( pchar | "/" )* ;
|
101
|
-
params = (param ( ";" param )*) ;
|
102
|
-
rel_path = (path? %request_path (";" params)?) ("?" %start_query query)?;
|
103
|
-
absolute_path = ("/"+ rel_path);
|
104
|
-
|
105
|
-
Request_URI = ("*" | absolute_uri | absolute_path) >mark %request_uri;
|
106
|
-
Method = (upper | digit | safe){1,20} >mark %request_method;
|
107
|
-
|
108
|
-
http_number = (digit+ "." digit+) ;
|
109
|
-
HTTP_Version = ("HTTP/" http_number) >mark %http_version ;
|
110
|
-
Request_Line = (Method " " Request_URI " " HTTP_Version CRLF) ;
|
111
|
-
|
112
|
-
field_name = (token -- ":")+ >start_field %write_field;
|
113
|
-
|
114
|
-
field_value = any* >start_value %write_value;
|
115
|
-
|
116
|
-
message_header = field_name ":" " "* field_value :> CRLF;
|
117
|
-
content_length = "Content-Length:"i " "* (field_value >mark %content_length) :> CRLF;
|
118
|
-
|
119
|
-
Request = Request_Line (content_length | message_header)* ( CRLF @done);
|
120
|
-
|
121
|
-
main := Request;
|
122
|
-
}%%
|
123
|
-
|
124
|
-
/** Data **/
|
125
|
-
%% write data;
|
126
|
-
|
127
|
-
int http_parser_init(http_parser *parser) {
|
128
|
-
int cs = 0;
|
129
|
-
%% write init;
|
130
|
-
parser->cs = cs;
|
131
|
-
parser->body_start = 0;
|
132
|
-
parser->content_len = 0;
|
133
|
-
parser->mark = 0;
|
134
|
-
parser->nread = 0;
|
135
|
-
parser->field_len = 0;
|
136
|
-
parser->field_start = 0;
|
137
|
-
|
138
|
-
return(1);
|
139
|
-
}
|
140
|
-
|
141
|
-
|
142
|
-
/** exec **/
|
143
|
-
size_t http_parser_execute(http_parser *parser, const char *buffer, size_t len, size_t off) {
|
144
|
-
const char *p, *pe;
|
145
|
-
int cs = parser->cs;
|
146
|
-
|
147
|
-
assert(off <= len && "offset past end of buffer");
|
148
|
-
|
149
|
-
p = buffer+off;
|
150
|
-
pe = buffer+len;
|
151
|
-
|
152
|
-
assert(*pe == '\0' && "pointer does not end on NUL");
|
153
|
-
assert(pe - p == len - off && "pointers aren't same distance");
|
154
|
-
|
155
|
-
|
156
|
-
%% write exec;
|
157
|
-
|
158
|
-
parser->cs = cs;
|
159
|
-
parser->nread += p - (buffer + off);
|
160
|
-
|
161
|
-
assert(p <= pe && "buffer overflow after parsing execute");
|
162
|
-
assert(parser->nread <= len && "nread longer than length");
|
163
|
-
assert(parser->body_start <= len && "body starts after buffer end");
|
164
|
-
assert(parser->mark < len && "mark is after buffer end");
|
165
|
-
assert(parser->field_len <= len && "field has length longer than whole buffer");
|
166
|
-
assert(parser->field_start < len && "field starts after buffer end");
|
167
|
-
|
168
|
-
if(parser->body_start) {
|
169
|
-
/* final \r\n combo encountered so stop right here */
|
170
|
-
%%write eof;
|
171
|
-
parser->nread++;
|
172
|
-
}
|
173
|
-
|
174
|
-
return(parser->nread);
|
175
|
-
}
|
176
|
-
|
177
|
-
int http_parser_finish(http_parser *parser)
|
178
|
-
{
|
179
|
-
int cs = parser->cs;
|
180
|
-
|
181
|
-
%%write eof;
|
182
|
-
|
183
|
-
parser->cs = cs;
|
184
|
-
|
185
|
-
if (http_parser_has_error(parser) ) {
|
186
|
-
return -1;
|
187
|
-
} else if (http_parser_is_finished(parser) ) {
|
188
|
-
return 1;
|
189
|
-
} else {
|
190
|
-
return 0;
|
191
|
-
}
|
192
|
-
}
|
193
|
-
|
194
|
-
int http_parser_has_error(http_parser *parser) {
|
195
|
-
return parser->cs == http_parser_error;
|
196
|
-
}
|
197
|
-
|
198
|
-
int http_parser_is_finished(http_parser *parser) {
|
199
|
-
return parser->cs == http_parser_first_final;
|
200
|
-
}
|