em-http-request 0.2.10 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of em-http-request might be problematic. Click here for more details.

@@ -1,48 +1,48 @@
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
-
6
- #ifndef http11_parser_h
7
- #define http11_parser_h
8
-
9
- #include <sys/types.h>
10
-
11
- #if defined(_WIN32)
12
- #include <stddef.h>
13
- #endif
14
-
15
- typedef void (*element_cb)(void *data, const char *at, size_t length);
16
- typedef void (*field_cb)(void *data, const char *field, size_t flen, const char *value, size_t vlen);
17
-
18
- typedef struct httpclient_parser {
19
- int cs;
20
- size_t body_start;
21
- int content_len;
22
- size_t nread;
23
- size_t mark;
24
- size_t field_start;
25
- size_t field_len;
26
-
27
- void *data;
28
-
29
- field_cb http_field;
30
- element_cb reason_phrase;
31
- element_cb status_code;
32
- element_cb chunk_size;
33
- element_cb http_version;
34
- element_cb header_done;
35
- element_cb last_chunk;
36
-
37
-
38
- } httpclient_parser;
39
-
40
- int httpclient_parser_init(httpclient_parser *parser);
41
- int httpclient_parser_finish(httpclient_parser *parser);
42
- size_t httpclient_parser_execute(httpclient_parser *parser, const char *data, size_t len, size_t off);
43
- int httpclient_parser_has_error(httpclient_parser *parser);
44
- int httpclient_parser_is_finished(httpclient_parser *parser);
45
-
46
- #define httpclient_parser_nread(parser) (parser)->nread
47
-
48
- #endif
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
+
6
+ #ifndef http11_parser_h
7
+ #define http11_parser_h
8
+
9
+ #include <sys/types.h>
10
+
11
+ #if defined(_WIN32)
12
+ #include <stddef.h>
13
+ #endif
14
+
15
+ typedef void (*element_cb)(void *data, const char *at, size_t length);
16
+ typedef void (*field_cb)(void *data, const char *field, size_t flen, const char *value, size_t vlen);
17
+
18
+ typedef struct httpclient_parser {
19
+ int cs;
20
+ size_t body_start;
21
+ int content_len;
22
+ size_t nread;
23
+ size_t mark;
24
+ size_t field_start;
25
+ size_t field_len;
26
+
27
+ void *data;
28
+
29
+ field_cb http_field;
30
+ element_cb reason_phrase;
31
+ element_cb status_code;
32
+ element_cb chunk_size;
33
+ element_cb http_version;
34
+ element_cb header_done;
35
+ element_cb last_chunk;
36
+
37
+
38
+ } httpclient_parser;
39
+
40
+ int httpclient_parser_init(httpclient_parser *parser);
41
+ int httpclient_parser_finish(httpclient_parser *parser);
42
+ size_t httpclient_parser_execute(httpclient_parser *parser, const char *data, size_t len, size_t off);
43
+ int httpclient_parser_has_error(httpclient_parser *parser);
44
+ int httpclient_parser_is_finished(httpclient_parser *parser);
45
+
46
+ #define httpclient_parser_nread(parser) (parser)->nread
47
+
48
+ #endif
@@ -1,170 +1,170 @@
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
-
6
- #include "http11_parser.h"
7
- #include <stdio.h>
8
- #include <assert.h>
9
- #include <stdlib.h>
10
- #include <ctype.h>
11
- #include <string.h>
12
-
13
- #define LEN(AT, FPC) (FPC - buffer - parser->AT)
14
- #define MARK(M,FPC) (parser->M = (FPC) - buffer)
15
- #define PTR_TO(F) (buffer + parser->F)
16
- #define L(M) fprintf(stderr, "" # M "\n");
17
-
18
-
19
- /** machine **/
20
- %%{
21
- machine httpclient_parser;
22
-
23
- action mark {MARK(mark, fpc); }
24
-
25
- action start_field { MARK(field_start, fpc); }
26
-
27
- action write_field {
28
- parser->field_len = LEN(field_start, fpc);
29
- }
30
-
31
- action start_value { MARK(mark, fpc); }
32
-
33
- action write_value {
34
- parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, fpc));
35
- }
36
-
37
- action reason_phrase {
38
- parser->reason_phrase(parser->data, PTR_TO(mark), LEN(mark, fpc));
39
- }
40
-
41
- action status_code {
42
- parser->status_code(parser->data, PTR_TO(mark), LEN(mark, fpc));
43
- }
44
-
45
- action http_version {
46
- parser->http_version(parser->data, PTR_TO(mark), LEN(mark, fpc));
47
- }
48
-
49
- action chunk_size {
50
- parser->chunk_size(parser->data, PTR_TO(mark), LEN(mark, fpc));
51
- }
52
-
53
- action last_chunk {
54
- parser->last_chunk(parser->data, NULL, 0);
55
- }
56
-
57
- action done {
58
- parser->body_start = fpc - buffer + 1;
59
- if(parser->header_done != NULL)
60
- parser->header_done(parser->data, fpc + 1, pe - fpc - 1);
61
- fbreak;
62
- }
63
-
64
- # line endings
65
- CRLF = ("\r\n" | "\n");
66
-
67
- # character types
68
- CTL = (cntrl | 127);
69
- tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");
70
-
71
- # elements
72
- token = (ascii -- (CTL | tspecials));
73
-
74
- Reason_Phrase = (any -- CRLF)* >mark %reason_phrase;
75
- Status_Code = digit{3} >mark %status_code;
76
- http_number = (digit+ "." digit+) ;
77
- HTTP_Version = ("HTTP/" http_number) >mark %http_version ;
78
- Status_Line = HTTP_Version " " Status_Code " "? Reason_Phrase :> CRLF;
79
-
80
- field_name = token+ >start_field %write_field;
81
- field_value = any* >start_value %write_value;
82
- message_header = field_name ":" " "* field_value :> CRLF;
83
-
84
- Response = Status_Line (message_header)* (CRLF @done);
85
-
86
- chunk_ext_val = token+;
87
- chunk_ext_name = token+;
88
- chunk_extension = (";" chunk_ext_name >start_field %write_field %start_value ("=" chunk_ext_val >start_value)? %write_value )*;
89
- last_chunk = "0"? chunk_extension :> (CRLF @last_chunk @done);
90
- chunk_size = xdigit+;
91
- chunk = chunk_size >mark %chunk_size chunk_extension space* :> (CRLF @done);
92
- Chunked_Header = (chunk | last_chunk);
93
-
94
- main := Response | Chunked_Header;
95
- }%%
96
-
97
- /** Data **/
98
- %% write data;
99
-
100
- int httpclient_parser_init(httpclient_parser *parser) {
101
- int cs = 0;
102
- %% write init;
103
- parser->cs = cs;
104
- parser->body_start = 0;
105
- parser->content_len = 0;
106
- parser->mark = 0;
107
- parser->nread = 0;
108
- parser->field_len = 0;
109
- parser->field_start = 0;
110
-
111
- return(1);
112
- }
113
-
114
-
115
- /** exec **/
116
- size_t httpclient_parser_execute(httpclient_parser *parser, const char *buffer, size_t len, size_t off) {
117
- const char *p, *pe;
118
- int cs = parser->cs;
119
-
120
- assert(off <= len && "offset past end of buffer");
121
-
122
- p = buffer+off;
123
- pe = buffer+len;
124
-
125
- assert(*pe == '\0' && "pointer does not end on NUL");
126
- assert(pe - p == len - off && "pointers aren't same distance");
127
-
128
-
129
- %% write exec;
130
-
131
- parser->cs = cs;
132
- parser->nread += p - (buffer + off);
133
-
134
- assert(p <= pe && "buffer overflow after parsing execute");
135
- assert(parser->nread <= len && "nread longer than length");
136
- assert(parser->body_start <= len && "body starts after buffer end");
137
- assert(parser->mark < len && "mark is after buffer end");
138
- assert(parser->field_len <= len && "field has length longer than whole buffer");
139
- assert(parser->field_start < len && "field starts after buffer end");
140
-
141
- if(parser->body_start) {
142
- /* final \r\n combo encountered so stop right here */
143
- parser->nread = parser->body_start;
144
- }
145
-
146
- return(parser->nread);
147
- }
148
-
149
- int httpclient_parser_finish(httpclient_parser *parser)
150
- {
151
- int cs = parser->cs;
152
-
153
- parser->cs = cs;
154
-
155
- if (httpclient_parser_has_error(parser) ) {
156
- return -1;
157
- } else if (httpclient_parser_is_finished(parser) ) {
158
- return 1;
159
- } else {
160
- return 0;
161
- }
162
- }
163
-
164
- int httpclient_parser_has_error(httpclient_parser *parser) {
165
- return parser->cs == httpclient_parser_error;
166
- }
167
-
168
- int httpclient_parser_is_finished(httpclient_parser *parser) {
169
- return parser->cs == httpclient_parser_first_final;
170
- }
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
+
6
+ #include "http11_parser.h"
7
+ #include <stdio.h>
8
+ #include <assert.h>
9
+ #include <stdlib.h>
10
+ #include <ctype.h>
11
+ #include <string.h>
12
+
13
+ #define LEN(AT, FPC) (FPC - buffer - parser->AT)
14
+ #define MARK(M,FPC) (parser->M = (FPC) - buffer)
15
+ #define PTR_TO(F) (buffer + parser->F)
16
+ #define L(M) fprintf(stderr, "" # M "\n");
17
+
18
+
19
+ /** machine **/
20
+ %%{
21
+ machine httpclient_parser;
22
+
23
+ action mark {MARK(mark, fpc); }
24
+
25
+ action start_field { MARK(field_start, fpc); }
26
+
27
+ action write_field {
28
+ parser->field_len = LEN(field_start, fpc);
29
+ }
30
+
31
+ action start_value { MARK(mark, fpc); }
32
+
33
+ action write_value {
34
+ parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, fpc));
35
+ }
36
+
37
+ action reason_phrase {
38
+ parser->reason_phrase(parser->data, PTR_TO(mark), LEN(mark, fpc));
39
+ }
40
+
41
+ action status_code {
42
+ parser->status_code(parser->data, PTR_TO(mark), LEN(mark, fpc));
43
+ }
44
+
45
+ action http_version {
46
+ parser->http_version(parser->data, PTR_TO(mark), LEN(mark, fpc));
47
+ }
48
+
49
+ action chunk_size {
50
+ parser->chunk_size(parser->data, PTR_TO(mark), LEN(mark, fpc));
51
+ }
52
+
53
+ action last_chunk {
54
+ parser->last_chunk(parser->data, NULL, 0);
55
+ }
56
+
57
+ action done {
58
+ parser->body_start = fpc - buffer + 1;
59
+ if(parser->header_done != NULL)
60
+ parser->header_done(parser->data, fpc + 1, pe - fpc - 1);
61
+ fbreak;
62
+ }
63
+
64
+ # line endings
65
+ CRLF = ("\r\n" | "\n");
66
+
67
+ # character types
68
+ CTL = (cntrl | 127);
69
+ tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");
70
+
71
+ # elements
72
+ token = (ascii -- (CTL | tspecials));
73
+
74
+ Reason_Phrase = (any -- CRLF)* >mark %reason_phrase;
75
+ Status_Code = digit{3} >mark %status_code;
76
+ http_number = (digit+ "." digit+) ;
77
+ HTTP_Version = ("HTTP/" http_number) >mark %http_version ;
78
+ Status_Line = HTTP_Version " " Status_Code " "? Reason_Phrase :> CRLF;
79
+
80
+ field_name = token+ >start_field %write_field;
81
+ field_value = any* >start_value %write_value;
82
+ message_header = field_name ":" " "* field_value :> CRLF;
83
+
84
+ Response = Status_Line (message_header)* (CRLF @done);
85
+
86
+ chunk_ext_val = token+;
87
+ chunk_ext_name = token+;
88
+ chunk_extension = (";" chunk_ext_name >start_field %write_field %start_value ("=" chunk_ext_val >start_value)? %write_value )*;
89
+ last_chunk = "0"? chunk_extension :> (CRLF @last_chunk @done);
90
+ chunk_size = xdigit+;
91
+ chunk = chunk_size >mark %chunk_size chunk_extension space* :> (CRLF @done);
92
+ Chunked_Header = (chunk | last_chunk);
93
+
94
+ main := Response | Chunked_Header;
95
+ }%%
96
+
97
+ /** Data **/
98
+ %% write data;
99
+
100
+ int httpclient_parser_init(httpclient_parser *parser) {
101
+ int cs = 0;
102
+ %% write init;
103
+ parser->cs = cs;
104
+ parser->body_start = 0;
105
+ parser->content_len = 0;
106
+ parser->mark = 0;
107
+ parser->nread = 0;
108
+ parser->field_len = 0;
109
+ parser->field_start = 0;
110
+
111
+ return(1);
112
+ }
113
+
114
+
115
+ /** exec **/
116
+ size_t httpclient_parser_execute(httpclient_parser *parser, const char *buffer, size_t len, size_t off) {
117
+ const char *p, *pe;
118
+ int cs = parser->cs;
119
+
120
+ assert(off <= len && "offset past end of buffer");
121
+
122
+ p = buffer+off;
123
+ pe = buffer+len;
124
+
125
+ assert(*pe == '\0' && "pointer does not end on NUL");
126
+ assert(pe - p == len - off && "pointers aren't same distance");
127
+
128
+
129
+ %% write exec;
130
+
131
+ parser->cs = cs;
132
+ parser->nread += p - (buffer + off);
133
+
134
+ assert(p <= pe && "buffer overflow after parsing execute");
135
+ assert(parser->nread <= len && "nread longer than length");
136
+ assert(parser->body_start <= len && "body starts after buffer end");
137
+ assert(parser->mark < len && "mark is after buffer end");
138
+ assert(parser->field_len <= len && "field has length longer than whole buffer");
139
+ assert(parser->field_start < len && "field starts after buffer end");
140
+
141
+ if(parser->body_start) {
142
+ /* final \r\n combo encountered so stop right here */
143
+ parser->nread = parser->body_start;
144
+ }
145
+
146
+ return(parser->nread);
147
+ }
148
+
149
+ int httpclient_parser_finish(httpclient_parser *parser)
150
+ {
151
+ int cs = parser->cs;
152
+
153
+ parser->cs = cs;
154
+
155
+ if (httpclient_parser_has_error(parser) ) {
156
+ return -1;
157
+ } else if (httpclient_parser_is_finished(parser) ) {
158
+ return 1;
159
+ } else {
160
+ return 0;
161
+ }
162
+ }
163
+
164
+ int httpclient_parser_has_error(httpclient_parser *parser) {
165
+ return parser->cs == httpclient_parser_error;
166
+ }
167
+
168
+ int httpclient_parser_is_finished(httpclient_parser *parser) {
169
+ return parser->cs == httpclient_parser_first_final;
170
+ }
@@ -1 +1 @@
1
- require 'em-http'
1
+ require 'em-http'
@@ -16,4 +16,5 @@ require File.dirname(__FILE__) + '/em-http/client'
16
16
  require File.dirname(__FILE__) + '/em-http/multi'
