/* * Copyright (C) 2004, 2005, 2006 James Antill * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * email: james@and.org */ /* main HTTPD appending APIs, output responses */ #define EX_UTILS_NO_FUNCS 1 #include "ex_utils.h" #include "mk.h" #include "vlg.h" #define HTTPD_HAVE_GLOBAL_OPTS 1 #include "httpd.h" #include "httpd_policy.h" #include "base64.h" #if ! COMPILE_DEBUG # define HTTP_CONF_MMAP_LIMIT_MIN (16 * 1024) /* a couple of pages */ # define HTTP_CONF_SAFE_PRINT_REQ TRUE #else # define HTTP_CONF_MMAP_LIMIT_MIN 8 /* debug... */ # define HTTP_CONF_SAFE_PRINT_REQ FALSE #endif #define HTTP_CONF_MMAP_LIMIT_MAX (50 * 1024 * 1024) #define HTTPD_CONF_ZIP_LIMIT_MIN 8 #define CLEN COMPILE_STRLEN /* is the cstr a prefix of the vstr */ #define VPREFIX(vstr, p, l, cstr) \ (((l) >= CLEN(cstr)) && \ vstr_cmp_buf_eq(vstr, p, CLEN(cstr), cstr, CLEN(cstr))) /* is the cstr a suffix of the vstr */ #define VSUFFIX(vstr, p, l, cstr) \ (((l) >= CLEN(cstr)) && \ vstr_cmp_eod_buf_eq(vstr, p, l, cstr, CLEN(cstr))) /* is the cstr a prefix of the vstr, no case */ #define VIPREFIX(vstr, p, l, cstr) \ (((l) >= CLEN(cstr)) && \ vstr_cmp_case_buf_eq(vstr, p, CLEN(cstr), cstr, CLEN(cstr))) /* for simplicity */ #define VEQ(vstr, p, l, cstr) vstr_cmp_cstr_eq(vstr, p, l, cstr) #define VIEQ(vstr, p, l, cstr) vstr_cmp_case_cstr_eq(vstr, p, l, cstr) #define HTTP__HDR_SET(req, h, p, l) do { \ (req)-> http_hdrs -> hdr_ ## h ->pos = (p); \ (req)-> http_hdrs -> hdr_ ## h ->len = (l); \ } while (FALSE) #define HTTP__HDR_MULTI_SET(req, h, p, l) do { \ (req)-> http_hdrs -> multi -> hdr_ ## h ->pos = (p); \ (req)-> http_hdrs -> multi -> hdr_ ## h ->len = (l); \ } while (FALSE) #define HTTP__XTRA_HDR_INIT(x) do { \ req-> x ## _vs1 = NULL; \ req-> x ## _pos = 0; \ req-> x ## _len = 0; \ } while (FALSE) #include static Vlg *vlg = NULL; void httpd_app_init(Vlg *passed_vlg) { ASSERT(passed_vlg && !vlg); vlg = passed_vlg; } void httpd_app_exit(void) { ASSERT(vlg); vlg = NULL; } static void http__app_hdr_hdr(Vstr_base *out, const char *hdr) { vstr_add_cstr_buf(out, out->len, hdr); vstr_add_cstr_buf(out, out->len, ": "); } static void http__app_hdr_eol(Vstr_base *out) { vstr_add_cstr_buf(out, out->len, HTTP_EOL); } void http_app_hdr_cstr(Vstr_base *out, const char *hdr, const char *data) { http__app_hdr_hdr(out, hdr); vstr_add_cstr_buf(out, out->len, data); http__app_hdr_eol(out); } void http_app_hdr_vstr(Vstr_base *out, const char *hdr, const Vstr_base *s1, size_t vpos, size_t vlen, unsigned int type) { if (!vlen) return; /* don't allow "empty" headers, use a single space */ http__app_hdr_hdr(out, hdr); vstr_add_vstr(out, out->len, s1, vpos, vlen, type); http__app_hdr_eol(out); } void http_app_hdr_vstr_def(Vstr_base *out, const char *hdr, const Vstr_base *s1, size_t vpos, size_t vlen) { if (!vlen) return; /* don't allow "empty" headers, use a single space */ http__app_hdr_hdr(out, hdr); vstr_add_vstr(out, out->len, s1, vpos, vlen, VSTR_TYPE_ADD_DEF); http__app_hdr_eol(out); } void http_app_hdr_conf_vstr(Vstr_base *out, const char *hdr, const Vstr_base *s1) { if (!s1->len) return; /* don't allow "empty" headers, a single space */ http__app_hdr_hdr(out, hdr); vstr_add_vstr(out, out->len, s1, 1, s1->len, VSTR_TYPE_ADD_DEF); http__app_hdr_eol(out); } /* convert a 4byte number into 4 bytes... */ #define APP_BUF4(x) do { \ buf[(x * 4) + 0] = (num[x] >> 24) & 0xFF; \ buf[(x * 4) + 1] = (num[x] >> 16) & 0xFF; \ buf[(x * 4) + 2] = (num[x] >> 8) & 0xFF; \ buf[(x * 4) + 3] = (num[x] >> 0) & 0xFF; \ } while (FALSE) static void http_app_hdr_vstr_md5(struct Con *con, Httpd_req_data *req, const Vstr_base *s1, size_t vpos, size_t vlen) { Vstr_base *out = con->evnt->io_w; if (!vlen) return; /* don't allow "empty" headers, use a single space */ http__app_hdr_hdr(out, "Content-MD5"); if (vlen == (128 / 8)) /* raw bytes ... */ vstr_x_conv_base64_encode(out, out->len, s1, vpos, vlen, 0); else if (vlen != (128 / 4)) /* just output, and hope... */ vstr_add_vstr(out, out->len, s1, vpos, vlen, VSTR_TYPE_ADD_DEF); else /* hex encoded bytes... */ { /* NOTE: lots of work, should be compiled to raw bytes in the conf */ Vstr_base *xc = req->xtra_content; size_t pos = 0; unsigned int nflags = VSTR_FLAG_PARSE_NUM_NO_BEG_PM; unsigned int num[4]; unsigned char buf[128 / 8]; ASSERT(xc); /* must be true, if the MD5 headers are used */ num[0] = vstr_parse_uint(s1, vpos + 0, 8, 16 | nflags, NULL, NULL); num[1] = vstr_parse_uint(s1, vpos + 8, 8, 16 | nflags, NULL, NULL); num[2] = vstr_parse_uint(s1, vpos + 16, 8, 16 | nflags, NULL, NULL); num[3] = vstr_parse_uint(s1, vpos + 24, 8, 16 | nflags, NULL, NULL); APP_BUF4(0); APP_BUF4(1); APP_BUF4(2); APP_BUF4(3); pos = xc->len + 1; if (!vstr_add_buf(xc, xc->len, buf, sizeof(buf))) return; /* raw bytes now... */ ASSERT(vstr_sc_posdiff(pos, xc->len) == sizeof(buf)); vstr_x_conv_base64_encode(out, out->len, xc, pos, sizeof(buf), 0); } http__app_hdr_eol(out); } #undef APP_BUF4 void http_app_hdr_fmt(Vstr_base *out, const char *hdr, const char *fmt, ...) { va_list ap; http__app_hdr_hdr(out, hdr); va_start(ap, fmt); vstr_add_vfmt(out, out->len, fmt, ap); va_end(ap); http__app_hdr_eol(out); } void http_app_hdr_uintmax(Vstr_base *out, const char *hdr, uintmax_t data) { http__app_hdr_hdr(out, hdr); vstr_add_fmt(out, out->len, "%ju", data); http__app_hdr_eol(out); } #define HTTP__VARY_ADD(x, y) do { \ ASSERT((x) < (sizeof(varies_ptr) / sizeof(varies_ptr[0]))); \ \ varies_ptr[(x)] = (y); \ varies_len[(x)] = CLEN(y); \ ++(x); \ } while (FALSE) void http_app_def_hdrs(struct Con *con, struct Httpd_req_data *req, unsigned int http_ret_code, const char *http_ret_line, time_t mtime, const char *custom_content_type, int use_range, uintmax_t content_length) { Vstr_base *out = con->evnt->io_w; Date_store *ds = httpd_opts->date; if (use_range) use_range = req->policy->use_range; vstr_add_fmt(out, out->len, "%s %03u %s" HTTP_EOL, "HTTP/1.1", http_ret_code, http_ret_line); http_app_hdr_cstr(out, "Date", date_rfc1123(ds, req->now)); http_app_hdr_conf_vstr(out, "Server", req->policy->server_name); if (mtime) { /* if mtime in future, chop it #14.29 * for cache validation we don't cmp last-modified == now either */ if (difftime(req->now, mtime) <= 0) mtime = req->now; http_app_hdr_cstr(out, "Last-Modified", date_rfc1123(ds, mtime)); } switch (con->keep_alive) { case HTTP_NON_KEEP_ALIVE: HTTP_APP_HDR_CONST_CSTR(out, "Connection", "close"); break; case HTTP_1_0_KEEP_ALIVE: HTTP_APP_HDR_CONST_CSTR(out, "Connection", "Keep-Alive"); /* FALLTHROUGH */ case HTTP_1_1_KEEP_ALIVE: if (req->policy->output_keep_alive_hdr) { if (!req->policy->max_requests) http_app_hdr_fmt(out, "Keep-Alive", "%s=%u", "timeout", req->policy->s->idle_timeout); else { unsigned int left = (req->policy->max_requests - con->evnt->acct.req_got); http_app_hdr_fmt(out, "Keep-Alive", "%s=%u, %s=%u", "timeout", req->policy->s->idle_timeout, "max", left); } } ASSERT_NO_SWITCH_DEF(); } switch (http_ret_code) { case 200: /* OK */ /* case 206: */ /* OK - partial -- needed? */ case 302: case 307: /* Tmp Redirects */ case 304: /* Not modified */ case 406: /* Not accept - a */ case 412: /* Not accept - precondition */ case 413: /* Too large */ case 416: /* Bad range */ case 417: /* Not accept - expect contained something */ if (use_range && req->policy->use_adv_range) HTTP_APP_HDR_CONST_CSTR(out, "Accept-Ranges", "bytes"); } if (req->vary_star) HTTP_APP_HDR_CONST_CSTR(out, "Vary", "*"); else if (req->vary_a || req->vary_ac || req->vary_ae || req->vary_al || req->vary_rf || req->vary_ua || req->vary_ims || req->vary_ius || req->vary_ir || req->vary_im || req->vary_inm || req->vary_xm) { const char *varies_ptr[11]; size_t varies_len[12]; unsigned int num = 0; if (req->vary_xm) HTTP__VARY_ADD(num, "X-Moz"); if (req->vary_ua) HTTP__VARY_ADD(num, "User-Agent"); if (req->vary_rf) HTTP__VARY_ADD(num, "Referer"); if (req->vary_ius) HTTP__VARY_ADD(num, "If-Unmodified-Since"); if (req->vary_ir) HTTP__VARY_ADD(num, "If-Range"); if (req->vary_inm) HTTP__VARY_ADD(num, "If-None-Match"); if (req->vary_ims) HTTP__VARY_ADD(num, "If-Modified-Since"); if (req->vary_im) HTTP__VARY_ADD(num, "If-Match"); if (req->vary_al) HTTP__VARY_ADD(num, "Accept-Language"); if (req->vary_ae) HTTP__VARY_ADD(num, "Accept-Encoding"); if (req->vary_ac) HTTP__VARY_ADD(num, "Accept-Charset"); if (req->vary_a) HTTP__VARY_ADD(num, "Accept"); ASSERT(num && (num <= 12)); http__app_hdr_hdr(out, "Vary"); while (num-- > 1) { vstr_add_buf(out, out->len, varies_ptr[num], varies_len[num]); vstr_add_cstr_buf(out, out->len, ","); } ASSERT(num == 0); vstr_add_buf(out, out->len, varies_ptr[0], varies_len[0]); http__app_hdr_eol(out); } if (con->use_mpbr) { ASSERT(con->mpbr_ct && !con->mpbr_ct->len); if (req->content_type_vs1 && req->content_type_len) http_app_hdr_vstr_def(con->mpbr_ct, "Content-Type", HTTP__XTRA_HDR_PARAMS(req, content_type)); HTTP_APP_HDR_CONST_CSTR(out, "Content-Type", "multipart/byteranges; boundary=SEP"); } else if (req->content_type_vs1 && req->content_type_len) http_app_hdr_vstr_def(out, "Content-Type", HTTP__XTRA_HDR_PARAMS(req, content_type)); else if (custom_content_type) /* possible we don't send one */ http_app_hdr_cstr(out, "Content-Type", custom_content_type); if (req->content_encoding_bzip2) HTTP_APP_HDR_CONST_CSTR(out, "Content-Encoding", "bzip2"); else if (req->content_encoding_gzip) { if (req->content_encoding_xgzip) HTTP_APP_HDR_CONST_CSTR(out, "Content-Encoding", "x-gzip"); else HTTP_APP_HDR_CONST_CSTR(out, "Content-Encoding", "gzip"); } if (!con->use_mpbr) http_app_hdr_uintmax(out, "Content-Length", content_length); } #undef HTTP__VARY_ADD void http_app_end_hdrs(Vstr_base *out) { /* apache contains a workaround for buggy Netscape 2.x, 3.x and 4.0beta */ http__app_hdr_eol(out); } void http_app_hdrs_url(struct Con *con, Httpd_req_data *req) { Vstr_base *out = con->evnt->io_w; if (HTTPD_VER_GE_1_1(req) && req->cache_control_vs1) http_app_hdr_vstr_def(out, "Cache-Control", HTTP__XTRA_HDR_PARAMS(req, cache_control)); if (req->expires_vs1 && (req->expires_time > req->f_stat->st_mtime)) http_app_hdr_vstr_def(out, "Expires", HTTP__XTRA_HDR_PARAMS(req, expires)); if (req->p3p_vs1) http_app_hdr_vstr_def(out, "P3P", HTTP__XTRA_HDR_PARAMS(req, p3p)); } void http_app_hdrs_file(struct Con *con, Httpd_req_data *req) { Vstr_base *out = con->evnt->io_w; time_t mtime = req->f_stat->st_mtime; time_t enc_mtime = req->encoded_mtime; if (req->content_disposition_vs1) http_app_hdr_vstr_def(out, "Content-Disposition", HTTP__XTRA_HDR_PARAMS(req, content_disposition)); if (req->content_language_vs1) http_app_hdr_vstr_def(out, "Content-Language", HTTP__XTRA_HDR_PARAMS(req, content_language)); if (req->content_encoding_bzip2) { if (req->bzip2_content_md5_vs1 && (req->content_md5_time >= enc_mtime)) http_app_hdr_vstr_md5(con, req, HTTP__XTRA_HDR_PARAMS(req, bzip2_content_md5)); } else if (req->content_encoding_gzip) { if (req->gzip_content_md5_vs1 && (req->content_md5_time >= enc_mtime)) http_app_hdr_vstr_md5(con, req, HTTP__XTRA_HDR_PARAMS(req, gzip_content_md5)); } else if (req->content_md5_vs1 && (req->content_md5_time >= mtime)) http_app_hdr_vstr_md5(con, req, HTTP__XTRA_HDR_PARAMS(req, content_md5)); /* we only obey IM and INM when in version 1.1+, but who cares... */ if (req->etag_vs1 && (req->etag_time >= mtime)) http_app_hdr_vstr_def(out, "ETag", HTTP__XTRA_HDR_PARAMS(req, etag)); if (req->link_vs1) http_app_hdr_vstr_def(out, "Link", HTTP__XTRA_HDR_PARAMS(req, link)); } void http_app_hdrs_mpbr(struct Con *con, struct File_sect *fs) { Vstr_base *out = con->evnt->io_w; uintmax_t range_beg; uintmax_t range_end; ASSERT(fs && (fs->fd != -1)); range_beg = fs->off; range_end = range_beg + fs->len - 1; vstr_add_cstr_ptr(out, out->len, "--SEP" HTTP_EOL); HTTPD_APP_REF_ALLVSTR(out, con->mpbr_ct); http_app_hdr_fmt(out, "Content-Range", "%s %ju-%ju/%ju", "bytes", range_beg, range_end, con->mpbr_fs_len); http_app_end_hdrs(out); }