http_tools 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +11 -5
- data/bench/parser/request_bench.rb +11 -15
- data/bench/parser/response_bench.rb +12 -0
- data/bench/transfer_encoding_chunked_bench.rb +26 -0
- data/example/http_client.rb +81 -32
- data/example/http_server.rb +82 -0
- data/lib/http_tools.rb +4 -0
- data/lib/http_tools/encoding.rb +25 -24
- data/lib/http_tools/errors.rb +1 -0
- data/lib/http_tools/parser.rb +170 -157
- data/test/encoding/transfer_encoding_chunked_test.rb +22 -0
- data/test/parser/request_test.rb +169 -126
- data/test/parser/response_test.rb +520 -52
- metadata +7 -5
data/lib/http_tools/parser.rb
CHANGED
@@ -11,9 +11,11 @@ module HTTPTools
|
|
11
11
|
#
|
12
12
|
# Example:
|
13
13
|
# parser = HTTPTools::Parser.new
|
14
|
-
# parser.on(:
|
15
|
-
#
|
16
|
-
#
|
14
|
+
# parser.on(:header) do |header|
|
15
|
+
# puts parser.status_code + " " + parser.method
|
16
|
+
# puts parser.header.inspect
|
17
|
+
# end
|
18
|
+
# parser.on(:stream) {|chunk| print chunk}
|
17
19
|
#
|
18
20
|
# parser << "HTTP/1.1 200 OK\r\n"
|
19
21
|
# parser << "Content-Length: 20\r\n\r\n"
|
@@ -27,16 +29,42 @@ module HTTPTools
|
|
27
29
|
class Parser
|
28
30
|
include Encoding
|
29
31
|
|
32
|
+
COLON = ":".freeze
|
30
33
|
KEY_TERMINATOR = ": ".freeze
|
31
34
|
CONTENT_LENGTH = "Content-Length".freeze
|
32
35
|
TRANSFER_ENCODING = "Transfer-Encoding".freeze
|
33
36
|
TRAILER = "Trailer".freeze
|
37
|
+
CONNECTION = "Connection".freeze
|
38
|
+
CLOSE = "close".freeze
|
34
39
|
CHUNKED = "chunked".freeze
|
35
|
-
EVENTS =
|
36
|
-
|
37
|
-
|
40
|
+
EVENTS = %W{header stream trailer finish error}.map do |event|
|
41
|
+
event.freeze
|
42
|
+
end.freeze
|
43
|
+
|
44
|
+
REQUEST_METHOD = "REQUEST_METHOD".freeze
|
45
|
+
PATH_INFO = "PATH_INFO".freeze
|
46
|
+
QUERY_STRING = "QUERY_STRING".freeze
|
47
|
+
REQUEST_URI = "REQUEST_URI".freeze
|
48
|
+
FRAGMENT = "FRAGMENT".freeze
|
49
|
+
|
50
|
+
PROTOTYPE_ENV = {
|
51
|
+
"SCRIPT_NAME" => "".freeze,
|
52
|
+
PATH_INFO => "/".freeze,
|
53
|
+
QUERY_STRING => "".freeze,
|
54
|
+
"rack.version" => [1, 1].freeze,
|
55
|
+
"rack.url_scheme" => "http".freeze,
|
56
|
+
"rack.errors" => STDERR,
|
57
|
+
"rack.multithread" => false,
|
58
|
+
"rack.multiprocess" => false,
|
59
|
+
"rack.run_once" => false}.freeze
|
60
|
+
|
61
|
+
HTTP_ = "HTTP_".freeze
|
62
|
+
LOWERCASE = "a-z-".freeze
|
63
|
+
UPPERCASE = "A-Z_".freeze
|
38
64
|
|
39
65
|
attr_reader :state # :nodoc:
|
66
|
+
attr_reader :request_method, :path_info, :query_string, :request_uri,
|
67
|
+
:fragment, :version, :status_code, :message, :header, :trailer
|
40
68
|
|
41
69
|
# Force parser to expect and parse a trailer when Trailer header missing.
|
42
70
|
attr_accessor :force_trailer
|
@@ -44,40 +72,19 @@ module HTTPTools
|
|
44
72
|
# Skip parsing the body, e.g. with the response to a HEAD request.
|
45
73
|
attr_accessor :force_no_body
|
46
74
|
|
47
|
-
#
|
75
|
+
# Allow responses with no status line or headers if it looks like HTML.
|
76
|
+
attr_accessor :allow_html_without_header
|
77
|
+
|
78
|
+
# :call-seq: Parser.new -> parser
|
48
79
|
#
|
49
80
|
# Create a new HTTPTools::Parser.
|
50
81
|
#
|
51
|
-
|
52
|
-
# parsing. The delegate's methods should be named on_[event name], e.g.
|
53
|
-
# on_status, on_body, etc. See #add_listener for more.
|
54
|
-
#
|
55
|
-
# Example:
|
56
|
-
# class ExampleDelegate
|
57
|
-
# def on_status(status, message)
|
58
|
-
# puts "#{status} #{message}"
|
59
|
-
# end
|
60
|
-
# end
|
61
|
-
# parser = HTTPTools::Parser.new(ExampleDelegate.new)
|
62
|
-
#
|
63
|
-
# If a callback is set for an event, it will take precedence over the
|
64
|
-
# delegate for that event.
|
65
|
-
#
|
66
|
-
def initialize(delegate=nil)
|
82
|
+
def initialize
|
67
83
|
@state = :start
|
68
84
|
@buffer = StringScanner.new("")
|
69
85
|
@buffer_backup_reference = @buffer
|
70
|
-
@
|
71
|
-
@
|
72
|
-
@last_key = nil
|
73
|
-
@content_left = nil
|
74
|
-
@body = nil
|
75
|
-
if delegate
|
76
|
-
EVENTS.each do |event|
|
77
|
-
id = "on_#{event}"
|
78
|
-
add_listener(event, delegate.method(id)) if delegate.respond_to?(id)
|
79
|
-
end
|
80
|
-
end
|
86
|
+
@header = {}
|
87
|
+
@trailer = {}
|
81
88
|
end
|
82
89
|
|
83
90
|
# :call-seq: parser.concat(data) -> parser
|
@@ -96,6 +103,29 @@ module HTTPTools
|
|
96
103
|
end
|
97
104
|
alias << concat
|
98
105
|
|
106
|
+
# :call-seq: parser.env -> hash or nil
|
107
|
+
#
|
108
|
+
# Returns a Rack compatible environment hash. Will return nil if called
|
109
|
+
# before headers are complete.
|
110
|
+
#
|
111
|
+
# The following are not supplied, and must be added to make the environment
|
112
|
+
# hash fully Rack compliant: SERVER_NAME, SERVER_PORT, rack.input
|
113
|
+
#
|
114
|
+
def env
|
115
|
+
return unless @header_complete
|
116
|
+
env = PROTOTYPE_ENV.merge(
|
117
|
+
REQUEST_METHOD => @request_method,
|
118
|
+
REQUEST_URI => @request_uri)
|
119
|
+
if @path_info
|
120
|
+
env[PATH_INFO] = @path_info
|
121
|
+
env[QUERY_STRING] = @query_string
|
122
|
+
end
|
123
|
+
env[FRAGMENT] = @fragment if @fragment
|
124
|
+
@header.each {|k, val| env[HTTP_ + k.tr(LOWERCASE, UPPERCASE)] = val}
|
125
|
+
@trailer.each {|k, val| env[HTTP_ + k.tr(LOWERCASE, UPPERCASE)] = val}
|
126
|
+
env
|
127
|
+
end
|
128
|
+
|
99
129
|
# :call-seq: parser.finish -> parser
|
100
130
|
#
|
101
131
|
# Used to notify the parser that the request has finished in a case where it
|
@@ -121,8 +151,12 @@ module HTTPTools
|
|
121
151
|
#
|
122
152
|
def finish
|
123
153
|
if @state == :body_on_close
|
124
|
-
@body_callback.call(@body) if @body_callback
|
125
154
|
@state = end_of_message
|
155
|
+
elsif @state == :body_chunked && @header[CONNECTION] == CLOSE &&
|
156
|
+
!@header[TRAILER] && @buffer.eos?
|
157
|
+
@state = end_of_message
|
158
|
+
elsif @state == :start && @buffer.string.length < 1
|
159
|
+
raise EmptyMessageError.new("Message empty")
|
126
160
|
else
|
127
161
|
raise MessageIncompleteError.new("Message ended early")
|
128
162
|
end
|
@@ -153,12 +187,17 @@ module HTTPTools
|
|
153
187
|
@buffer = @buffer_backup_reference
|
154
188
|
@buffer.string.replace("")
|
155
189
|
@buffer.reset
|
156
|
-
|
157
|
-
@
|
190
|
+
@request_method = nil
|
191
|
+
@path_info = nil
|
192
|
+
@query_string = nil
|
193
|
+
@request_uri = nil
|
194
|
+
@fragment = nil
|
195
|
+
@version = nil
|
196
|
+
@status_code = nil
|
197
|
+
@header = {}
|
158
198
|
@trailer = {}
|
159
|
-
|
160
|
-
|
161
|
-
@body = nil
|
199
|
+
@last_key = nil
|
200
|
+
@content_left = nil
|
162
201
|
self
|
163
202
|
end
|
164
203
|
|
@@ -167,44 +206,20 @@ module HTTPTools
|
|
167
206
|
# parser.on(event) {|arg1 [, arg2]| block} -> parser
|
168
207
|
# parser.on(event, proc) -> parser
|
169
208
|
#
|
170
|
-
# Available events are :
|
171
|
-
# :body, and :error.
|
209
|
+
# Available events are :header, :stream, :trailer, :finish, and :error.
|
172
210
|
#
|
173
211
|
# Adding a second callback for an event will overwite the existing callback
|
174
212
|
# or delegate.
|
175
213
|
#
|
176
214
|
# Events:
|
177
|
-
# [
|
178
|
-
# e.g. "GET"
|
179
|
-
#
|
180
|
-
# [path] Supplied with two arguments, the request path as a String,
|
181
|
-
# e.g. "/example.html", and the query string as a String,
|
182
|
-
# e.g. "query=foo"
|
183
|
-
# (this callback is only called if the request uri is a path)
|
184
|
-
#
|
185
|
-
# [uri] Supplied with one argument, the request uri as a String,
|
186
|
-
# e.g. "/example.html?query=foo"
|
187
|
-
#
|
188
|
-
# [fragment] Supplied with one argument, the fragment from the request
|
189
|
-
# uri, if present
|
190
|
-
#
|
191
|
-
# [version] Supplied with one argument, the HTTP version as a String,
|
192
|
-
# e.g. "1.1"
|
193
|
-
#
|
194
|
-
# [status] Supplied with two arguments, the HTTP status code as a
|
195
|
-
# Numeric, e.g. 200, and the HTTP status message as a String,
|
196
|
-
# e.g. "OK"
|
197
|
-
#
|
198
|
-
# [headers] Supplied with one argument, the message headers as a Hash,
|
199
|
-
# e.g. {"Content-Length" => "20"}
|
215
|
+
# [header] Called when headers are complete
|
200
216
|
#
|
201
217
|
# [stream] Supplied with one argument, the last chunk of body data fed
|
202
218
|
# in to the parser as a String, e.g. "<h1>Hello"
|
203
219
|
#
|
204
|
-
# [
|
205
|
-
# e.g. "<h1>Hello world</h1>"
|
220
|
+
# [trailer] Called on the completion of the trailer, if present
|
206
221
|
#
|
207
|
-
# [
|
222
|
+
# [finish] Supplied with one argument, any data left in the parser's
|
208
223
|
# buffer after the end of the HTTP message (likely nil, but
|
209
224
|
# possibly the start of the next message)
|
210
225
|
#
|
@@ -221,37 +236,31 @@ module HTTPTools
|
|
221
236
|
|
222
237
|
private
|
223
238
|
def start
|
224
|
-
|
225
|
-
if
|
226
|
-
|
227
|
-
|
228
|
-
method.upcase!
|
229
|
-
@method_callback.call(method)
|
230
|
-
end
|
239
|
+
@request_method = @buffer.scan(/[a-z]+ /i)
|
240
|
+
if @request_method
|
241
|
+
@request_method.chop!
|
242
|
+
@request_method.upcase!
|
231
243
|
uri
|
232
244
|
elsif @buffer.skip(/HTTP\//i)
|
233
245
|
response_http_version
|
234
|
-
elsif @buffer.check(/[a-z]
|
246
|
+
elsif @buffer.check(/[a-z]*\Z/i)
|
235
247
|
:start
|
248
|
+
elsif @allow_html_without_header && @buffer.check(/\s*</i)
|
249
|
+
skip_header
|
236
250
|
else
|
237
251
|
raise ParseError.new("Protocol or method not recognised")
|
238
252
|
end
|
239
253
|
end
|
240
254
|
|
241
255
|
def uri
|
242
|
-
|
243
|
-
if
|
244
|
-
fragment =
|
245
|
-
if @
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
@
|
250
|
-
end
|
251
|
-
@uri_callback.call(uri) if @uri_callback
|
252
|
-
if fragment && @fragment_callback
|
253
|
-
fragment.slice!(0)
|
254
|
-
@fragment_callback.call(fragment)
|
256
|
+
@request_uri= @buffer.scan(/[a-z0-9;\/?:@&=+$,%_.!~*')(#-]*(?=( |\r\n))/i)
|
257
|
+
if @request_uri
|
258
|
+
@fragment = @request_uri.slice!(/#[a-z0-9;\/?:@&=+$,%_.!~*')(-]+\Z/i)
|
259
|
+
@fragment.slice!(0) if @fragment
|
260
|
+
if @request_uri =~ /^\//i
|
261
|
+
@path_info = @request_uri.dup
|
262
|
+
@query_string = @path_info.slice!(/\?[a-z0-9;\/?:@&=+$,%_.!~*')(-]*/i)
|
263
|
+
@query_string ? @query_string.slice!(0) : @query_string = ""
|
255
264
|
end
|
256
265
|
space_before_http
|
257
266
|
elsif @buffer.check(/[a-z0-9;\/?:@&=+$,%_.!~*')(#-]+\Z/i)
|
@@ -280,12 +289,9 @@ module HTTPTools
|
|
280
289
|
end
|
281
290
|
|
282
291
|
def request_http_version
|
283
|
-
version = @buffer.scan(/[0-9]+\.[0-
|
284
|
-
if version
|
285
|
-
|
286
|
-
version.chop!
|
287
|
-
@version_callback.call(version)
|
288
|
-
end
|
292
|
+
@version = @buffer.scan(/[0-9]+\.[0-9x]+\r\n/i)
|
293
|
+
if @version
|
294
|
+
@version.chop!
|
289
295
|
key_or_newline
|
290
296
|
elsif @buffer.eos? || @buffer.check(/\d+(\.(\d+\r?)?)?\Z/i)
|
291
297
|
:request_http_version
|
@@ -295,12 +301,9 @@ module HTTPTools
|
|
295
301
|
end
|
296
302
|
|
297
303
|
def response_http_version
|
298
|
-
version = @buffer.scan(/[0-9]+\.[0-
|
299
|
-
if version
|
300
|
-
|
301
|
-
version.chop!
|
302
|
-
@version_callback.call(version)
|
303
|
-
end
|
304
|
+
@version = @buffer.scan(/[0-9]+\.[0-9x]+ /i)
|
305
|
+
if @version
|
306
|
+
version.chop!
|
304
307
|
status
|
305
308
|
elsif @buffer.eos? || @buffer.check(/\d+(\.(\d+)?)?\Z/i)
|
306
309
|
:response_http_version
|
@@ -309,13 +312,23 @@ module HTTPTools
|
|
309
312
|
end
|
310
313
|
end
|
311
314
|
|
315
|
+
def skip_header
|
316
|
+
@version = "0.0"
|
317
|
+
@status_code = 200
|
318
|
+
@message = ""
|
319
|
+
@header_complete = true
|
320
|
+
@header_callback.call if @header_callback
|
321
|
+
body
|
322
|
+
end
|
323
|
+
|
312
324
|
def status
|
313
|
-
status = @buffer.scan(/\d\d\d
|
325
|
+
status = @buffer.scan(/\d\d\d[^\x00-\x1f\x7f]*\r?\n/i)
|
314
326
|
if status
|
315
|
-
@
|
316
|
-
@
|
327
|
+
@status_code = status.slice!(0, 3).to_i
|
328
|
+
@message = status.strip
|
317
329
|
key_or_newline
|
318
|
-
elsif @buffer.eos? ||
|
330
|
+
elsif @buffer.eos? ||
|
331
|
+
@buffer.check(/\d(\d(\d( ([^\x00-\x1f\x7f]+\r?)?)?)?)?\Z/i)
|
319
332
|
:status
|
320
333
|
else
|
321
334
|
raise ParseError.new("Invalid status line")
|
@@ -323,27 +336,45 @@ module HTTPTools
|
|
323
336
|
end
|
324
337
|
|
325
338
|
def key_or_newline
|
326
|
-
@last_key = @buffer.scan(/[
|
339
|
+
@last_key = @buffer.scan(/[ -9;-~]+: /i)
|
327
340
|
if @last_key
|
328
341
|
@last_key.chomp!(KEY_TERMINATOR)
|
329
342
|
value
|
330
|
-
elsif @buffer.skip(/\
|
331
|
-
@
|
343
|
+
elsif @buffer.skip(/\r?\n/i)
|
344
|
+
@header_complete = true
|
345
|
+
@header_callback.call if @header_callback
|
332
346
|
body
|
333
|
-
elsif @buffer.eos? || @buffer.check(/[
|
347
|
+
elsif @buffer.eos? || @buffer.check(/([ -9;-~]+:?|\r)\Z/i)
|
334
348
|
:key_or_newline
|
349
|
+
elsif @last_key = @buffer.scan(/[ -9;-~]+:(?=[^ ])/i)
|
350
|
+
@last_key.chomp!(COLON)
|
351
|
+
value
|
352
|
+
else
|
353
|
+
skip_bad_header
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def skip_bad_header
|
358
|
+
if @buffer.skip(/[^\x00\n\x7f]*\n/)
|
359
|
+
key_or_newline
|
360
|
+
elsif @buffer.check(/[^\x00\n\x7f]+\Z/)
|
361
|
+
:skip_bad_header
|
335
362
|
else
|
336
363
|
raise ParseError.new("Illegal character in field name")
|
337
364
|
end
|
338
365
|
end
|
339
366
|
|
340
367
|
def value
|
341
|
-
value = @buffer.scan(/[
|
368
|
+
value = @buffer.scan(/[^\x00\n\x7f]*\r?\n/i)
|
342
369
|
if value
|
343
370
|
value.chop!
|
344
|
-
|
371
|
+
if ARRAY_VALUE_HEADERS[@last_key]
|
372
|
+
@header.fetch(@last_key) {@header[@last_key] = []}.push(value)
|
373
|
+
else
|
374
|
+
@header[@last_key] = value
|
375
|
+
end
|
345
376
|
key_or_newline
|
346
|
-
elsif @buffer.eos? || @buffer.check(/[
|
377
|
+
elsif @buffer.eos? || @buffer.check(/[^\x00\n\x7f]+\r?\Z/i)
|
347
378
|
:value
|
348
379
|
else
|
349
380
|
raise ParseError.new("Illegal character in field body")
|
@@ -351,18 +382,14 @@ module HTTPTools
|
|
351
382
|
end
|
352
383
|
|
353
384
|
def body
|
354
|
-
if @force_no_body || NO_BODY[@
|
385
|
+
if @force_no_body || NO_BODY[@status_code]
|
355
386
|
end_of_message
|
356
|
-
elsif @buffer.eos?
|
357
|
-
:body
|
358
387
|
else
|
359
|
-
|
360
|
-
@buffer = @buffer.rest # Switch @buffer from StringScanner to String
|
361
|
-
length = @headers[CONTENT_LENGTH]
|
388
|
+
length = @header[CONTENT_LENGTH]
|
362
389
|
if length
|
363
390
|
@content_left = length.to_i
|
364
391
|
body_with_length
|
365
|
-
elsif @
|
392
|
+
elsif @header[TRANSFER_ENCODING] == CHUNKED
|
366
393
|
body_chunked
|
367
394
|
else
|
368
395
|
body_on_close
|
@@ -370,48 +397,35 @@ module HTTPTools
|
|
370
397
|
end
|
371
398
|
end
|
372
399
|
|
373
|
-
#--
|
374
|
-
# From this point on @buffer is a String, not a StringScanner.
|
375
|
-
# This is because 1. we don't need a StringScanner anymore, 2. if we
|
376
|
-
# switched to a diffrent instace variable we'd need a condition in #concat
|
377
|
-
# to feed the data in to the new instace variable, which would slow us down.
|
378
|
-
#++
|
379
|
-
|
380
400
|
def body_with_length
|
381
|
-
if
|
382
|
-
chunk = @buffer.slice
|
401
|
+
if !@buffer.eos?
|
402
|
+
chunk = @buffer.string.slice(@buffer.pos, @content_left)
|
383
403
|
@stream_callback.call(chunk) if @stream_callback
|
384
|
-
|
385
|
-
@
|
404
|
+
chunk_length = chunk.length
|
405
|
+
@buffer.pos += chunk_length
|
406
|
+
@content_left -= chunk_length
|
386
407
|
if @content_left < 1
|
387
|
-
@body_callback.call(@body) if @body_callback
|
388
408
|
end_of_message
|
389
409
|
else
|
390
410
|
:body_with_length
|
391
411
|
end
|
412
|
+
elsif @content_left < 1 # zero length body
|
413
|
+
@stream_callback.call("") if @stream_callback
|
414
|
+
end_of_message
|
392
415
|
else
|
393
416
|
:body_with_length
|
394
417
|
end
|
395
418
|
end
|
396
419
|
|
397
420
|
def body_chunked
|
398
|
-
decoded, remainder = transfer_encoding_chunked_decode(@buffer)
|
421
|
+
decoded, remainder = transfer_encoding_chunked_decode(nil, @buffer)
|
399
422
|
if decoded
|
400
423
|
@stream_callback.call(decoded) if @stream_callback
|
401
|
-
@body << decoded if @body_callback
|
402
424
|
end
|
403
425
|
if remainder
|
404
|
-
@buffer = remainder
|
405
426
|
:body_chunked
|
406
427
|
else
|
407
|
-
@
|
408
|
-
@body_callback.call(@body) if @body_callback
|
409
|
-
if @headers[TRAILER] || @force_trailer
|
410
|
-
@trailer = {}
|
411
|
-
# @buffer switches back to a StringScanner for the trailer.
|
412
|
-
@buffer_backup_reference.string.replace(@buffer)
|
413
|
-
@buffer_backup_reference.reset
|
414
|
-
@buffer = @buffer_backup_reference
|
428
|
+
if @header[TRAILER] || @force_trailer
|
415
429
|
trailer_key_or_newline
|
416
430
|
else
|
417
431
|
end_of_message
|
@@ -420,37 +434,36 @@ module HTTPTools
|
|
420
434
|
end
|
421
435
|
|
422
436
|
def body_on_close
|
423
|
-
|
424
|
-
@
|
425
|
-
@
|
437
|
+
chunk = @buffer.rest
|
438
|
+
@buffer.terminate
|
439
|
+
@stream_callback.call(chunk) if @stream_callback
|
426
440
|
:body_on_close
|
427
441
|
end
|
428
442
|
|
429
|
-
#--
|
430
|
-
# @buffer switches back to a StringScanner for the trailer.
|
431
|
-
#++
|
432
|
-
|
433
443
|
def trailer_key_or_newline
|
434
|
-
if @last_key = @buffer.scan(/[
|
444
|
+
if @last_key = @buffer.scan(/[ -9;-~]+: /i)
|
435
445
|
@last_key.chomp!(KEY_TERMINATOR)
|
436
446
|
trailer_value
|
437
|
-
elsif @buffer.skip(/\
|
438
|
-
@trailer_callback.call
|
447
|
+
elsif @buffer.skip(/\r?\n/i)
|
448
|
+
@trailer_callback.call if @trailer_callback
|
439
449
|
end_of_message
|
440
|
-
elsif @buffer.eos? || @buffer.check(/[
|
450
|
+
elsif @buffer.eos? || @buffer.check(/([ -9;-~]+:?|\r)\Z/i)
|
441
451
|
:trailer_key_or_newline
|
452
|
+
elsif @last_key = @buffer.scan(/[ -9;-~]+:(?=[^ ])/i)
|
453
|
+
@last_key.chomp!(COLON)
|
454
|
+
trailer_value
|
442
455
|
else
|
443
456
|
raise ParseError.new("Illegal character in field name")
|
444
457
|
end
|
445
458
|
end
|
446
459
|
|
447
460
|
def trailer_value
|
448
|
-
value = @buffer.scan(/[
|
461
|
+
value = @buffer.scan(/[^\000\n\177]+\r?\n/i)
|
449
462
|
if value
|
450
463
|
value.chop!
|
451
464
|
@trailer[@last_key] = value
|
452
465
|
trailer_key_or_newline
|
453
|
-
elsif @buffer.eos? || @buffer.check(/[
|
466
|
+
elsif @buffer.eos? || @buffer.check(/[^\x00\n\x7f]+\r?\Z/i)
|
454
467
|
:trailer_value
|
455
468
|
else
|
456
469
|
raise ParseError.new("Illegal character in field body")
|
@@ -460,8 +473,8 @@ module HTTPTools
|
|
460
473
|
def end_of_message
|
461
474
|
raise EndOfMessageError.new("Message ended") if @state == :end_of_message
|
462
475
|
remainder = @buffer.respond_to?(:rest) ? @buffer.rest : @buffer
|
463
|
-
if @
|
464
|
-
@
|
476
|
+
if @finish_callback
|
477
|
+
@finish_callback.call((remainder if remainder.length > 0))
|
465
478
|
end
|
466
479
|
:end_of_message
|
467
480
|
end
|