17
17
  require File.dirname(__FILE__) + '/em-http/request'
18
18
  require File.dirname(__FILE__) + '/em-http/decoders'
19
- require File.dirname(__FILE__) + '/em-http/http_options'
19
+ require File.dirname(__FILE__) + '/em-http/http_options'
20
+ require File.dirname(__FILE__) + '/em-http/mock'
@@ -23,12 +23,11 @@ module EventMachine
23
23
 
24
24
  # E-Tag
25
25
  def etag
26
- self["ETag"]
26
+ self[HttpClient::ETAG]
27
27
  end
28
28
 
29
29
  def last_modified
30
- time = self["Last-Modified"]
31
- Time.parse(time) if time
30
+ self[HttpClient::LAST_MODIFIED]
32
31
  end
33
32
 
34
33
  # HTTP response status as an integer
@@ -84,7 +83,7 @@ module EventMachine
84
83
  # Escapes a URI.
85
84
  def escape(s)
86
85
  s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
87
- '%'+$1.unpack('H2'*$1.bytesize).join('%').upcase
86
+ '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
88
87
  }.tr(' ', '+')
89
88
  end
90
89
 
@@ -95,6 +94,16 @@ module EventMachine
95
94
  }
96
95
  end
97
96
 
97
+ if ''.respond_to?(:bytesize)
98
+ def bytesize(string)
99
+ string.bytesize
100
+ end
101
+ else
102
+ def bytesize(string)
103
+ string.size
104
+ end
105
+ end
106
+
98
107
  # Map all header keys to a downcased string version
