giraffesoft-unicorn 0.93.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +16 -0
  3. data/.gitignore +20 -0
  4. data/.mailmap +26 -0
  5. data/CONTRIBUTORS +31 -0
  6. data/COPYING +339 -0
  7. data/DESIGN +105 -0
  8. data/Documentation/.gitignore +5 -0
  9. data/Documentation/GNUmakefile +30 -0
  10. data/Documentation/unicorn.1.txt +167 -0
  11. data/Documentation/unicorn_rails.1.txt +169 -0
  12. data/GIT-VERSION-GEN +40 -0
  13. data/GNUmakefile +270 -0
  14. data/HACKING +113 -0
  15. data/KNOWN_ISSUES +40 -0
  16. data/LICENSE +55 -0
  17. data/PHILOSOPHY +144 -0
  18. data/README +153 -0
  19. data/Rakefile +108 -0
  20. data/SIGNALS +97 -0
  21. data/TODO +16 -0
  22. data/TUNING +70 -0
  23. data/bin/unicorn +165 -0
  24. data/bin/unicorn_rails +208 -0
  25. data/examples/echo.ru +27 -0
  26. data/examples/git.ru +13 -0
  27. data/examples/init.sh +53 -0
  28. data/ext/unicorn_http/c_util.h +107 -0
  29. data/ext/unicorn_http/common_field_optimization.h +111 -0
  30. data/ext/unicorn_http/ext_help.h +73 -0
  31. data/ext/unicorn_http/extconf.rb +14 -0
  32. data/ext/unicorn_http/global_variables.h +91 -0
  33. data/ext/unicorn_http/unicorn_http.rl +715 -0
  34. data/ext/unicorn_http/unicorn_http_common.rl +74 -0
  35. data/lib/unicorn.rb +730 -0
  36. data/lib/unicorn/app/exec_cgi.rb +150 -0
  37. data/lib/unicorn/app/inetd.rb +109 -0
  38. data/lib/unicorn/app/old_rails.rb +31 -0
  39. data/lib/unicorn/app/old_rails/static.rb +60 -0
  40. data/lib/unicorn/cgi_wrapper.rb +145 -0
  41. data/lib/unicorn/configurator.rb +403 -0
  42. data/lib/unicorn/const.rb +37 -0
  43. data/lib/unicorn/http_request.rb +74 -0
  44. data/lib/unicorn/http_response.rb +74 -0
  45. data/lib/unicorn/launcher.rb +39 -0
  46. data/lib/unicorn/socket_helper.rb +138 -0
  47. data/lib/unicorn/tee_input.rb +174 -0
  48. data/lib/unicorn/util.rb +64 -0
  49. data/local.mk.sample +53 -0
  50. data/setup.rb +1586 -0
  51. data/test/aggregate.rb +15 -0
  52. data/test/benchmark/README +50 -0
  53. data/test/benchmark/dd.ru +18 -0
  54. data/test/exec/README +5 -0
  55. data/test/exec/test_exec.rb +855 -0
  56. data/test/rails/app-1.2.3/.gitignore +2 -0
  57. data/test/rails/app-1.2.3/Rakefile +7 -0
  58. data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
  59. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
  60. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
  61. data/test/rails/app-1.2.3/config/boot.rb +11 -0
  62. data/test/rails/app-1.2.3/config/database.yml +12 -0
  63. data/test/rails/app-1.2.3/config/environment.rb +13 -0
  64. data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
  65. data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
  66. data/test/rails/app-1.2.3/config/routes.rb +6 -0
  67. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  68. data/test/rails/app-1.2.3/public/404.html +1 -0
  69. data/test/rails/app-1.2.3/public/500.html +1 -0
  70. data/test/rails/app-2.0.2/.gitignore +2 -0
  71. data/test/rails/app-2.0.2/Rakefile +7 -0
  72. data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
  73. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
  74. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
  75. data/test/rails/app-2.0.2/config/boot.rb +11 -0
  76. data/test/rails/app-2.0.2/config/database.yml +12 -0
  77. data/test/rails/app-2.0.2/config/environment.rb +17 -0
  78. data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
  79. data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
  80. data/test/rails/app-2.0.2/config/routes.rb +6 -0
  81. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  82. data/test/rails/app-2.0.2/public/404.html +1 -0
  83. data/test/rails/app-2.0.2/public/500.html +1 -0
  84. data/test/rails/app-2.1.2/.gitignore +2 -0
  85. data/test/rails/app-2.1.2/Rakefile +7 -0
  86. data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
  87. data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
  88. data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
  89. data/test/rails/app-2.1.2/config/boot.rb +111 -0
  90. data/test/rails/app-2.1.2/config/database.yml +12 -0
  91. data/test/rails/app-2.1.2/config/environment.rb +17 -0
  92. data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
  93. data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
  94. data/test/rails/app-2.1.2/config/routes.rb +6 -0
  95. data/test/rails/app-2.1.2/db/.gitignore +0 -0
  96. data/test/rails/app-2.1.2/public/404.html +1 -0
  97. data/test/rails/app-2.1.2/public/500.html +1 -0
  98. data/test/rails/app-2.2.2/.gitignore +2 -0
  99. data/test/rails/app-2.2.2/Rakefile +7 -0
  100. data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
  101. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
  102. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
  103. data/test/rails/app-2.2.2/config/boot.rb +111 -0
  104. data/test/rails/app-2.2.2/config/database.yml +12 -0
  105. data/test/rails/app-2.2.2/config/environment.rb +17 -0
  106. data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
  107. data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
  108. data/test/rails/app-2.2.2/config/routes.rb +6 -0
  109. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  110. data/test/rails/app-2.2.2/public/404.html +1 -0
  111. data/test/rails/app-2.2.2/public/500.html +1 -0
  112. data/test/rails/app-2.3.3.1/.gitignore +2 -0
  113. data/test/rails/app-2.3.3.1/Rakefile +7 -0
  114. data/test/rails/app-2.3.3.1/app/controllers/application_controller.rb +5 -0
  115. data/test/rails/app-2.3.3.1/app/controllers/foo_controller.rb +36 -0
  116. data/test/rails/app-2.3.3.1/app/helpers/application_helper.rb +4 -0
  117. data/test/rails/app-2.3.3.1/config/boot.rb +109 -0
  118. data/test/rails/app-2.3.3.1/config/database.yml +12 -0
  119. data/test/rails/app-2.3.3.1/config/environment.rb +17 -0
  120. data/test/rails/app-2.3.3.1/config/environments/development.rb +7 -0
  121. data/test/rails/app-2.3.3.1/config/environments/production.rb +6 -0
  122. data/test/rails/app-2.3.3.1/config/routes.rb +6 -0
  123. data/test/rails/app-2.3.3.1/db/.gitignore +0 -0
  124. data/test/rails/app-2.3.3.1/public/404.html +1 -0
  125. data/test/rails/app-2.3.3.1/public/500.html +1 -0
  126. data/test/rails/app-2.3.3.1/public/x.txt +1 -0
  127. data/test/rails/test_rails.rb +280 -0
  128. data/test/test_helper.rb +296 -0
  129. data/test/unit/test_configurator.rb +150 -0
  130. data/test/unit/test_http_parser.rb +492 -0
  131. data/test/unit/test_http_parser_ng.rb +308 -0
  132. data/test/unit/test_request.rb +184 -0
  133. data/test/unit/test_response.rb +110 -0
  134. data/test/unit/test_server.rb +188 -0
  135. data/test/unit/test_signals.rb +202 -0
  136. data/test/unit/test_socket_helper.rb +133 -0
  137. data/test/unit/test_tee_input.rb +229 -0
  138. data/test/unit/test_upload.rb +297 -0
  139. data/test/unit/test_util.rb +96 -0
  140. data/unicorn.gemspec +42 -0
  141. metadata +228 -0
