#ifdef __cplusplus extern "C" { #endif #define PERL_NO_GET_CONTEXT /* we want efficiency */ #include #include #include #include #include #ifdef __cplusplus } /* extern "C" */ #endif #define NEED_newSVpvn_flags #include "ppport.h" #ifndef __need_IOV_MAX #define __need_IOV_MAX #endif #include #include #include #include #define _GNU_SOURCE /* See feature_test_macros(7) */ #include #include #include #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 */ #ifndef IOV_MAX #if defined(__FreeBSD__) || defined(__APPLE__) # define IOV_MAX 128 #endif #endif #ifndef IOV_MAX # error "Unable to determine IOV_MAX from system headers" #endif #define MAX_HEADER_SIZE 16384 #define MAX_HEADER_NAME_LEN 1024 #define MAX_HEADERS 128 #if defined(__OpenBSD__) #define READ_BUFSZ 16383 #else #define READ_BUFSZ 16384 #endif #define BAD_REQUEST "HTTP/1.0 400 Bad Request\r\nConnection: close\r\n\r\n400 Bad Request\r\n" #define EXPECT_CONTINUE "HTTP/1.1 100 Continue\r\n\r\n" #define EXPECT_FAILED "HTTP/1.1 417 Expectation Failed\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nExpectation Failed\r\n" #define TOU(ch) (('a' <= ch && ch <= 'z') ? ch - ('a' - 'A') : ch) static const char *DoW[] = { "Sun","Mon","Tue","Wed","Thu","Fri","Sat" }; static const char *MoY[] = { "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" }; static const char xdigit[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; static HV *env_template; /* stolen from HTTP::Status and Feersum */ /* Unmarked codes are from RFC 2616 */ /* See also: http://en.wikipedia.org/wiki/List_of_HTTP_status_codes */ static const char * status_message (int code) { switch (code) { case 100: return "Continue"; case 101: return "Switching Protocols"; case 102: return "Processing"; /* RFC 2518 (WebDAV) */ case 200: return "OK"; case 201: return "Created"; case 202: return "Accepted"; case 203: return "Non-Authoritative Information"; case 204: return "No Content"; case 205: return "Reset Content"; case 206: return "Partial Content"; case 207: return "Multi-Status"; /* RFC 2518 (WebDAV) */ case 208: return "Already Reported"; /* RFC 5842 */ case 300: return "Multiple Choices"; case 301: return "Moved Permanently"; case 302: return "Found"; case 303: return "See Other"; case 304: return "Not Modified"; case 305: return "Use Proxy"; case 307: return "Temporary Redirect"; case 400: return "Bad Request"; case 401: return "Unauthorized"; case 402: return "Payment Required"; case 403: return "Forbidden"; case 404: return "Not Found"; case 405: return "Method Not Allowed"; case 406: return "Not Acceptable"; case 407: return "Proxy Authentication Required"; case 408: return "Request Timeout"; case 409: return "Conflict"; case 410: return "Gone"; case 411: return "Length Required"; case 412: return "Precondition Failed"; case 413: return "Request Entity Too Large"; case 414: return "Request-URI Too Large"; case 415: return "Unsupported Media Type"; case 416: return "Request Range Not Satisfiable"; case 417: return "Expectation Failed"; case 418: return "I'm a teapot"; /* RFC 2324 */ case 422: return "Unprocessable Entity"; /* RFC 2518 (WebDAV) */ case 423: return "Locked"; /* RFC 2518 (WebDAV) */ case 424: return "Failed Dependency"; /* RFC 2518 (WebDAV) */ case 425: return "No code"; /* WebDAV Advanced Collections */ case 426: return "Upgrade Required"; /* RFC 2817 */ case 428: return "Precondition Required"; case 429: return "Too Many Requests"; case 431: return "Request Header Fields Too Large"; case 449: return "Retry with"; /* unofficial Microsoft */ case 500: return "Internal Server Error"; case 501: return "Not Implemented"; case 502: return "Bad Gateway"; case 503: return "Service Unavailable"; case 504: return "Gateway Timeout"; case 505: return "HTTP Version Not Supported"; case 506: return "Variant Also Negotiates"; /* RFC 2295 */ case 507: return "Insufficient Storage"; /* RFC 2518 (WebDAV) */ case 509: return "Bandwidth Limit Exceeded"; /* unofficial */ case 510: return "Not Extended"; /* RFC 2774 */ case 511: return "Network Authentication Required"; default: break; } /* default to the Nxx group names in RFC 2616 */ if (100 <= code && code <= 199) { return "Informational"; } else if (200 <= code && code <= 299) { return "Success"; } else if (300 <= code && code <= 399) { return "Redirection"; } else if (400 <= code && code <= 499) { return "Client Error"; } else { return "Error"; } } /* stolen from HTTP::Parser::XS */ 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 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_INLINE int store_path_info(pTHX_ HV* env, const char* src, size_t src_len) { size_t dlen = 0, i = 0; char *d; char s2, s3; SV * dst; dst = newSV(0); (void)SvUPGRADE(dst, SVt_PV); d = SvGROW(dst, src_len * 3 + 1); for (i = 0; i < src_len; i++ ) { if ( src[i] == '%' ) { if ( !isxdigit(src[i+1]) || !isxdigit(src[i+2]) ) { return -1; } s2 = src[i+1]; s3 = src[i+2]; s2 -= s2 <= '9' ? '0' : s2 <= 'F' ? 'A' - 10 : 'a' - 10; s3 -= s3 <= '9' ? '0' : s3 <= 'F' ? 'A' - 10 : 'a' - 10; d[dlen++] = s2 * 16 + s3; i += 2; } else { d[dlen++] = src[i]; } } SvCUR_set(dst, dlen); *SvEND(dst) = '\0'; SvPOK_only(dst); (void)hv_stores(env, "PATH_INFO", dst); return 1; } STATIC_INLINE int _parse_http_request(pTHX_ char *buf, ssize_t buf_len, HV *env) { 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 = MAX_HEADERS; size_t question_at; size_t i; int ret; SV* last_value; char tmp[MAX_HEADER_NAME_LEN + sizeof("HTTP_") - 1]; int seen_content_length = 0; int seen_transfer_encoding = 0; ret = phr_parse_request( buf, buf_len, &method, &method_len, &path, &path_len, &minor_version, headers, &num_headers, 0 ); if (ret < 0) goto done; if (minor_version > 1 || minor_version < 0 ) { ret = -1; goto done; } (void)hv_stores(env, "REQUEST_METHOD", newSVpvn(method, method_len)); (void)hv_stores(env, "REQUEST_URI", newSVpvn(path, path_len)); (void)hv_stores(env, "SCRIPT_NAME", newSVpvn("", 0)); strcpy(tmp, "HTTP/1."); tmp[sizeof("HTTP/1.")-1] = '0' + minor_version; (void)hv_stores(env, "SERVER_PROTOCOL", newSVpvn(tmp, sizeof("HTTP/1.1")-1)); /* PATH_INFO QUERY_STRING */ 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_path_info(aTHX_ env, path, question_at) < 0 ) { hv_clear(env); ret = -1; goto done; } if (question_at != path_len) ++question_at; (void)hv_stores(env, "QUERY_STRING", newSVpvn(path + question_at, path_len - question_at)); 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)) { if (seen_content_length) { /* duplicated Content_Length headers is * a potential request-smuggling attack. Reject */ ret = -1; goto done; } seen_content_length = 1; if (seen_transfer_encoding) { /* RFC 7230 §3.3.3: Transfer-Encoding overrides Content-Length; * receiving both is a potential request-smuggling attack. Reject. * env will be freed via sv_2mortal in the badexit_clear path. */ ret = -1; goto done; } name = "CONTENT_LENGTH"; name_len = sizeof("CONTENT_LENGTH") - 1; } else if (header_is(headers + i, "TRANSFER-ENCODING", sizeof("TRANSFER-ENCODING") - 1)) { if (seen_transfer_encoding ) { /* duplicated transfer-encoding headers is * a potential request-smuggling attack. Reject */ ret = -1; goto done; } seen_transfer_encoding = 1; if (seen_content_length) { /* RFC 7230 §3.3.3: Transfer-Encoding overrides Content-Length; * receiving both is a potential request-smuggling attack. Reject. * env will be freed via sv_2mortal in the badexit_clear path. */ ret = -1; goto done; } name = tmp; strcpy(tmp, "HTTP_TRANSFER_ENCODING"); name_len = sizeof("HTTP_TRANSFER_ENCODING") - 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("ERROR: 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: return ret; } STATIC_INLINE char * svpv2char(pTHX_ SV *sv, STRLEN *lp) { if (SvGAMAGIC(sv)) sv = sv_2mortal(newSVsv(sv)); return SvPV(sv, *lp); } STATIC_INLINE int _accept(int fileno, struct sockaddr *addr, unsigned int addrlen) { int fd; #ifdef HAVE_ACCEPT4 fd = accept4(fileno, addr, &addrlen, SOCK_CLOEXEC|SOCK_NONBLOCK); #else fd = accept(fileno, addr, &addrlen); #endif if (fd < 0) { return fd; } #ifndef HAVE_ACCEPT4 fcntl(fd, F_SETFD, FD_CLOEXEC); fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); #endif return fd; } STATIC_INLINE ssize_t _writev_timeout(const int fileno, const double timeout, struct iovec *iovec, const int iovcnt, const int do_select ) { int rv; int nfound; struct pollfd wfds[1]; if ( do_select == 1) goto WAIT_WRITE; DO_WRITE: rv = writev(fileno, iovec, iovcnt); if ( rv >= 0 ) { return rv; } if ( rv < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK ) { return rv; } WAIT_WRITE: while (1) { wfds[0].fd = fileno; wfds[0].events = POLLOUT; nfound = poll(wfds, 1, (int)timeout*1000); if ( nfound == 1 ) { break; } if ( nfound == 0 && errno != EINTR ) { return -1; } } goto DO_WRITE; } STATIC_INLINE ssize_t _read_timeout(const int fileno, const double timeout, char * read_buf, const int read_len ) { int rv; int nfound; struct pollfd rfds[1]; DO_READ: rfds[0].fd = fileno; rfds[0].events = POLLIN; rv = read(fileno, read_buf, read_len); if ( rv >= 0 ) { return rv; } if ( rv < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK ) { return rv; } WAIT_READ: while (1) { nfound = poll(rfds, 1, (int)timeout*1000); if ( nfound == 1 ) { break; } if ( nfound == 0 && errno != EINTR ) { return -1; } } goto DO_READ; } STATIC_INLINE ssize_t _write_timeout(const int fileno, const double timeout, char * write_buf, const int write_len ) { int rv; int nfound; struct pollfd wfds[1]; DO_WRITE: rv = write(fileno, write_buf, write_len); if ( rv >= 0 ) { return rv; } if ( rv < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK ) { return rv; } WAIT_WRITE: while (1) { wfds[0].fd = fileno; wfds[0].events = POLLOUT; nfound = poll(wfds, 1, (int)timeout*1000); if ( nfound == 1 ) { break; } if ( nfound == 0 && errno != EINTR ) { return -1; } } goto DO_WRITE; } STATIC_INLINE void str_s(char * dst, size_t *dst_len, const char * src, int src_len) { int i; int dlen = *dst_len; for ( i=0; i= *dst_len ); *dst_len += fig; } STATIC_INLINE int _date_line(char * date_line) { struct tm gtm; time_t lt; size_t i = 0; time(<); gmtime_r(<, >m); date_line[i++] = 'D'; date_line[i++] = 'a'; date_line[i++] = 't'; date_line[i++] = 'e'; date_line[i++] = ':'; date_line[i++] = ' '; str_s(date_line, &i, DoW[gtm.tm_wday], 3); date_line[i++] = ','; date_line[i++] = ' '; str_i(date_line, &i, gtm.tm_mday, 2); date_line[i++] = ' '; str_s(date_line, &i, MoY[gtm.tm_mon], 3); date_line[i++] = ' '; str_i(date_line, &i, gtm.tm_year + 1900, 4); date_line[i++] = ' '; str_i(date_line, &i, gtm.tm_hour,2); date_line[i++] = ':'; str_i(date_line, &i, gtm.tm_min,2); date_line[i++] = ':'; str_i(date_line, &i, gtm.tm_sec,2); date_line[i++] = ' '; date_line[i++] = 'G'; date_line[i++] = 'M'; date_line[i++] = 'T'; date_line[i++] = 13; date_line[i++] = 10; return i; } STATIC_INLINE int _chunked_header(char *buf, ssize_t len) { int dlen = 0, i; ssize_t l = len; while ( l > 0 ) { dlen++; l /= 16; } i = dlen; buf[i++] = 13; buf[i++] = 10; buf[i+1] = 0; while ( len > 0 ) { buf[--dlen] = xdigit[len % 16]; len /= 16; } return i; } MODULE = Plack::Handler::Gazelle PACKAGE = Plack::Handler::Gazelle PROTOTYPES: DISABLE BOOT: { AV * psgi_version; psgi_version = newAV(); av_extend(psgi_version, 2); (void)av_push(psgi_version,newSViv(1)); (void)av_push(psgi_version,newSViv(1)); SvREADONLY_on((SV*)psgi_version); HV *e; e = newHV(); (void)hv_stores(e,"SCRIPT_NAME", newSVpvs("")); (void)hv_stores(e,"psgi.version", newRV((SV*)psgi_version)); (void)hv_stores(e,"psgi.errors", newRV((SV*)PL_stderrgv)); (void)hv_stores(e,"psgi.url_scheme", newSVpvs("http")); (void)hv_stores(e,"psgi.run_once", newSV(0)); (void)hv_stores(e,"psgi.multithread", newSV(0)); (void)hv_stores(e,"psgi.multiprocess", newSViv(1)); (void)hv_stores(e,"psgi.streaming", newSViv(1)); (void)hv_stores(e,"psgi.nonblocking", newSV(0)); (void)hv_stores(e,"psgix.input.buffered", newSViv(1)); (void)hv_stores(e,"psgix.harakiri", newSViv(1)); /* stolenn from Feersum */ /* placeholders that get defined for every request */ (void)hv_stores(e, "SERVER_PROTOCOL", &PL_sv_undef); (void)hv_stores(e, "SERVER_NAME", &PL_sv_undef); (void)hv_stores(e, "SERVER_PORT", &PL_sv_undef); (void)hv_stores(e, "REQUEST_URI", &PL_sv_undef); (void)hv_stores(e, "REQUEST_METHOD", &PL_sv_undef); (void)hv_stores(e, "PATH_INFO", &PL_sv_undef); (void)hv_stores(e, "REMOTE_ADDR", &PL_sv_placeholder); (void)hv_stores(e, "REMOTE_PORT", &PL_sv_placeholder); /* defaults that get changed for some requests */ (void)hv_stores(e, "psgi.input", &PL_sv_placeholder); (void)hv_stores(e, "CONTENT_LENGTH", &PL_sv_placeholder); (void)hv_stores(e, "QUERY_STRING", &PL_sv_placeholder); /* anticipated headers */ (void)hv_stores(e, "CONTENT_TYPE", &PL_sv_placeholder); (void)hv_stores(e, "HTTP_HOST", &PL_sv_placeholder); (void)hv_stores(e, "HTTP_USER_AGENT", &PL_sv_placeholder); (void)hv_stores(e, "HTTP_ACCEPT", &PL_sv_placeholder); (void)hv_stores(e, "HTTP_ACCEPT_LANGUAGE", &PL_sv_placeholder); (void)hv_stores(e, "HTTP_ACCEPT_CHARSET", &PL_sv_placeholder); (void)hv_stores(e, "HTTP_REFERER", &PL_sv_placeholder); (void)hv_stores(e, "HTTP_COOKIE", &PL_sv_placeholder); (void)hv_stores(e, "HTTP_IF_MODIFIED_SINCE", &PL_sv_placeholder); (void)hv_stores(e, "HTTP_IF_NONE_MATCH", &PL_sv_placeholder); (void)hv_stores(e, "HTTP_IF_MODIFIED_SINCE", &PL_sv_placeholder); (void)hv_stores(e, "HTTP_IF_NONE_MATCH", &PL_sv_placeholder); (void)hv_stores(e, "HTTP_CACHE_CONTROL", &PL_sv_placeholder); (void)hv_stores(e, "HTTP_X_FORWARDED_FOR", &PL_sv_placeholder); env_template = e; } SV * accept_psgi(fileno, timeout, tcp, host, port) int fileno double timeout int tcp SV * host SV * port PREINIT: int fd; struct sockaddr_in cliaddr; unsigned int len; char read_buf[MAX_HEADER_SIZE]; HV * env; int flag = 1; ssize_t rv = 0; ssize_t buf_len; ssize_t reqlen; PPCODE: { /* if ( my ($conn, $buf, $env) = accept_buffer(fileno($server),timeout,tcp,host,port) */ len = sizeof(cliaddr); fd = _accept(fileno, (struct sockaddr *)&cliaddr, len); /* endif */ if (fd < 0) { goto badexit; } rv = _read_timeout(fd, timeout, &read_buf[0], MAX_HEADER_SIZE); // printf("fd:%d rv:%ld %f %d\n",fd,rv,timeout); if ( rv <= 0 ) { close(fd); goto badexit; } env = newHVhv(env_template); if ( tcp == 1 ) { setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(int)); (void)hv_stores(env,"REMOTE_ADDR",newSVpv(inet_ntoa(cliaddr.sin_addr),0)); (void)hv_stores(env,"REMOTE_PORT",newSViv(ntohs(cliaddr.sin_port))); } else { (void)hv_stores(env,"REMOTE_ADDR",newSV(0)); (void)hv_stores(env,"REMOTE_PORT",newSViv(0)); } (void)hv_stores(env,"SERVER_PORT",SvREFCNT_inc(port)); (void)hv_stores(env,"SERVER_NAME",SvREFCNT_inc(host)); buf_len = rv; while (1) { reqlen = _parse_http_request(aTHX_ &read_buf[0],buf_len,env); if ( reqlen >= 0 ) { break; } else if ( reqlen == -1 ) { /* error */ close(fd); goto badexit_clear; } if ( MAX_HEADER_SIZE - buf_len == 0 ) { /* too large header */ char* badreq; badreq = BAD_REQUEST; rv = _write_timeout(fd, timeout, badreq, sizeof(BAD_REQUEST) - 1); close(fd); goto badexit_clear; } /* request is incomplete */ rv = _read_timeout(fd, timeout, &read_buf[buf_len], MAX_HEADER_SIZE - buf_len); if ( rv <= 0 ) { close(fd); goto badexit_clear; } buf_len += rv; } /* expect */ SV **expect_val = hv_fetch(env, "HTTP_EXPECT" , sizeof("HTTP_EXPECT")-1, 0); if (expect_val != NULL) { if ( strncmp(SvPV_nolen(*expect_val), "100-continue", SvCUR(*expect_val)) == 0 ) { rv = _write_timeout(fd, timeout, EXPECT_CONTINUE, sizeof(EXPECT_CONTINUE) - 1); if ( rv <= 0 ) { close(fd); goto badexit; } } else { rv = _write_timeout(fd, timeout, EXPECT_FAILED, sizeof(EXPECT_FAILED) - 1); close(fd); goto badexit; } } PUSHs(sv_2mortal(newSViv(fd))); PUSHs(sv_2mortal(newSVpvn(&read_buf[reqlen], buf_len - reqlen))); PUSHs(sv_2mortal(newRV_noinc((SV*)env))); XSRETURN(3); badexit_clear: sv_2mortal((SV*)env); badexit: XSRETURN(0); } unsigned long read_timeout(fileno, rbuf, len, offset, timeout) int fileno SV * rbuf ssize_t len ssize_t offset double timeout PREINIT: SV * buf; char * d; ssize_t rv; ssize_t buf_len; CODE: if (!SvROK(rbuf)) croak("ERROR: buf must be RV"); buf = SvRV(rbuf); if (!SvOK(buf)) { sv_setpvn(buf,"",0); } SvUPGRADE(buf, SVt_PV); SvPV_nolen(buf); buf_len = SvCUR(buf); if ( len > READ_BUFSZ ) { len = READ_BUFSZ; } d = SvGROW(buf, buf_len + len + 1); rv = _read_timeout(fileno, timeout, &d[offset], len); SvCUR_set(buf, (rv > 0) ? rv + buf_len : buf_len); *SvEND(buf) = '\0'; SvPOK_only(buf); if (rv < 0) XSRETURN_UNDEF; RETVAL = (unsigned long)rv; OUTPUT: RETVAL unsigned long write_timeout(fileno, buf, len, offset, timeout) int fileno SV * buf ssize_t len ssize_t offset double timeout PREINIT: char * d; ssize_t rv; CODE: SvUPGRADE(buf, SVt_PV); d = SvPV_nolen(buf); rv = _write_timeout(fileno, timeout, &d[offset], len); if (rv < 0) XSRETURN_UNDEF; RETVAL = (unsigned long)rv; OUTPUT: RETVAL unsigned long write_chunk(fileno, buf, offset, timeout) int fileno SV * buf ssize_t offset double timeout PREINIT: char *d; ssize_t buf_len; ssize_t rv = 0; ssize_t written = 0; ssize_t vec_offset = 0; int count =0; int remain; ssize_t iovcnt = 3; char chunked_header_buf[18]; CODE: if ( !SvOK(buf) ) { RETVAL = 0; return; } SvUPGRADE(buf, SVt_PV); d = SvPV_nolen(buf); buf_len = SvCUR(buf); if ( buf_len == 0 ){ RETVAL = 0; return; } { struct iovec v[iovcnt]; // Needs C99 compiler v[0].iov_len = _chunked_header(chunked_header_buf,buf_len); v[0].iov_base = chunked_header_buf; v[1].iov_len = buf_len; v[1].iov_base = d; v[2].iov_base = "\r\n"; v[2].iov_len = sizeof("\r\n") -1; vec_offset = 0; written = 0; remain = iovcnt; while ( remain > 0 ) { count = (remain > IOV_MAX) ? IOV_MAX : remain; rv = _writev_timeout(fileno, timeout, &v[vec_offset], count, (vec_offset == 0) ? 0 : 1); if ( rv <= 0 ) { warn("failed to writev: %zd errno:%d", rv, errno); // error or disconnected break; } written += rv; while ( rv > 0 ) { if ( (unsigned int)rv >= v[vec_offset].iov_len ) { rv -= v[vec_offset].iov_len; vec_offset++; remain--; } else { v[vec_offset].iov_base = (char*)v[vec_offset].iov_base + rv; v[vec_offset].iov_len -= rv; rv = 0; } } } } if (rv < 0) XSRETURN_UNDEF; RETVAL = (unsigned long)written; OUTPUT: RETVAL unsigned long write_all(fileno, buf, offset, timeout) int fileno SV * buf ssize_t offset double timeout PREINIT: char * d; ssize_t buf_len; ssize_t rv; ssize_t written = 0; CODE: if ( !SvOK(buf) ) { RETVAL = 0; return; } SvUPGRADE(buf, SVt_PV); d = SvPV_nolen(buf); buf_len = SvCUR(buf); if ( buf_len == 0 ) { RETVAL = 0; return; } written = 0; while ( buf_len > written ) { rv = _write_timeout(fileno, timeout, &d[written], buf_len - written); if ( rv <= 0 ) { break; } written += rv; } if (rv < 0) XSRETURN_UNDEF; RETVAL = (unsigned long)written; OUTPUT: RETVAL void close_client(fileno) int fileno CODE: close(fileno); unsigned long write_informational_response(fileno, timeout, status_code, headers) int fileno double timeout int status_code AV * headers PREINIT: ssize_t rv; ssize_t iovcnt; ssize_t vec_offset; ssize_t written; int count; int remain; size_t i; struct iovec * iv; char status_line[512]; char * key; char * val; STRLEN key_len = 0; STRLEN val_len = 0; CODE: if( (av_len(headers)+1) % 2 == 1 ) croak("ERROR: Odd number of element in header"); iovcnt = 10 + (av_len(headers)+2)*2; { struct iovec iv[iovcnt]; // Needs C99 compiler /* status line */ iovcnt = 0; i=0; status_line[i++] = 'H'; status_line[i++] = 'T'; status_line[i++] = 'T'; status_line[i++] = 'P'; status_line[i++] = '/'; status_line[i++] = '1'; status_line[i++] = '.'; status_line[i++] = '1'; status_line[i++] = ' '; str_i(status_line,&i,status_code,3); status_line[i++] = ' '; const char * message = status_message(status_code); str_s(status_line,&i, message, strlen(message)); status_line[i++] = 13; status_line[i++] = 10; iv[iovcnt].iov_base = status_line; iv[iovcnt].iov_len = i; iovcnt++; i=0; while (i < av_len(headers) + 1 ) { /* key */ key = svpv2char(aTHX_ *av_fetch(headers,i,0), &key_len); i++; val = svpv2char(aTHX_ *av_fetch(headers,i,0), &val_len); i++; iv[iovcnt].iov_base = key; iv[iovcnt].iov_len = key_len; iovcnt++; iv[iovcnt].iov_base = ": "; iv[iovcnt].iov_len = sizeof(": ") - 1; iovcnt++; /* value */ iv[iovcnt].iov_base = val; iv[iovcnt].iov_len = val_len; iovcnt++; iv[iovcnt].iov_base = "\r\n"; iv[iovcnt].iov_len = sizeof("\r\n") - 1; iovcnt++; } iv[iovcnt].iov_base = "\r\n"; iv[iovcnt].iov_len = sizeof("\r\n") - 1; iovcnt++; vec_offset = 0; written = 0; remain = iovcnt; while ( remain > 0 ) { count = (remain > IOV_MAX) ? IOV_MAX : remain; rv = _writev_timeout(fileno, timeout, &iv[vec_offset], count, (vec_offset == 0) ? 0 : 1); if ( rv <= 0 ) { warn("failed to writev: %zd errno:%d", rv, errno); // error or disconnected break; } written += rv; while ( rv > 0 ) { if ( (unsigned int)rv >= iv[vec_offset].iov_len ) { rv -= iv[vec_offset].iov_len; vec_offset++; remain--; } else { iv[vec_offset].iov_base = (char*)iv[vec_offset].iov_base + rv; iv[vec_offset].iov_len -= rv; rv = 0; } } } } if (rv < 0) XSRETURN_UNDEF; RETVAL = (unsigned long) written; OUTPUT: RETVAL unsigned long write_psgi_response(fileno, timeout, status_code, headers, body, use_chunkedv) int fileno double timeout int status_code AV * headers AV * body SV * use_chunkedv ALIAS: Plack::Handler::Gazelle::write_psgi_response = 0 Plack::Handler::Gazelle::write_psgi_response_header = 1 PREINIT: ssize_t rv; ssize_t iovcnt; ssize_t vec_offset; ssize_t written; int count; int remain; size_t i; struct iovec * v; char status_line[512]; char date_line[512]; char server_line[1032]; char * key; char * val; STRLEN key_len = 0; STRLEN val_len; int date_pushed = 0; const char * s; char* d; ssize_t n; IV use_chunked; char * chunked_header_buf; CODE: if( (av_len(headers)+1) % 2 == 1 ) croak("ERROR: Odd number of element in header"); use_chunked = SvIV(use_chunkedv); iovcnt = 10 + (av_len(headers)+2)*2 + (av_len(body) + 1); /* status_with_no_entity_body */ if ( status_code < 200 || status_code == 204 || status_code == 304 ) { use_chunked = 0; } if ( use_chunked > 0 ) { iovcnt += (av_len(body)+1)*2; } Newx(chunked_header_buf, 18 * (av_len(body)+2), char); { struct iovec v[iovcnt]; // Needs C99 compiler /* status line */ iovcnt = 0; i=0; status_line[i++] = 'H'; status_line[i++] = 'T'; status_line[i++] = 'T'; status_line[i++] = 'P'; status_line[i++] = '/'; status_line[i++] = '1'; status_line[i++] = '.'; status_line[i++] = '1'; status_line[i++] = ' '; str_i(status_line,&i,status_code,3); status_line[i++] = ' '; const char * message = status_message(status_code); str_s(status_line,&i, message, strlen(message)); status_line[i++] = 13; status_line[i++] = 10; v[iovcnt].iov_base = status_line; v[iovcnt].iov_len = i; iovcnt++; /* for date header */ iovcnt++; v[iovcnt].iov_base = "Server: gazelle\r\n"; v[iovcnt].iov_len = sizeof("Server: gazelle\r\n")-1; iovcnt++; i=0; date_pushed = 0; while ( i < av_len(headers) + 1 ) { /* key */ key = svpv2char(aTHX_ *av_fetch(headers,i,0), &key_len); i++; if ( strncasecmp(key,"Connection",key_len) == 0 ) { i++; continue; } val = svpv2char(aTHX_ *av_fetch(headers,i,0), &val_len); i++; if ( strncasecmp(key,"Date",key_len) == 0 ) { strcpy(date_line, "Date: "); for ( s=val, n = val_len, d=date_line+sizeof("Date: ")-1; n !=0; s++, --n, d++) { *d = *s; } date_line[sizeof("Date: ") -1 + val_len] = 13; date_line[sizeof("Date: ") -1 + val_len + 1] = 10; v[1].iov_base = date_line; v[1].iov_len = sizeof("Date: ") -1 + val_len + 2; date_pushed = 1; continue; } else if ( strncasecmp(key,"Server",key_len) == 0 ) { strcpy(server_line, "Server: "); for ( s=val, n = val_len, d=server_line+sizeof("Server: ")-1; n !=0; s++, --n, d++) { *d = *s; } server_line[sizeof("Server: ") -1 + val_len] = 13; server_line[sizeof("Server: ") -1 + val_len + 1] = 10; v[2].iov_base = server_line; v[2].iov_len = sizeof("Server: ") -1 + val_len + 2; continue; } else if ( strncasecmp(key,"Content-Length",key_len) == 0 || strncasecmp(key,"Transfer-Encoding",key_len) == 0) { use_chunked = 0; } v[iovcnt].iov_base = key; v[iovcnt].iov_len = key_len; iovcnt++; v[iovcnt].iov_base = ": "; v[iovcnt].iov_len = sizeof(": ") - 1; iovcnt++; /* value */ v[iovcnt].iov_base = val; v[iovcnt].iov_len = val_len; iovcnt++; v[iovcnt].iov_base = "\r\n"; v[iovcnt].iov_len = sizeof("\r\n") - 1; iovcnt++; } if ( date_pushed == 0 ) { v[1].iov_len = _date_line(date_line); v[1].iov_base = date_line; } if ( use_chunked > 0 ) { v[iovcnt].iov_base = "Transfer-Encoding: chunked\r\n"; v[iovcnt].iov_len = sizeof("Transfer-Encoding: chunked\r\n") - 1; iovcnt++; } v[iovcnt].iov_base = "Connection: close\r\n\r\n"; v[iovcnt].iov_len = sizeof("Connection: close\r\n\r\n") - 1; iovcnt++; size_t chb_offset = 0; for (i=0; i < av_len(body) + 1; i++ ) { SV **b = av_fetch(body,i,0); if (!SvOK(*b)) { continue; } d = svpv2char(aTHX_ *b, &val_len); if ( val_len < 1 ) { continue; } if ( use_chunked ) { v[iovcnt].iov_len = _chunked_header(&chunked_header_buf[chb_offset],val_len); v[iovcnt].iov_base = &chunked_header_buf[chb_offset]; chb_offset += v[iovcnt].iov_len; iovcnt++; } v[iovcnt].iov_base = d; v[iovcnt].iov_len = val_len; iovcnt++; if ( use_chunked ) { v[iovcnt].iov_base = "\r\n"; v[iovcnt].iov_len = sizeof("\r\n") -1; iovcnt++; } } if ( use_chunked && ix == 0 ) { v[iovcnt].iov_base = "0\r\n\r\n"; v[iovcnt].iov_len = sizeof("0\r\n\r\n") - 1; iovcnt++; } vec_offset = 0; written = 0; remain = iovcnt; while ( remain > 0 ) { count = (remain > IOV_MAX) ? IOV_MAX : remain; rv = _writev_timeout(fileno, timeout, &v[vec_offset], count, (vec_offset == 0) ? 0 : 1); if ( rv <= 0 ) { warn("failed to writev: %zd errno:%d", rv, errno); // error or disconnected break; } written += rv; while ( rv > 0 ) { if ( (unsigned int)rv >= v[vec_offset].iov_len ) { rv -= v[vec_offset].iov_len; vec_offset++; remain--; } else { v[vec_offset].iov_base = (char*)v[vec_offset].iov_base + rv; v[vec_offset].iov_len -= rv; rv = 0; } } } } sv_setiv(use_chunkedv, use_chunked); Safefree(chunked_header_buf); if (rv < 0) XSRETURN_UNDEF; RETVAL = (unsigned long) written; OUTPUT: RETVAL