#define PERL_NO_GET_CONTEXT #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #define NEED_newRV_noinc #define NEED_newSVpvn_flags #define NEED_sv_2pv_flags #include "ppport.h" #include "picohttpparser/picohttpparser.c" #ifndef STATIC_INLINE /* a public perl API from 5.13.4 */ # if defined(__GNUC__) || defined(__cplusplus) || (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) # define STATIC_INLINE static inline # else # define STATIC_INLINE static # endif #endif /* STATIC_INLINE */ #define MAX_HEADER_NAME_LEN 1024 #define MAX_HEADERS 128 #define HEADERS_NONE 0 #define HEADERS_AS_HASHREF 1 #define HEADERS_AS_ARRAYREF 2 STATIC_INLINE char tou(char ch) { if ('a' <= ch && ch <= 'z') ch -= 'a' - 'A'; return ch; } STATIC_INLINE char tol(char const ch) { return ('A' <= ch && ch <= 'Z') ? ch - ('A' - 'a') : ch; } /* copy src to dest with normalization. dest must have enough size for src */ STATIC_INLINE void normalize_response_header_name(pTHX_ char* const dest, const char* const src, STRLEN const len) { STRLEN i; for(i = 0; i < len; i++) { dest[i] = tol(src[i]); } } STATIC_INLINE void concat_multiline_header(pTHX_ SV * val, const char * const cont, size_t const cont_len) { sv_catpvs(val, "\n"); /* XXX: is it collect? */ sv_catpvn(val, cont, cont_len); } static int header_is(const struct phr_header* header, const char* name, size_t len) { const char* x, * y; if (header->name_len != len) return 0; for (x = header->name, y = name; len != 0; --len, ++x, ++y) if (tou(*x) != *y) return 0; return 1; } static size_t find_ch(const char* s, size_t len, char ch) { size_t i; for (i = 0; i != len; ++i, ++s) if (*s == ch) break; return i; } STATIC_INLINE int hex_decode(const char ch) { int r; if ('0' <= ch && ch <= '9') r = ch - '0'; else if ('A' <= ch && ch <= 'F') r = ch - 'A' + 0xa; else if ('a' <= ch && ch <= 'f') r = ch - 'a' + 0xa; else r = -1; return r; } static char* url_decode(const char* s, size_t len) { dTHX; char* dbuf, * d; size_t i; for (i = 0; i < len; ++i) if (s[i] == '%') goto NEEDS_DECODE; return (char*)s; NEEDS_DECODE: dbuf = (char*)malloc(len - 1); assert(dbuf != NULL); memcpy(dbuf, s, i); d = dbuf + i; while (i < len) { if (s[i] == '%') { int hi, lo; if ((hi = hex_decode(s[i + 1])) == -1 || (lo = hex_decode(s[i + 2])) == -1) { free(dbuf); return NULL; } *d++ = hi * 16 + lo; i += 3; } else *d++ = s[i++]; } *d = '\0'; return dbuf; } STATIC_INLINE int store_url_decoded(HV* env, const char* name, size_t name_len, const char* value, size_t value_len) { dTHX; char* decoded = url_decode(value, value_len); if (decoded == NULL) return -1; if (decoded == value) hv_store(env, name, name_len, newSVpvn(value, value_len), 0); else { hv_store(env, name, name_len, newSVpv(decoded, 0), 0); free(decoded); } return 0; } MODULE = HTTP::Parser::XS PACKAGE = HTTP::Parser::XS int parse_http_request(SV* buf, SV* envref) PROTOTYPE: $$ CODE: { const char* buf_str; STRLEN buf_len; const char* method; size_t method_len; const char* path; size_t path_len; int minor_version; struct phr_header headers[MAX_HEADERS]; size_t num_headers, question_at; size_t i; int ret; HV* env; SV* last_value; char tmp[MAX_HEADER_NAME_LEN + sizeof("HTTP_") - 1]; buf_str = SvPV(buf, buf_len); num_headers = MAX_HEADERS; ret = phr_parse_request(buf_str, buf_len, &method, &method_len, &path, &path_len, &minor_version, headers, &num_headers, 0); if (ret < 0) goto done; if (!SvROK(envref)) Perl_croak(aTHX_ "second param to parse_http_request should be a hashref"); env = (HV*)SvRV(envref); if (SvTYPE(env) != SVt_PVHV) Perl_croak(aTHX_ "second param to parse_http_request should be a hashref"); hv_store(env, "REQUEST_METHOD", sizeof("REQUEST_METHOD") - 1, newSVpvn(method, method_len), 0); hv_store(env, "REQUEST_URI", sizeof("REQUEST_URI") - 1, newSVpvn(path, path_len), 0); hv_store(env, "SCRIPT_NAME", sizeof("SCRIPT_NAME") - 1, newSVpvn("", 0), 0); path_len = find_ch(path, path_len, '#'); /* strip off all text after # after storing request_uri */ question_at = find_ch(path, path_len, '?'); if (store_url_decoded(env, "PATH_INFO", sizeof("PATH_INFO") - 1, path, question_at) != 0) { hv_clear(env); ret = -1; goto done; } if (question_at != path_len) ++question_at; hv_store(env, "QUERY_STRING", sizeof("QUERY_STRING") - 1, newSVpvn(path + question_at, path_len - question_at), 0); sprintf(tmp, "HTTP/1.%d", minor_version); hv_store(env, "SERVER_PROTOCOL", sizeof("SERVER_PROTOCOL") - 1, newSVpv(tmp, 0), 0); last_value = NULL; for (i = 0; i < num_headers; ++i) { if (headers[i].name != NULL) { const char* name; size_t name_len; SV** slot; if (header_is(headers + i, "CONTENT-TYPE", sizeof("CONTENT-TYPE") - 1)) { name = "CONTENT_TYPE"; name_len = sizeof("CONTENT_TYPE") - 1; } else if (header_is(headers + i, "CONTENT-LENGTH", sizeof("CONTENT-LENGTH") - 1)) { name = "CONTENT_LENGTH"; name_len = sizeof("CONTENT_LENGTH") - 1; } else { const char* s; char* d; size_t n; if (sizeof(tmp) - 5 < headers[i].name_len) { hv_clear(env); ret = -1; goto done; } strcpy(tmp, "HTTP_"); for (s = headers[i].name, n = headers[i].name_len, d = tmp + 5; n != 0; s++, --n, d++) *d = *s == '-' ? '_' : tou(*s); name = tmp; name_len = headers[i].name_len + 5; } slot = hv_fetch(env, name, name_len, 1); if ( !slot ) croak("failed to create hash entry"); if (SvOK(*slot)) { sv_catpvn(*slot, ", ", 2); sv_catpvn(*slot, headers[i].value, headers[i].value_len); } else sv_setpvn(*slot, headers[i].value, headers[i].value_len); last_value = *slot; } else { /* continuing lines of a mulitiline header */ sv_catpvn(last_value, headers[i].value, headers[i].value_len); } } done: RETVAL = ret; } OUTPUT: RETVAL void parse_http_response(SV* buf, int header_format, HV* special_headers = NULL) PPCODE: { int minor_version, status; const char* msg; size_t msg_len; struct phr_header headers[MAX_HEADERS]; size_t num_headers = MAX_HEADERS; STRLEN buf_len; const char* const buf_str = SvPV_const(buf, buf_len); size_t last_len = 0; int const ret = phr_parse_response(buf_str, buf_len, &minor_version, &status, &msg, &msg_len, headers, &num_headers, last_len); SV* last_special_headers_value_sv = NULL; SV* last_element_value_sv = NULL; size_t i; SV *res_headers; char name[MAX_HEADER_NAME_LEN]; /* temp buffer for normalized names */ if (header_format == HEADERS_AS_HASHREF) { res_headers = sv_2mortal((SV*)newHV()); } else if (header_format == HEADERS_AS_ARRAYREF) { res_headers = sv_2mortal((SV*)newAV()); av_extend((AV*)res_headers, (num_headers * 2) - 1); } else if (header_format == HEADERS_NONE) { res_headers = NULL; } for (i = 0; i < num_headers; i++) { struct phr_header const h = headers[i]; if (h.name != NULL) { SV* namesv; SV* valuesv; if(h.name_len > sizeof(name)) { /* skip if name_len is too long */ continue; } normalize_response_header_name(aTHX_ name, h.name, h.name_len); if(special_headers) { SV** const slot = hv_fetch(special_headers, name, h.name_len, FALSE); if (slot) { SV* const hash_value = *slot; sv_setpvn_mg(hash_value, h.value, h.value_len); last_special_headers_value_sv = hash_value; } else { last_special_headers_value_sv = NULL; } } if(header_format == HEADERS_NONE) { continue; } namesv = sv_2mortal(newSVpvn_share(name, h.name_len, 0U)); valuesv = newSVpvn_flags( h.value, h.value_len, SVs_TEMP); if (header_format == HEADERS_AS_HASHREF) { HE* const slot = hv_fetch_ent((HV*)res_headers, namesv, FALSE, 0U); if(!slot) { /* first time */ (void)hv_store_ent((HV*)res_headers, namesv, SvREFCNT_inc_simple_NN(valuesv), 0U); } else { /* second time; the header has multiple values */ SV* sv = hv_iterval((HV*)res_headers, slot); if(!( SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV )) { /* make $value to [$value] and restore it to $res_header */ AV* const av = newAV(); SV* const avref = newRV_noinc((SV*)av); (void)av_store(av, 0, SvREFCNT_inc_simple_NN(sv)); (void)hv_store_ent((HV*)res_headers, namesv, avref, 0U); sv = avref; } av_push((AV*)SvRV(sv), SvREFCNT_inc_simple_NN(valuesv)); } last_element_value_sv = valuesv; } else if (header_format == HEADERS_AS_ARRAYREF) { av_push((AV*)res_headers, SvREFCNT_inc_simple_NN(namesv)); av_push((AV*)res_headers, SvREFCNT_inc_simple_NN(valuesv)); last_element_value_sv = valuesv; } } else { /* continuing lines of a mulitiline header */ if (special_headers && last_special_headers_value_sv) { concat_multiline_header(aTHX_ last_special_headers_value_sv, h.value, h.value_len); } if ((header_format == HEADERS_AS_HASHREF || header_format == HEADERS_AS_ARRAYREF) && last_element_value_sv) { concat_multiline_header(aTHX_ last_element_value_sv, h.value, h.value_len); } } } if(ret > 0) { EXTEND(SP, 5); mPUSHi(ret); mPUSHi(minor_version); mPUSHi(status); mPUSHp(msg, msg_len); if (res_headers) { mPUSHs(newRV_inc(res_headers)); } else { mPUSHs(&PL_sv_undef); } } else { EXTEND(SP, 1); mPUSHi(ret); } }