@@ -0,0 +1,715 @@
1
+ /**
2
+ * Copyright (c) 2009 Eric Wong (all bugs are Eric's fault)
3
+ * Copyright (c) 2005 Zed A. Shaw
4
+ * You can redistribute it and/or modify it under the same terms as Ruby.
5
+ */
6
+ #include "ruby.h"
7
+ #include "ext_help.h"
8
+ #include <assert.h>
9
+ #include <string.h>
10
+ #include <sys/types.h>
11
+ #include "common_field_optimization.h"
12
+ #include "global_variables.h"
13
+ #include "c_util.h"
14
+
15
+ #define UH_FL_CHUNKED 0x1
16
+ #define UH_FL_HASBODY 0x2
17
+ #define UH_FL_INBODY 0x4
18
+ #define UH_FL_HASTRAILER 0x8
19
+ #define UH_FL_INTRAILER 0x10
20
+ #define UH_FL_INCHUNK 0x20
21
+ #define UH_FL_KAMETHOD 0x40
22
+ #define UH_FL_KAVERSION 0x80
23
+ #define UH_FL_HASHEADER 0x100
24
+
25
+ /* both of these flags need to be set for keepalive to be supported */
26
+ #define UH_FL_KEEPALIVE (UH_FL_KAMETHOD | UH_FL_KAVERSION)
27
+
28
+ struct http_parser {
29
+ int cs; /* Ragel internal state */
30
+ unsigned int flags;
31
+ size_t mark;
32
+ union { /* these 3 fields don't nest */
33
+ size_t field;
34
+ size_t query;
35
+ size_t offset;
36
+ } start;
37
+ union {
38
+ size_t field_len; /* only used during header processing */
39
+ size_t dest_offset; /* only used during body processing */
40
+ } s;
41
+ VALUE cont; /* Qfalse: unset, Qnil: ignored header, T_STRING: append */
42
+ union {
43
+ off_t content;
44
+ off_t chunk;
45
+ } len;
46
+ };
47
+
48
+ static void finalize_header(struct http_parser *hp, VALUE req);
49
+
50
+ #define REMAINING (unsigned long)(pe - p)
51
+ #define LEN(AT, FPC) (FPC - buffer - hp->AT)
52
+ #define MARK(M,FPC) (hp->M = (FPC) - buffer)
53
+ #define PTR_TO(F) (buffer + hp->F)
54
+ #define STR_NEW(M,FPC) rb_str_new(PTR_TO(M), LEN(M, FPC))
55
+
56
+ #define HP_FL_TEST(hp,fl) ((hp)->flags & (UH_FL_##fl))
57
+ #define HP_FL_SET(hp,fl) ((hp)->flags |= (UH_FL_##fl))
58
+ #define HP_FL_UNSET(hp,fl) ((hp)->flags &= ~(UH_FL_##fl))
59
+ #define HP_FL_ALL(hp,fl) (HP_FL_TEST(hp, fl) == (UH_FL_##fl))
60
+
61
+ /*
62
+ * handles values of the "Connection:" header, keepalive is implied
63
+ * for HTTP/1.1 but needs to be explicitly enabled with HTTP/1.0
64
+ * Additionally, we require GET/HEAD requests to support keepalive.
65
+ */
66
+ static void hp_keepalive_connection(struct http_parser *hp, VALUE val)
67
+ {
68
+ /* REQUEST_METHOD is always set before any headers */
69
+ if (HP_FL_TEST(hp, KAMETHOD)) {
70
+ if (STR_CSTR_CASE_EQ(val, "keep-alive")) {
71
+ /* basically have HTTP/1.0 masquerade as HTTP/1.1+ */
72
+ HP_FL_SET(hp, KAVERSION);
73
+ } else if (STR_CSTR_CASE_EQ(val, "close")) {
74
+ /*
75
+ * it doesn't matter what HTTP version or request method we have,
76
+ * if a client says "Connection: close", we disable keepalive
77
+ */
78
+ HP_FL_UNSET(hp, KEEPALIVE);
79
+ } else {
80
+ /*
81
+ * client could've sent anything, ignore it for now. Maybe
82
+ * "HP_FL_UNSET(hp, KEEPALIVE);" just in case?
83
+ * Raising an exception might be too mean...
84
+ */
85
+ }
86
+ }
87
+ }
88
+
89
+ static void
90
+ request_method(struct http_parser *hp, VALUE req, const char *ptr, size_t len)
91
+ {
92
+ VALUE v;
93
+
94
+ /*
95
+ * we only support keepalive for GET and HEAD requests for now other
96
+ * methods are too rarely seen to be worth optimizing. POST is unsafe
97
+ * since some clients send extra bytes after POST bodies.
98
+ */
99
+ if (CONST_MEM_EQ("GET", ptr, len)) {
100
+ HP_FL_SET(hp, KAMETHOD);
101
+ v = g_GET;
102
+ } else if (CONST_MEM_EQ("HEAD", ptr, len)) {
103
+ HP_FL_SET(hp, KAMETHOD);
104
+ v = g_HEAD;
105
+ } else {
106
+ v = rb_str_new(ptr, len);
107
+ }
108
+ rb_hash_aset(req, g_request_method, v);
109
+ }
110
+
111
+ static void
112
+ http_version(struct http_parser *hp, VALUE req, const char *ptr, size_t len)
113
+ {
114
+ VALUE v;
115
+
116
+ HP_FL_SET(hp, HASHEADER);
117
+
118
+ if (CONST_MEM_EQ("HTTP/1.1", ptr, len)) {
119
+ /* HTTP/1.1 implies keepalive unless "Connection: close" is set */
120
+ HP_FL_SET(hp, KAVERSION);
121
+ v = g_http_11;
122
+ } else if (CONST_MEM_EQ("HTTP/1.0", ptr, len)) {
123
+ v = g_http_10;
124
+ } else {
125
+ v = rb_str_new(ptr, len);
126
+ }
127
+ rb_hash_aset(req, g_server_protocol, v);
128
+ rb_hash_aset(req, g_http_version, v);
129
+ }
130
+
131
+ static inline void hp_invalid_if_trailer(struct http_parser *hp)
132
+ {
133
+ if (HP_FL_TEST(hp, INTRAILER))
134
+ rb_raise(eHttpParserError, "invalid Trailer");
135
+ }
136
+
137
+ static void write_cont_value(struct http_parser *hp,
138
+ const char *buffer, const char *p)
139
+ {
140
+ char *vptr;
141
+
142
+ if (hp->cont == Qfalse)
143
+ rb_raise(eHttpParserError, "invalid continuation line");
144
+ if (NIL_P(hp->cont))
145
+ return; /* we're ignoring this header (probably Host:) */
146
+
147
+ assert(TYPE(hp->cont) == T_STRING && "continuation line is not a string");
148
+ assert(hp->mark > 0 && "impossible continuation line offset");
149
+
150
+ if (LEN(mark, p) == 0)
151
+ return;
152
+
153
+ if (RSTRING_LEN(hp->cont) > 0)
154
+ --hp->mark;
155
+
156
+ vptr = (char *)PTR_TO(mark);
157
+
158
+ if (RSTRING_LEN(hp->cont) > 0) {
159
+ assert((' ' == *vptr || '\t' == *vptr) && "invalid leading white space");
160
+ *vptr = ' ';
161
+ }
162
+ rb_str_buf_cat(hp->cont, vptr, LEN(mark, p));
163
+ }
164
+
165
+ static void write_value(VALUE req, struct http_parser *hp,
166
+ const char *buffer, const char *p)
167
+ {
168
+ VALUE f = find_common_field(PTR_TO(start.field), hp->s.field_len);
169
+ VALUE v;
170
+ VALUE e;
171
+
172
+ VALIDATE_MAX_LENGTH(LEN(mark, p), FIELD_VALUE);
173
+ v = LEN(mark, p) == 0 ? rb_str_buf_new(128) : STR_NEW(mark, p);
174
+ if (NIL_P(f)) {
175
+ VALIDATE_MAX_LENGTH(hp->s.field_len, FIELD_NAME);
176
+ f = uncommon_field(PTR_TO(start.field), hp->s.field_len);
177
+ } else if (f == g_http_connection) {
178
+ hp_keepalive_connection(hp, v);
179
+ } else if (f == g_content_length) {
180
+ hp->len.content = parse_length(RSTRING_PTR(v), RSTRING_LEN(v));
181
+ if (hp->len.content < 0)
182
+ rb_raise(eHttpParserError, "invalid Content-Length");
183
+ HP_FL_SET(hp, HASBODY);
184
+ hp_invalid_if_trailer(hp);
185
+ } else if (f == g_http_transfer_encoding) {
186
+ if (STR_CSTR_CASE_EQ(v, "chunked")) {
187
+ HP_FL_SET(hp, CHUNKED);
188
+ HP_FL_SET(hp, HASBODY);
189
+ }
190
+ hp_invalid_if_trailer(hp);
191
+ } else if (f == g_http_trailer) {
192
+ HP_FL_SET(hp, HASTRAILER);
193
+ hp_invalid_if_trailer(hp);
194
+ } else {
195
+ assert(TYPE(f) == T_STRING && "memoized object is not a string");
196
+ assert_frozen(f);
197
+ }
198
+
199
+ e = rb_hash_aref(req, f);
200
+ if (NIL_P(e)) {
201
+ hp->cont = rb_hash_aset(req, f, v);
202
+ } else if (f == g_http_host) {
203
+ /*
204
+ * ignored, absolute URLs in REQUEST_URI take precedence over
205
+ * the Host: header (ref: rfc 2616, section 5.2.1)
206
+ */
207
+ hp->cont = Qnil;
208
+ } else {
209
+ rb_str_buf_cat(e, ",", 1);
210
+ hp->cont = rb_str_buf_append(e, v);
211
+ }
212
+ }
213
+
214
+ /** Machine **/
215
+
216
+ %%{
217
+ machine http_parser;
218
+
219
+ action mark {MARK(mark, fpc); }
220
+
221
+ action start_field { MARK(start.field, fpc); }
222
+ action snake_upcase_field { snake_upcase_char((char *)fpc); }
223
+ action downcase_char { downcase_char((char *)fpc); }
224
+ action write_field { hp->s.field_len = LEN(start.field, fpc); }
225
+ action start_value { MARK(mark, fpc); }
226
+ action write_value { write_value(req, hp, buffer, fpc); }
227
+ action write_cont_value { write_cont_value(hp, buffer, fpc); }
228
+ action request_method {
229
+ request_method(hp, req, PTR_TO(mark), LEN(mark, fpc));
230
+ }
231
+ action scheme {
232
+ rb_hash_aset(req, g_rack_url_scheme, STR_NEW(mark, fpc));
233
+ }
234
+ action host {
235
+ rb_hash_aset(req, g_http_host, STR_NEW(mark, fpc));
236
+ }
237
+ action request_uri {
238
+ size_t len = LEN(mark, fpc);
239
+ VALUE str;
240
+
241
+ VALIDATE_MAX_LENGTH(len, REQUEST_URI);
242
+ str = rb_hash_aset(req, g_request_uri, STR_NEW(mark, fpc));
243
+ /*
244
+ * "OPTIONS * HTTP/1.1\r\n" is a valid request, but we can't have '*'
245
+ * in REQUEST_PATH or PATH_INFO or else Rack::Lint will complain
246
+ */
247
+ if (STR_CSTR_EQ(str, "*")) {
248
+ str = rb_str_new(NULL, 0);
249
+ rb_hash_aset(req, g_path_info, str);
250
+ rb_hash_aset(req, g_request_path, str);
251
+ }
252
+ }
253
+ action fragment {
254
+ VALIDATE_MAX_LENGTH(LEN(mark, fpc), FRAGMENT);
255
+ rb_hash_aset(req, g_fragment, STR_NEW(mark, fpc));
256
+ }
257
+ action start_query {MARK(start.query, fpc); }
258
+ action query_string {
259
+ VALIDATE_MAX_LENGTH(LEN(start.query, fpc), QUERY_STRING);
260
+ rb_hash_aset(req, g_query_string, STR_NEW(start.query, fpc));
261
+ }
262
+ action http_version { http_version(hp, req, PTR_TO(mark), LEN(mark, fpc)); }
263
+ action request_path {
264
+ VALUE val;
265
+ size_t len = LEN(mark, fpc);
266
+
267
+ VALIDATE_MAX_LENGTH(len, REQUEST_PATH);
268
+ val = rb_hash_aset(req, g_request_path, STR_NEW(mark, fpc));
269
+
270
+ /* rack says PATH_INFO must start with "/" or be empty */
271
+ if (!STR_CSTR_EQ(val, "*"))
272
+ rb_hash_aset(req, g_path_info, val);
273
+ }
274
+ action add_to_chunk_size {
275
+ hp->len.chunk = step_incr(hp->len.chunk, fc, 16);
276
+ if (hp->len.chunk < 0)
277
+ rb_raise(eHttpParserError, "invalid chunk size");
278
+ }
279
+ action header_done {
280
+ finalize_header(hp, req);
281
+
282
+ cs = http_parser_first_final;
283
+ if (HP_FL_TEST(hp, HASBODY)) {
284
+ HP_FL_SET(hp, INBODY);
285
+ if (HP_FL_TEST(hp, CHUNKED))
286
+ cs = http_parser_en_ChunkedBody;
287
+ } else {
288
+ assert(!HP_FL_TEST(hp, CHUNKED) && "chunked encoding without body!");
289
+ }
290
+ /*
291
+ * go back to Ruby so we can call the Rack application, we'll reenter
292
+ * the parser iff the body needs to be processed.
293
+ */
294
+ goto post_exec;
295
+ }
296
+
297
+ action end_trailers {
298
+ cs = http_parser_first_final;
299
+ goto post_exec;
300
+ }
301
+
302
+ action end_chunked_body {
303
+ if (HP_FL_TEST(hp, HASTRAILER)) {
304
+ HP_FL_SET(hp, INTRAILER);
305
+ cs = http_parser_en_Trailers;
306
+ } else {
307
+ cs = http_parser_first_final;
308
+ }
309
+ ++p;
310
+ assert(p <= pe && "buffer overflow after chunked body");
311
+ goto post_exec;
312
+ }
313
+
314
+ action skip_chunk_data {
315
+ skip_chunk_data_hack: {
316
+ size_t nr = MIN(hp->len.chunk, REMAINING);
317
+ memcpy(RSTRING_PTR(req) + hp->s.dest_offset, fpc, nr);
318
+ hp->s.dest_offset += nr;
319
+ hp->len.chunk -= nr;
320
+ p += nr;
321
+ assert(hp->len.chunk >= 0 && "negative chunk length");
322
+ if (hp->len.chunk > REMAINING) {
323
+ HP_FL_SET(hp, INCHUNK);
324
+ goto post_exec;
325
+ } else {
326
+ fhold;
327
+ fgoto chunk_end;
328
+ }
329
+ }}
330
+
331
+ include unicorn_http_common "unicorn_http_common.rl";
332
+ }%%
333
+
334
+ /** Data **/
335
+ %% write data;
336
+
337
+ static void http_parser_init(struct http_parser *hp)
338
+ {
339
+ int cs = 0;
340
+ memset(hp, 0, sizeof(struct http_parser));
341
+ hp->cont = Qfalse; /* zero on MRI, should be optimized away by above */
342
+ %% write init;
343
+ hp->cs = cs;
344
+ }
345
+
346
+ /** exec **/
347
+ static void http_parser_execute(struct http_parser *hp,
348
+ VALUE req, const char *buffer, size_t len)
349
+ {
350
+ const char *p, *pe;
351
+ int cs = hp->cs;
352
+ size_t off = hp->start.offset;
353
+
354
+ if (cs == http_parser_first_final)
355
+ return;
356
+
357
+ assert(off <= len && "offset past end of buffer");
358
+
359
+ p = buffer+off;
360
+ pe = buffer+len;
361
+
362
+ assert(pe - p == len - off && "pointers aren't same distance");
363
+
364
+ if (HP_FL_TEST(hp, INCHUNK)) {
365
+ HP_FL_UNSET(hp, INCHUNK);
366
+ goto skip_chunk_data_hack;
367
+ }
368
+ %% write exec;
369
+ post_exec: /* "_out:" also goes here */
370
+ if (hp->cs != http_parser_error)
371
+ hp->cs = cs;
372
+ hp->start.offset = p - buffer;
373
+
374
+ assert(p <= pe && "buffer overflow after parsing execute");
375
+ assert(hp->start.offset <= len && "start.offset longer than length");
376
+ }
377
+
378
+ static struct http_parser *data_get(VALUE self)
379
+ {
380
+ struct http_parser *hp;
381
+
382
+ Data_Get_Struct(self, struct http_parser, hp);
383
+ assert(hp && "failed to extract http_parser struct");
384
+ return hp;
385
+ }
386
+
387
+ static void finalize_header(struct http_parser *hp, VALUE req)
388
+ {
389
+ VALUE temp = rb_hash_aref(req, g_rack_url_scheme);
390
+ VALUE server_name = g_localhost;
391
+ VALUE server_port = g_port_80;
392
+
393
+ /* set rack.url_scheme to "https" or "http", no others are allowed by Rack */
394
+ if (NIL_P(temp)) {
395
+ temp = rb_hash_aref(req, g_http_x_forwarded_proto);
396
+ if (!NIL_P(temp) && STR_CSTR_EQ(temp, "https"))
397
+ server_port = g_port_443;
398
+ else
399
+ temp = g_http;
400
+ rb_hash_aset(req, g_rack_url_scheme, temp);
401
+ } else if (STR_CSTR_EQ(temp, "https")) {
402
+ server_port = g_port_443;
403
+ } else {
404
+ assert(server_port == g_port_80 && "server_port not set");
405
+ }
406
+
407
+ /* parse and set the SERVER_NAME and SERVER_PORT variables */
408
+ temp = rb_hash_aref(req, g_http_host);
409
+ if (!NIL_P(temp)) {
410
+ char *colon = memchr(RSTRING_PTR(temp), ':', RSTRING_LEN(temp));
411
+ if (colon) {
412
+ long port_start = colon - RSTRING_PTR(temp) + 1;
413
+
414
+ server_name = rb_str_substr(temp, 0, colon - RSTRING_PTR(temp));
415
+ if ((RSTRING_LEN(temp) - port_start) > 0)
416
+ server_port = rb_str_substr(temp, port_start, RSTRING_LEN(temp));
417
+ } else {
418
+ server_name = temp;
419
+ }
420
+ }
421
+ rb_hash_aset(req, g_server_name, server_name);
422
+ rb_hash_aset(req, g_server_port, server_port);
423
+ if (!HP_FL_TEST(hp, HASHEADER))
424
+ rb_hash_aset(req, g_server_protocol, g_http_09);
425
+
426
+ /* rack requires QUERY_STRING */
427
+ if (NIL_P(rb_hash_aref(req, g_query_string)))
428
+ rb_hash_aset(req, g_query_string, rb_str_new(NULL, 0));
429
+ }
430
+
431
+ static void hp_mark(void *ptr)
432
+ {
433
+ struct http_parser *hp = ptr;
434
+
435
+ rb_gc_mark(hp->cont);
436
+ }
437
+
438
+ static VALUE HttpParser_alloc(VALUE klass)
439
+ {
440
+ struct http_parser *hp;
441
+ return Data_Make_Struct(klass, struct http_parser, hp_mark, NULL, hp);
442
+ }
443
+
444
+
445
+ /**
446
+ * call-seq:
447
+ * parser.new => parser
448
+ *
449
+ * Creates a new parser.
450
+ */
451
+ static VALUE HttpParser_init(VALUE self)
452
+ {
453
+ http_parser_init(data_get(self));
454
+
455
+ return self;
456
+ }
457
+
458
+ /**
459
+ * call-seq:
460
+ * parser.reset => nil
461
+ *
462
+ * Resets the parser to it's initial state so that you can reuse it
463
+ * rather than making new ones.
464
+ */
465
+ static VALUE HttpParser_reset(VALUE self)
466
+ {
467
+ http_parser_init(data_get(self));
468
+
469
+ return Qnil;
470
+ }
471
+
472
+ static void advance_str(VALUE str, off_t nr)
473
+ {
474
+ long len = RSTRING_LEN(str);
475
+
476
+ if (len == 0)
477
+ return;
478
+
479
+ rb_str_modify(str);
480
+
481
+ assert(nr <= len && "trying to advance past end of buffer");
482
+ len -= nr;
483
+ if (len > 0) /* unlikely, len is usually 0 */
484
+ memmove(RSTRING_PTR(str), RSTRING_PTR(str) + nr, len);
485
+ rb_str_set_len(str, len);
486
+ }
487
+
488
+ /**
489
+ * call-seq:
490
+ * parser.content_length => nil or Integer
491
+ *
492
+ * Returns the number of bytes left to run through HttpParser#filter_body.
493
+ * This will initially be the value of the "Content-Length" HTTP header
494
+ * after header parsing is complete and will decrease in value as
495
+ * HttpParser#filter_body is called for each chunk. This should return
496
+ * zero for requests with no body.
497
+ *
498
+ * This will return nil on "Transfer-Encoding: chunked" requests.
499
+ */
500
+ static VALUE HttpParser_content_length(VALUE self)
501
+ {
502
+ struct http_parser *hp = data_get(self);
503
+
504
+ return HP_FL_TEST(hp, CHUNKED) ? Qnil : OFFT2NUM(hp->len.content);
505
+ }
506
+
507
+ /**
508
+ * Document-method: trailers
509
+ * call-seq:
510
+ * parser.trailers(req, data) => req or nil
511
+ *
512
+ * This is an alias for HttpParser#headers
513
+ */
514
+
515
+ /**
516
+ * Document-method: headers
517
+ * call-seq:
518
+ * parser.headers(req, data) => req or nil
519
+ *
520
+ * Takes a Hash and a String of data, parses the String of data filling
521
+ * in the Hash returning the Hash if parsing is finished, nil otherwise
522
+ * When returning the req Hash, it may modify data to point to where
523
+ * body processing should begin.
524
+ *
525
+ * Raises HttpParserError if there are parsing errors.
526
+ */
527
+ static VALUE HttpParser_headers(VALUE self, VALUE req, VALUE data)
528
+ {
529
+ struct http_parser *hp = data_get(self);
530
+
531
+ rb_str_update(data);
532
+
533
+ http_parser_execute(hp, req, RSTRING_PTR(data), RSTRING_LEN(data));
534
+ VALIDATE_MAX_LENGTH(hp->start.offset, HEADER);
535
+
536
+ if (hp->cs == http_parser_first_final ||
537
+ hp->cs == http_parser_en_ChunkedBody) {
538
+ advance_str(data, hp->start.offset + 1);
539
+ hp->start.offset = 0;
540
+
541
+ return req;
542
+ }
543
+
544
+ if (hp->cs == http_parser_error)
545
+ rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
546
+
547
+ return Qnil;
548
+ }
549
+
550
+ static int chunked_eof(struct http_parser *hp)
551
+ {
552
+ return ((hp->cs == http_parser_first_final) || HP_FL_TEST(hp, INTRAILER));
553
+ }
554
+
555
+ /**
556
+ * call-seq:
557
+ * parser.body_eof? => true or false
558
+ *
559
+ * Detects if we're done filtering the body or not. This can be used
560
+ * to detect when to stop calling HttpParser#filter_body.
561
+ */
562
+ static VALUE HttpParser_body_eof(VALUE self)
563
+ {
564
+ struct http_parser *hp = data_get(self);
565
+
566
+ if (HP_FL_TEST(hp, CHUNKED))
567
+ return chunked_eof(hp) ? Qtrue : Qfalse;
568
+
569
+ return hp->len.content == 0 ? Qtrue : Qfalse;
570
+ }
571
+
572
+ /**
573
+ * call-seq:
574
+ * parser.keepalive? => true or false
575
+ *
576
+ * This should be used to detect if a request can really handle
577
+ * keepalives and pipelining. Currently, the rules are:
578
+ *
579
+ * 1. MUST be a GET or HEAD request
580
+ * 2. MUST be HTTP/1.1 +or+ HTTP/1.0 with "Connection: keep-alive"
581
+ * 3. MUST NOT have "Connection: close" set
582
+ */
583
+ static VALUE HttpParser_keepalive(VALUE self)
584
+ {
585
+ struct http_parser *hp = data_get(self);
586
+
587
+ return HP_FL_ALL(hp, KEEPALIVE) ? Qtrue : Qfalse;
588
+ }
589
+
590
+ /**
591
+ * call-seq:
592
+ * parser.headers? => true or false
593
+ *
594
+ * This should be used to detect if a request has headers (and if
595
+ * the response will have headers as well). HTTP/0.9 requests
596
+ * should return false, all subsequent HTTP versions will return true
597
+ */
598
+ static VALUE HttpParser_has_headers(VALUE self)
599
+ {
600
+ struct http_parser *hp = data_get(self);
601
+
602
+ return HP_FL_TEST(hp, HASHEADER) ? Qtrue : Qfalse;
603
+ }
604
+
605
+ /**
606
+ * call-seq:
607
+ * parser.filter_body(buf, data) => nil/data
608
+ *
609
+ * Takes a String of +data+, will modify data if dechunking is done.
610
+ * Returns +nil+ if there is more data left to process. Returns
611
+ * +data+ if body processing is complete. When returning +data+,
612
+ * it may modify +data+ so the start of the string points to where
613
+ * the body ended so that trailer processing can begin.
614
+ *
615
+ * Raises HttpParserError if there are dechunking errors.
616
+ * Basically this is a glorified memcpy(3) that copies +data+
617
+ * into +buf+ while filtering it through the dechunker.
618
+ */
619
+ static VALUE HttpParser_filter_body(VALUE self, VALUE buf, VALUE data)
620
+ {
621
+ struct http_parser *hp = data_get(self);
622
+ char *dptr;
623
+ long dlen;
624
+
625
+ rb_str_update(data);
626
+ dptr = RSTRING_PTR(data);
627
+ dlen = RSTRING_LEN(data);
628
+
629
+ StringValue(buf);
630
+ rb_str_resize(buf, dlen); /* we can never copy more than dlen bytes */
631
+ OBJ_TAINT(buf); /* keep weirdo $SAFE users happy */
632
+
633
+ if (HP_FL_TEST(hp, CHUNKED)) {
634
+ if (!chunked_eof(hp)) {
635
+ hp->s.dest_offset = 0;
636
+ http_parser_execute(hp, buf, dptr, dlen);
637
+ if (hp->cs == http_parser_error)
638
+ rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
639
+
640
+ assert(hp->s.dest_offset <= hp->start.offset &&
641
+ "destination buffer overflow");
642
+ advance_str(data, hp->start.offset);
643
+ rb_str_set_len(buf, hp->s.dest_offset);
644
+
645
+ if (RSTRING_LEN(buf) == 0 && chunked_eof(hp)) {
646
+ assert(hp->len.chunk == 0 && "chunk at EOF but more to parse");
647
+ } else {
648
+ data = Qnil;
649
+ }
650
+ }
651
+ } else {
652
+ /* no need to enter the Ragel machine for unchunked transfers */
653
+ assert(hp->len.content >= 0 && "negative Content-Length");
654
+ if (hp->len.content > 0) {
655
+ long nr = MIN(dlen, hp->len.content);
656
+
657
+ memcpy(RSTRING_PTR(buf), dptr, nr);
658
+ hp->len.content -= nr;
659
+ if (hp->len.content == 0)
660
+ hp->cs = http_parser_first_final;
661
+ advance_str(data, nr);
662
+ rb_str_set_len(buf, nr);
663
+ data = Qnil;
664
+ }
665
+ }
666
+ hp->start.offset = 0; /* for trailer parsing */
667
+ return data;
668
+ }
669
+
670
+ #define SET_GLOBAL(var,str) do { \
671
+ var = find_common_field(str, sizeof(str) - 1); \
672
+ assert(!NIL_P(var) && "missed global field"); \
673
+ } while (0)
674
+
675
+ void Init_unicorn_http(void)
676
+ {
677
+ mUnicorn = rb_define_module("Unicorn");
678
+ eHttpParserError =
679
+ rb_define_class_under(mUnicorn, "HttpParserError", rb_eIOError);
680
+ cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
681
+ init_globals();
682
+ rb_define_alloc_func(cHttpParser, HttpParser_alloc);
683
+ rb_define_method(cHttpParser, "initialize", HttpParser_init,0);
684
+ rb_define_method(cHttpParser, "reset", HttpParser_reset,0);
685
+ rb_define_method(cHttpParser, "headers", HttpParser_headers, 2);
686
+ rb_define_method(cHttpParser, "filter_body", HttpParser_filter_body, 2);
687
+ rb_define_method(cHttpParser, "trailers", HttpParser_headers, 2);
688
+ rb_define_method(cHttpParser, "content_length", HttpParser_content_length, 0);
689
+ rb_define_method(cHttpParser, "body_eof?", HttpParser_body_eof, 0);
690
+ rb_define_method(cHttpParser, "keepalive?", HttpParser_keepalive, 0);
691
+ rb_define_method(cHttpParser, "headers?", HttpParser_has_headers, 0);
692
+
693
+ /*
694
+ * The maximum size a single chunk when using chunked transfer encoding.
695
+ * This is only a theoretical maximum used to detect errors in clients,
696
+ * it is highly unlikely to encounter clients that send more than
697
+ * several kilobytes at once.
698
+ */
699
+ rb_define_const(cHttpParser, "CHUNK_MAX", OFFT2NUM(UH_OFF_T_MAX));
700
+
701
+ /*
702
+ * The maximum size of the body as specified by Content-Length.
703
+ * This is only a theoretical maximum, the actual limit is subject
704
+ * to the limits of the file system used for +Dir.tmpdir+.
705
+ */
706
+ rb_define_const(cHttpParser, "LENGTH_MAX", OFFT2NUM(UH_OFF_T_MAX));
707
+
708
+ init_common_fields();
709
+ SET_GLOBAL(g_http_host, "HOST");
710
+ SET_GLOBAL(g_http_trailer, "TRAILER");
711
+ SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING");
712
+ SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
713
+ SET_GLOBAL(g_http_connection, "CONNECTION");
714
+ }
715
+ #undef SET_GLOBAL