99
108
  def munge_header_keys(head)
100
109
  head.inject({}) { |h, (k, v)| h[k.to_s.downcase] = v; h }
@@ -111,21 +120,27 @@ module EventMachine
111
120
  end
112
121
  end
113
122
 
114
- def encode_request(method, path, query, uri_query)
115
- HTTP_REQUEST_HEADER % [method.to_s.upcase, encode_query(path, query, uri_query)]
123
+ def encode_request(method, uri, query, proxy)
124
+ query = encode_query(uri, query)
125
+
126
+ # Non CONNECT proxies require that you provide the full request
127
+ # uri in request header, as opposed to a relative path.
128
+ query = uri.join(query) if proxy and not proxy[:use_connect]
129
+
130
+ HTTP_REQUEST_HEADER % [method.to_s.upcase, query]
116
131
  end
117
132
 
118
- def encode_query(path, query, uri_query)
133
+ def encode_query(uri, query)
119
134
  encoded_query = if query.kind_of?(Hash)
120
135
  query.map { |k, v| encode_param(k, v) }.join('&')
121
136
  else
122
137
  query.to_s
123
138
  end
124
- if !uri_query.to_s.empty?
125
- encoded_query = [encoded_query, uri_query].reject {|part| part.empty?}.join("&")
139
+
140
+ if !uri.query.to_s.empty?
141
+ encoded_query = [encoded_query, uri.query].reject {|part| part.empty?}.join("&")
126
142
  end
