em-http-request 0.2.10 → 0.2.11

Sign up to get free protection for your applications and to get access to all the features.

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