File Coverage

blib/lib/Hypersonic/UA/Protocol/HTTP1.pm
Criterion Covered Total %
statement 15 44 34.0
branch n/a
condition n/a
subroutine 6 15 40.0
pod 0 10 0.0
total 21 69 30.4


line stmt bran cond sub pod time code
1             package Hypersonic::UA::Protocol::HTTP1;
2              
3 1     1   212613 use strict;
  1         2  
  1         29  
4 1     1   3 use warnings;
  1         1  
  1         45  
5 1     1   13 use 5.010;
  1         3  
6              
7             our $VERSION = '0.15';
8              
9 1     1   6 use constant MAX_HEADER_SIZE => 16384;
  1         1  
  1         48  
10 1     1   3 use constant DECODE_BUF_SIZE => 1048576;
  1         1  
  1         1248  
11              
12             sub generate_c_code {
13 0     0 0 0 my ($class, $builder, $opts) = @_;
14              
15 0         0 $class->gen_helpers($builder);
16 0         0 $class->gen_xs_build_request($builder);
17 0         0 $class->gen_xs_parse_status_line($builder);
18 0         0 $class->gen_xs_parse_headers($builder);
19 0         0 $class->gen_xs_find_body_start($builder);
20 0         0 $class->gen_xs_get_content_length($builder);
21 0         0 $class->gen_xs_decode_chunked($builder);
22 0         0 $class->gen_xs_parse_response($builder);
23             }
24              
25             sub get_xs_functions {
26             return {
27 1     1 0 1388 'Hypersonic::UA::Protocol::HTTP1::build_request' => { source => 'xs_http1_build_request', is_xs_native => 1 },
28             'Hypersonic::UA::Protocol::HTTP1::parse_status_line' => { source => 'xs_http1_parse_status_line', is_xs_native => 1 },
29             'Hypersonic::UA::Protocol::HTTP1::parse_headers' => { source => 'xs_http1_parse_headers', is_xs_native => 1 },
30             'Hypersonic::UA::Protocol::HTTP1::find_body_start' => { source => 'xs_http1_find_body_start', is_xs_native => 1 },
31             'Hypersonic::UA::Protocol::HTTP1::get_content_length' => { source => 'xs_http1_get_content_length', is_xs_native => 1 },
32             'Hypersonic::UA::Protocol::HTTP1::decode_chunked' => { source => 'xs_http1_decode_chunked', is_xs_native => 1 },
33             'Hypersonic::UA::Protocol::HTTP1::parse_response' => { source => 'xs_http1_parse_response', is_xs_native => 1 },
34             };
35             }
36              
37             sub gen_helpers {
38 0     0 0   my ($class, $builder) = @_;
39              
40 0           $builder->line("#define HTTP1_MAX_HEADER_SIZE " . MAX_HEADER_SIZE)
41             ->line("#define HTTP1_DECODE_BUF_SIZE " . DECODE_BUF_SIZE)
42             ->blank
43             ->line("static char g_http1_decode_buf[HTTP1_DECODE_BUF_SIZE];")
44             ->blank;
45              
46 0           $builder->comment('Parse HTTP/1.x response status line')
47             ->comment('Returns: status code, or -1 on error')
48             ->line('static int http1_parse_status_line(const char* buf, size_t len, int* http_minor) {')
49             ->line(' int i;')
50             ->line(' if (len < 12) return -1;')
51             ->line(' if (memcmp(buf, "HTTP/1.", 7) != 0) return -1;')
52             ->blank
53             ->line(' *http_minor = buf[7] - \'0\';')
54             ->line(' if (buf[8] != \' \') return -1;')
55             ->blank
56             ->line(' int status = 0;')
57             ->line(' for (i = 9; i < 12; i++) {')
58             ->line(' if (buf[i] < \'0\' || buf[i] > \'9\') return -1;')
59             ->line(' status = status * 10 + (buf[i] - \'0\');')
60             ->line(' }')
61             ->line(' return status;')
62             ->line('}')
63             ->blank;
64              
65 0           $builder->comment('Find body start (after \\r\\n\\r\\n)')
66             ->line('static const char* http1_find_body_start(const char* buf, size_t len) {')
67             ->line(' if (len < 4) return NULL;')
68             ->line(' const char* end = buf + len - 3;')
69             ->line(' for (const char* p = buf; p < end; p++) {')
70             ->line(' if (p[0] == \'\\r\' && p[1] == \'\\n\' && p[2] == \'\\r\' && p[3] == \'\\n\') {')
71             ->line(' return p + 4;')
72             ->line(' }')
73             ->line(' }')
74             ->line(' return NULL;')
75             ->line('}')
76             ->blank;
77              
78 0           $builder->comment('Parse response headers into Perl HV')
79             ->line('static HV* http1_parse_headers_into_hv(const char* buf, size_t len) {')
80             ->line(' HV* headers = newHV();')
81             ->line(' const char* p = buf;')
82             ->line(' const char* end = buf + len;')
83             ->blank
84             ->line(' while (p < end && *p != \'\\n\') p++;')
85             ->line(' if (p < end) p++;')
86             ->blank
87             ->line(' while (p < end - 1) {')
88             ->line(' if (p[0] == \'\\r\' && p[1] == \'\\n\') break;')
89             ->line(' if (p[0] == \'\\n\') break;')
90             ->blank
91             ->line(' const char* colon = p;')
92             ->line(' while (colon < end && *colon != \':\' && *colon != \'\\r\') colon++;')
93             ->line(' if (colon >= end || *colon != \':\') break;')
94             ->blank
95             ->line(' int name_len = colon - p;')
96             ->blank
97             ->line(' const char* value = colon + 1;')
98             ->line(' while (value < end && *value == \' \') value++;')
99             ->blank
100             ->line(' const char* vend = value;')
101             ->line(' while (vend < end && *vend != \'\\r\' && *vend != \'\\n\') vend++;')
102             ->blank
103             ->line(' int i;')
104             ->line(' char norm_name[256];')
105             ->line(' if (name_len > 255) name_len = 255;')
106             ->line(' for (i = 0; i < name_len; i++) {')
107             ->line(' char c = p[i];')
108             ->line(' if (c >= \'A\' && c <= \'Z\') c += 32;')
109             ->line(' else if (c == \'-\') c = \'_\';')
110             ->line(' norm_name[i] = c;')
111             ->line(' }')
112             ->blank
113             ->line(' hv_store(headers, norm_name, name_len, newSVpvn(value, vend - value), 0);')
114             ->blank
115             ->line(' p = vend;')
116             ->line(' if (p < end && *p == \'\\r\') p++;')
117             ->line(' if (p < end && *p == \'\\n\') p++;')
118             ->line(' }')
119             ->blank
120             ->line(' return headers;')
121             ->line('}')
122             ->blank;
123              
124 0           $builder->comment('Decode chunked transfer encoding')
125             ->line('static ssize_t http1_decode_chunked_data(const char* input, size_t input_len, char* output, size_t output_size) {')
126             ->line(' const char* p = input;')
127             ->line(' const char* end = input + input_len;')
128             ->line(' size_t out_pos = 0;')
129             ->blank
130             ->line(' while (p < end) {')
131             ->line(' size_t chunk_size = 0;')
132             ->line(' while (p < end && *p != \'\\r\' && *p != \'\\n\') {')
133             ->line(' char c = *p;')
134             ->line(' if (c >= \'0\' && c <= \'9\') chunk_size = chunk_size * 16 + (c - \'0\');')
135             ->line(' else if (c >= \'a\' && c <= \'f\') chunk_size = chunk_size * 16 + (c - \'a\' + 10);')
136             ->line(' else if (c >= \'A\' && c <= \'F\') chunk_size = chunk_size * 16 + (c - \'A\' + 10);')
137             ->line(' else if (c == \';\') break;')
138             ->line(' else break;')
139             ->line(' p++;')
140             ->line(' }')
141             ->blank
142             ->line(' while (p < end && *p != \'\\r\' && *p != \'\\n\') p++;')
143             ->line(' if (p < end && *p == \'\\r\') p++;')
144             ->line(' if (p < end && *p == \'\\n\') p++;')
145             ->blank
146             ->line(' if (chunk_size == 0) {')
147             ->line(' while (p < end - 1 && !(p[0] == \'\\r\' && p[1] == \'\\n\')) {')
148             ->line(' while (p < end && *p != \'\\n\') p++;')
149             ->line(' if (p < end) p++;')
150             ->line(' }')
151             ->line(' return (ssize_t)out_pos;')
152             ->line(' }')
153             ->blank
154             ->line(' if (p + chunk_size + 2 > end) return -1;')
155             ->line(' if (out_pos + chunk_size > output_size) return -2;')
156             ->blank
157             ->line(' memcpy(output + out_pos, p, chunk_size);')
158             ->line(' out_pos += chunk_size;')
159             ->line(' p += chunk_size;')
160             ->blank
161             ->line(' if (p < end && *p == \'\\r\') p++;')
162             ->line(' if (p < end && *p == \'\\n\') p++;')
163             ->line(' }')
164             ->blank
165             ->line(' return -1;')
166             ->line('}')
167             ->blank;
168             }
169              
170             sub gen_xs_build_request {
171 0     0 0   my ($class, $builder) = @_;
172              
173 0           $builder->comment('Build HTTP/1.1 request string')
174             ->xs_function('xs_http1_build_request')
175             ->xs_preamble
176             ->line('if (items < 4) croak("Usage: build_request(method, path, host, headers_hv, [body])");')
177             ->blank
178             ->line('STRLEN method_len, path_len, host_len;')
179             ->line('const char* method = SvPV(ST(0), method_len);')
180             ->line('const char* path = SvPV(ST(1), path_len);')
181             ->line('const char* host = SvPV(ST(2), host_len);')
182             ->line('HV* headers = (HV*)SvRV(ST(3));')
183             ->line('SV* body_sv = (items > 4) ? ST(4) : NULL;')
184             ->blank
185             ->line('size_t request_size = method_len + 1 + path_len + 12 + 6 + host_len + 2;')
186             ->blank
187             ->line('hv_iterinit(headers);')
188             ->line('HE* entry;')
189             ->line('while ((entry = hv_iternext(headers)) != NULL) {')
190             ->line(' SV* key_sv = hv_iterkeysv(entry);')
191             ->line(' SV* val_sv = hv_iterval(headers, entry);')
192             ->line(' STRLEN key_len, val_len;')
193             ->line(' SvPV(key_sv, key_len);')
194             ->line(' SvPV(val_sv, val_len);')
195             ->line(' request_size += key_len + 2 + val_len + 2;')
196             ->line('}')
197             ->blank
198             ->line('STRLEN body_len = 0;')
199             ->if('body_sv && SvOK(body_sv)')
200             ->line('SvPV(body_sv, body_len);')
201             ->line('request_size += 20 + body_len;')
202             ->endif
203             ->line('request_size += 2;')
204             ->blank
205             ->line('SV* request = newSV(request_size);')
206             ->line('SvPOK_on(request);')
207             ->line('char* rp = SvPVX(request);')
208             ->blank
209             ->line('memcpy(rp, method, method_len); rp += method_len;')
210             ->line('*rp++ = \' \';')
211             ->line('memcpy(rp, path, path_len); rp += path_len;')
212             ->line('memcpy(rp, " HTTP/1.1\\r\\n", 11); rp += 11;')
213             ->blank
214             ->line('memcpy(rp, "Host: ", 6); rp += 6;')
215             ->line('memcpy(rp, host, host_len); rp += host_len;')
216             ->line('*rp++ = \'\\r\'; *rp++ = \'\\n\';')
217             ->blank
218             ->line('hv_iterinit(headers);')
219             ->line('while ((entry = hv_iternext(headers)) != NULL) {')
220             ->line(' SV* key_sv = hv_iterkeysv(entry);')
221             ->line(' SV* val_sv = hv_iterval(headers, entry);')
222             ->line(' STRLEN key_len, val_len;')
223             ->line(' const char* key = SvPV(key_sv, key_len);')
224             ->line(' const char* val = SvPV(val_sv, val_len);')
225             ->line(' memcpy(rp, key, key_len); rp += key_len;')
226             ->line(' *rp++ = \':\'; *rp++ = \' \';')
227             ->line(' memcpy(rp, val, val_len); rp += val_len;')
228             ->line(' *rp++ = \'\\r\'; *rp++ = \'\\n\';')
229             ->line('}')
230             ->blank
231             ->if('body_len > 0')
232             ->line('rp += sprintf(rp, "Content-Length: %zu\\r\\n", body_len);')
233             ->endif
234             ->blank
235             ->line('*rp++ = \'\\r\'; *rp++ = \'\\n\';')
236             ->if('body_len > 0')
237             ->line('const char* body = SvPV_nolen(body_sv);')
238             ->line('memcpy(rp, body, body_len); rp += body_len;')
239             ->endif
240             ->blank
241             ->line('SvCUR_set(request, rp - SvPVX(request));')
242             ->line('ST(0) = sv_2mortal(request);')
243             ->xs_return('1')
244             ->xs_end
245             ->blank;
246             }
247              
248             sub gen_xs_parse_status_line {
249 0     0 0   my ($class, $builder) = @_;
250              
251 0           $builder->comment('Parse status line, return status code')
252             ->xs_function('xs_http1_parse_status_line')
253             ->xs_preamble
254             ->line('if (items != 1) croak("Usage: parse_status_line(raw)");')
255             ->blank
256             ->line('STRLEN raw_len;')
257             ->line('const char* raw = SvPV(ST(0), raw_len);')
258             ->line('int http_minor;')
259             ->line('int status = http1_parse_status_line(raw, raw_len, &http_minor);')
260             ->blank
261             ->line('ST(0) = sv_2mortal(newSViv(status));')
262             ->xs_return('1')
263             ->xs_end
264             ->blank;
265             }
266              
267             sub gen_xs_parse_headers {
268 0     0 0   my ($class, $builder) = @_;
269              
270 0           $builder->comment('Parse headers into hashref')
271             ->xs_function('xs_http1_parse_headers')
272             ->xs_preamble
273             ->line('if (items != 1) croak("Usage: parse_headers(raw)");')
274             ->blank
275             ->line('STRLEN raw_len;')
276             ->line('const char* raw = SvPV(ST(0), raw_len);')
277             ->blank
278             ->line('const char* body_start = http1_find_body_start(raw, raw_len);')
279             ->line('size_t headers_len = body_start ? (body_start - raw) : raw_len;')
280             ->blank
281             ->line('HV* headers = http1_parse_headers_into_hv(raw, headers_len);')
282             ->line('ST(0) = sv_2mortal(newRV_noinc((SV*)headers));')
283             ->xs_return('1')
284             ->xs_end
285             ->blank;
286             }
287              
288             sub gen_xs_find_body_start {
289 0     0 0   my ($class, $builder) = @_;
290              
291 0           $builder->comment('Find offset of body start')
292             ->xs_function('xs_http1_find_body_start')
293             ->xs_preamble
294             ->line('if (items != 1) croak("Usage: find_body_start(raw)");')
295             ->blank
296             ->line('STRLEN raw_len;')
297             ->line('const char* raw = SvPV(ST(0), raw_len);')
298             ->blank
299             ->line('const char* body = http1_find_body_start(raw, raw_len);')
300             ->if('body')
301             ->line('ST(0) = sv_2mortal(newSViv(body - raw));')
302             ->else
303             ->line('ST(0) = sv_2mortal(newSViv(-1));')
304             ->endif
305             ->xs_return('1')
306             ->xs_end
307             ->blank;
308             }
309              
310             sub gen_xs_get_content_length {
311 0     0 0   my ($class, $builder) = @_;
312              
313 0           $builder->comment('Extract Content-Length from headers')
314             ->xs_function('xs_http1_get_content_length')
315             ->xs_preamble
316             ->line('if (items != 1) croak("Usage: get_content_length(headers_str)");')
317             ->blank
318             ->line('STRLEN len;')
319             ->line('const char* headers = SvPV(ST(0), len);')
320             ->blank
321             ->line('const char* cl = strcasestr(headers, "\\r\\nContent-Length:");')
322             ->line('if (!cl) cl = strcasestr(headers, "\\nContent-Length:");')
323             ->if('!cl')
324             ->line('ST(0) = sv_2mortal(newSViv(-1));')
325             ->line('XSRETURN(1);')
326             ->endif
327             ->blank
328             ->line('cl += (cl[0] == \'\\r\') ? 17 : 16;')
329             ->line('while (*cl == \' \') cl++;')
330             ->blank
331             ->line('ssize_t length = 0;')
332             ->line('while (*cl >= \'0\' && *cl <= \'9\') {')
333             ->line(' length = length * 10 + (*cl - \'0\');')
334             ->line(' cl++;')
335             ->line('}')
336             ->line('ST(0) = sv_2mortal(newSViv(length));')
337             ->xs_return('1')
338             ->xs_end
339             ->blank;
340             }
341              
342             sub gen_xs_decode_chunked {
343 0     0 0   my ($class, $builder) = @_;
344              
345 0           $builder->comment('Decode chunked transfer encoding')
346             ->xs_function('xs_http1_decode_chunked')
347             ->xs_preamble
348             ->line('if (items != 1) croak("Usage: decode_chunked(body)");')
349             ->blank
350             ->line('STRLEN input_len;')
351             ->line('const char* input = SvPV(ST(0), input_len);')
352             ->blank
353             ->line('ssize_t decoded_len = http1_decode_chunked_data(input, input_len, g_http1_decode_buf, HTTP1_DECODE_BUF_SIZE);')
354             ->if('decoded_len >= 0')
355             ->line('ST(0) = sv_2mortal(newSVpvn(g_http1_decode_buf, decoded_len));')
356             ->else
357             ->line('ST(0) = &PL_sv_undef;')
358             ->endif
359             ->xs_return('1')
360             ->xs_end
361             ->blank;
362             }
363              
364             sub gen_xs_parse_response {
365 0     0 0   my ($class, $builder) = @_;
366              
367 0           $builder->comment('Parse raw HTTP response into [status, headers_hv, body, raw_headers]')
368             ->xs_function('xs_http1_parse_response')
369             ->xs_preamble
370             ->line('if (items != 1) croak("Usage: parse_response(raw_response)");')
371             ->blank
372             ->line('STRLEN raw_len;')
373             ->line('const char* raw = SvPV(ST(0), raw_len);')
374             ->blank
375             ->line('int http_minor;')
376             ->line('int status = http1_parse_status_line(raw, raw_len, &http_minor);')
377             ->if('status < 0')
378             ->line('ST(0) = &PL_sv_undef;')
379             ->line('XSRETURN(1);')
380             ->endif
381             ->blank
382             ->line('const char* body_start = http1_find_body_start(raw, raw_len);')
383             ->if('!body_start')
384             ->line('ST(0) = &PL_sv_undef;')
385             ->line('XSRETURN(1);')
386             ->endif
387             ->blank
388             ->line('size_t headers_len = body_start - raw;')
389             ->line('size_t body_len = raw_len - headers_len;')
390             ->blank
391             ->line('HV* headers = http1_parse_headers_into_hv(raw, headers_len);')
392             ->blank
393             ->line('SV** te = hv_fetch(headers, "transfer_encoding", 17, 0);')
394             ->line('int chunked = (te && *te && strstr(SvPV_nolen(*te), "chunked"));')
395             ->blank
396             ->line('SV* body_sv;')
397             ->if('chunked')
398             ->line('ssize_t decoded_len = http1_decode_chunked_data(body_start, body_len, g_http1_decode_buf, HTTP1_DECODE_BUF_SIZE);')
399             ->if('decoded_len >= 0')
400             ->line('body_sv = newSVpvn(g_http1_decode_buf, decoded_len);')
401             ->else
402             ->line('body_sv = newSVpvn(body_start, body_len);')
403             ->endif
404             ->else
405             ->line('body_sv = newSVpvn(body_start, body_len);')
406             ->endif
407             ->blank
408             ->line('AV* result = newAV();')
409             ->line('av_push(result, newSViv(status));')
410             ->line('av_push(result, newRV_noinc((SV*)headers));')
411             ->line('av_push(result, body_sv);')
412             ->line('av_push(result, newSVpvn(raw, headers_len));')
413             ->blank
414             ->line('ST(0) = sv_2mortal(newRV_noinc((SV*)result));')
415             ->xs_return('1')
416             ->xs_end
417             ->blank;
418             }
419              
420             1;