127
- return path if encoded_query.to_s.empty?
128
- "#{path}?#{encoded_query}"
143
+ encoded_query.to_s.empty? ? uri.path : "#{uri.path}?#{encoded_query}"
129
144
  end
130
145
 
131
146
  # URL encodes query parameters:
@@ -183,10 +198,13 @@ module EventMachine
183
198
  TRANSFER_ENCODING="TRANSFER_ENCODING"
184
199
  CONTENT_ENCODING="CONTENT_ENCODING"
185
200
  CONTENT_LENGTH="CONTENT_LENGTH"
201
+ LAST_MODIFIED="LAST_MODIFIED"
186
202
  KEEP_ALIVE="CONNECTION"
187
203
  SET_COOKIE="SET_COOKIE"
188
204
  LOCATION="LOCATION"
189
205
  HOST="HOST"
206
+ ETAG="ETAG"
207
+
190
208
  CRLF="\r\n"
191
209
 
192
210
  attr_accessor :method, :options, :uri
@@ -210,10 +228,10 @@ module EventMachine
210
228
 
211
229
  # start HTTP request once we establish connection to host
212
230
  def connection_completed
213
- # if connecting to proxy, then first negotiate the connection
214
- # to intermediate server and wait for 200 response
215
- if @options[:proxy] and @state == :response_header
216
- @state = :response_proxy
231
+ # if we need to negotiate the proxy connection first, then
232
+ # issue a CONNECT query and wait for 200 response
233
+ if connect_proxy? and @state == :response_header
234
+ @state = :connect_proxy
217
235
  send_request_header
218
236
 
219
237
  # if connecting via proxy, then state will be :proxy_connected,
@@ -283,23 +301,32 @@ module EventMachine
283
301
  end
284
302
 
285
303
  def websocket?; @uri.scheme == 'ws'; end
304
+ def proxy?; !@options[:proxy].nil?; end
305
+ def connect_proxy?; proxy? && (@options[:proxy][:use_connect] == true); end
286
306
 
287
307
  def send_request_header
288
308
  query = @options[:query]
289
309
  head = @options[:head] ? munge_header_keys(@options[:head]) : {}
290
310
  file = @options[:file]
311
+ proxy = @options[:proxy]
291
312
  body = normalize_body
292
- request_header = nil
293
313
 
294
- if @state == :response_proxy
295
- proxy = @options[:proxy]
314
+ request_header = nil
296
315
 
297
- # initialize headers to establish the HTTP tunnel
316
+ if proxy
317
+ # initialize headers for the http proxy
298
318
  head = proxy[:head] ? munge_header_keys(proxy[:head]) : {}
299
319
  head['proxy-authorization'] = proxy[:authorization] if proxy[:authorization]
300
- request_header = HTTP_REQUEST_HEADER % ['CONNECT', "#{@uri.host}:#{@uri.port}"]
301
320
 
302
- elsif websocket?
321
+ # if we need to negotiate the tunnel connection first, then
322
+ # issue a CONNECT query to the proxy first. This is an optional
323
+ # flag, by default we will provide full URIs to the proxy
324
+ if @state == :connect_proxy
325
+ request_header = HTTP_REQUEST_HEADER % ['CONNECT', "#{@uri.host}:#{@uri.port}"]
326
+ end
327
+ end
328
+
329
+ if websocket?
303
330
  head['upgrade'] = 'WebSocket'
304
331
  head['connection'] = 'Upgrade'
305
332
  head['origin'] = @options[:origin] || @uri.host
@@ -332,7 +359,7 @@ module EventMachine
332
359
  @last_effective_url = @uri
333
360
 
334
361
  # Build the request headers
335
- request_header ||= encode_request(@method, @uri.path, query, @uri.query)
362
+ request_header ||= encode_request(@method, @uri, query, proxy)
336
363
  request_header << encode_headers(head)
337
364
  request_header << CRLF
338
365
  send_data request_header
@@ -388,6 +415,7 @@ module EventMachine
388
415
 
389
416
  @response_header = HttpResponseHeader.new
390
417
  @state = :response_header
418
+ @response = ''
391
419
  @data.clear
392
420
  else
393
421
  if @state == :finished || (@state == :body && @bytes_remaining.nil?)
@@ -405,7 +433,7 @@ module EventMachine
405
433
 
406
434
  def dispatch
407
435
  while case @state
408
- when :response_proxy
436
+ when :connect_proxy
409
437
  parse_response_header
410
438
  when :response_header
411
439
  parse_response_header
@@ -457,7 +485,7 @@ module EventMachine
457
485
  return false
458
486
  end
459
487
 
460
- if @state == :response_proxy
488
+ if @state == :connect_proxy
461
489
  # when a successfull tunnel is established, the proxy responds with a
462
490
  # 200 response code. from here, the tunnel is transparent.
463
491
  if @response_header.http_status.to_i == 200
@@ -475,9 +503,13 @@ module EventMachine
475
503
  if @response_header.location
476
504
  begin
477
505
  location = Addressable::URI.parse(@response_header.location)
506
+
478
507
  if location.relative?
479
508
  location = @uri.join(location)
480
509
  @response_header[LOCATION] = location.to_s
510
+ else
511
+ # if redirect is to an absolute url, check for correct URI structure
512
+ raise if location.host.nil?
481
513
  end
482
514
 
483
515
  # store last url on any sign of redirect
@@ -628,8 +660,8 @@ module EventMachine
628
660
  # slice the message out of the buffer and pass in
629
661
  # for processing, and buffer data otherwise
630
662
  buffer = @data.read
631
- while msg = buffer.slice!(/\000([^\377]*)\377/)
632
- msg.gsub!(/^\x00|\xff$/, '')
663
+ while msg = buffer.slice!(/\000([^\377]*)\377/n)
664
+ msg.gsub!(/\A\x00|\xff\z/n, '')
633
665
  @stream.call(msg)
634
666
  end
635
667