static liHandlerResult expire(liVRequest *vr, gpointer param, gpointer *context) { struct tm tm; time_t expire_date; guint len; gint max_age; GString *date_str = vr->wrk->tmp_str; expire_rule *rule = param; guint num = rule->num; time_t now = (time_t)li_cur_ts(vr->wrk); UNUSED(context); if (rule->base == EXPIRE_ACCESS) { expire_date = now + num; max_age = num; } else { /* modification */ struct stat st; gint err; if (!vr->physical.path->len) return LI_HANDLER_GO_ON; switch (li_stat_cache_get(vr, vr->physical.path, &st, &err, NULL)) { case LI_HANDLER_GO_ON: break; case LI_HANDLER_WAIT_FOR_EVENT: return LI_HANDLER_WAIT_FOR_EVENT; default: return LI_HANDLER_GO_ON; } expire_date = st.st_mtime + num; if (expire_date < now) expire_date = now; max_age = expire_date - now; } /* format date */ g_string_set_size(date_str, 255); if (!gmtime_r(&expire_date, &tm)) { VR_ERROR(vr, "gmtime_r(%"G_GUINT64_FORMAT") failed: %s", (guint64)expire_date, g_strerror(errno)); return LI_HANDLER_GO_ON; } len = strftime(date_str->str, date_str->allocated_len, "%a, %d %b %Y %H:%M:%S GMT", &tm); if (len == 0) return LI_HANDLER_GO_ON; g_string_set_size(date_str, len); /* finally set the headers */ li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Expires"), GSTR_LEN(date_str)); g_string_truncate(date_str, 0); g_string_append_len(date_str, CONST_STR_LEN("max-age=")); li_string_append_int(date_str, max_age); li_http_header_append(vr->response.headers, CONST_STR_LEN("Cache-Control"), GSTR_LEN(date_str)); return LI_HANDLER_GO_ON; }
static liHandlerResult redirect(liVRequest *vr, gpointer param, gpointer *context) { guint i; redirect_rule *rule; redirect_data *rd = param; gboolean debug = _OPTION(vr, rd->p, 0).boolean; GString *dest = vr->wrk->tmp_str; UNUSED(context); if (li_vrequest_is_handled(vr)) return LI_HANDLER_GO_ON; for (i = 0; i < rd->rules->len; i++) { rule = &g_array_index(rd->rules, redirect_rule, i); if (redirect_internal(vr, dest, rule)) { /* regex matched */ if (debug) { VR_DEBUG(vr, "redirect: \"%s\"", dest->str); } if (!li_vrequest_handle_direct(vr)) return LI_HANDLER_ERROR; vr->response.http_status = 301; li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Location"), GSTR_LEN(dest)); /* stop at first matching regex */ return LI_HANDLER_GO_ON; } } return LI_HANDLER_GO_ON; }
static liHandlerResult mc_handle_lookup(liVRequest *vr, gpointer param, gpointer *context) { memcached_ctx *ctx = param; memcache_request *req = *context; if (req) { static const GString default_mime_str = { CONST_STR_LEN("application/octet-stream"), 0 }; liBuffer *buf = req->buffer; const GString *mime_str; if (NULL != req->req) return LI_HANDLER_WAIT_FOR_EVENT; /* not done yet */ g_slice_free(memcache_request, req); *context = NULL; if (NULL == buf) { /* miss */ if (ctx->act_miss) li_action_enter(vr, ctx->act_miss); return LI_HANDLER_GO_ON; } if (!li_vrequest_handle_direct(vr)) { if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "memcached.lookup: request already handled"); } li_buffer_release(buf); return LI_HANDLER_GO_ON; } if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "memcached.lookup: key found, handling request"); } li_chunkqueue_append_buffer(vr->direct_out, buf); vr->response.http_status = 200; mime_str = li_mimetype_get(vr, vr->request.uri.path); if (!mime_str) mime_str = &default_mime_str; li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), GSTR_LEN(mime_str)); /* hit */ if (ctx->act_found) li_action_enter(vr, ctx->act_found); return LI_HANDLER_GO_ON; } else { liMemcachedCon *con; GError *err = NULL; if (li_vrequest_is_handled(vr)) { if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "memcached.lookup: request already handled"); } return LI_HANDLER_GO_ON; } con = mc_ctx_prepare(ctx, vr->wrk); mc_ctx_build_key(vr->wrk->tmp_str, ctx, vr); if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "memcached.lookup: looking up key '%s'", vr->wrk->tmp_str->str); } req = g_slice_new0(memcache_request); req->req = li_memcached_get(con, vr->wrk->tmp_str, memcache_callback, req, &err); if (NULL == req->req) { if (NULL != err) { if (LI_MEMCACHED_DISABLED != err->code) { VR_ERROR(vr, "memcached.lookup: get failed: %s", err->message); } g_clear_error(&err); } else { VR_ERROR(vr, "memcached.lookup: get failed: %s", "Unkown error"); } g_slice_free(memcache_request, req); /* miss */ if (ctx->act_miss) li_action_enter(vr, ctx->act_miss); return LI_HANDLER_GO_ON; } req->vr = vr; *context = req; return LI_HANDLER_WAIT_FOR_EVENT; } }
static liHandlerResult dirlist(liVRequest *vr, gpointer param, gpointer *context) { GString *listing; liStatCacheEntry *sce; dirlist_data *dd; UNUSED(context); switch (vr->request.http_method) { case LI_HTTP_METHOD_GET: case LI_HTTP_METHOD_HEAD: break; default: return LI_HANDLER_GO_ON; } if (li_vrequest_is_handled(vr)) return LI_HANDLER_GO_ON; if (vr->physical.path->len == 0) return LI_HANDLER_GO_ON; switch (li_stat_cache_get_dirlist(vr, vr->physical.path, &sce)) { case LI_HANDLER_GO_ON: break; case LI_HANDLER_WAIT_FOR_EVENT: return LI_HANDLER_WAIT_FOR_EVENT; default: return LI_HANDLER_ERROR; } dd = param; if (sce->data.failed) { /* stat failed */ int e = sce->data.err; li_stat_cache_entry_release(vr, sce); switch (e) { case ENOENT: case ENOTDIR: return LI_HANDLER_GO_ON; case EACCES: if (!li_vrequest_handle_direct(vr)) return LI_HANDLER_ERROR; vr->response.http_status = 403; return LI_HANDLER_GO_ON; default: VR_ERROR(vr, "stat('%s') failed: %s", sce->data.path->str, g_strerror(sce->data.err)); return LI_HANDLER_ERROR; } } else if (!S_ISDIR(sce->data.st.st_mode)) { li_stat_cache_entry_release(vr, sce); return LI_HANDLER_GO_ON; } else if (vr->request.uri.path->len == 0 || vr->request.uri.path->str[vr->request.uri.path->len-1] != '/') { li_stat_cache_entry_release(vr, sce); li_vrequest_redirect_directory(vr); return LI_HANDLER_GO_ON; } else { /* everything ok, we have the directory listing */ gboolean cachable; guint i, j; liStatCacheEntryData *sced; GString *mime_str, *encoded; GArray *directories, *files; gchar sizebuf[sizeof("999.9K")+1]; gchar datebuf[sizeof("2005-Jan-01 22:23:24")+1]; guint datebuflen; struct tm tm; gboolean hide; if (!li_vrequest_handle_direct(vr)) { li_stat_cache_entry_release(vr, sce); return LI_HANDLER_ERROR; } vr->response.http_status = 200; if (dd->debug) VR_DEBUG(vr, "dirlist for \"%s\", %u entries", sce->data.path->str, sce->dirlist->len); li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), GSTR_LEN(dd->content_type)); li_etag_set_header(vr, &sce->data.st, &cachable); if (cachable) { vr->response.http_status = 304; li_stat_cache_entry_release(vr, sce); return LI_HANDLER_GO_ON; } /* temporary string for encoded names */ encoded = g_string_sized_new(64-1); /* seperate directories from other files */ directories = g_array_sized_new(FALSE, FALSE, sizeof(guint), 16); files = g_array_sized_new(FALSE, FALSE, sizeof(guint), sce->dirlist->len); for (i = 0; i < sce->dirlist->len; i++) { sced = &g_array_index(sce->dirlist, liStatCacheEntryData, i); hide = FALSE; /* ingore entries where the stat() failed */ if (sced->failed) continue; if (dd->hide_dotfiles && sced->path->str[0] == '.') continue; if (dd->hide_tildefiles && sced->path->str[sced->path->len-1] == '~') continue; for (j = 0; j < dd->exclude_suffix->len; j++) { if (li_string_suffix(sced->path, GSTR_LEN((GString*)g_ptr_array_index(dd->exclude_suffix, j)))) { hide = TRUE; break; } } if (hide) continue; for (j = 0; j < dd->exclude_prefix->len; j++) { if (li_string_prefix(sced->path, GSTR_LEN((GString*)g_ptr_array_index(dd->exclude_prefix, j)))) { hide = TRUE; break; } } if (hide) continue; if (S_ISDIR(sced->st.st_mode)) { if (dd->hide_directories) continue; g_array_append_val(directories, i); } else { if ((dd->include_header || dd->hide_header) && g_str_equal(sced->path, "HEADER.txt")) { if (dd->hide_header) continue; } else if ((dd->include_readme || dd->hide_readme) && g_str_equal(sced->path, "README.txt")) { if (dd->hide_readme) continue; } g_array_append_val(files, i); } } listing = g_string_sized_new(4*1024-1); g_string_append_printf(listing, html_header_start, vr->request.uri.path->str); if (dd->css) { /* custom css */ g_string_append_len(listing, CONST_STR_LEN(" <link rel=\"stylesheet\" type=\"text/css\" href=\"")); g_string_append_len(listing, GSTR_LEN(dd->css)); g_string_append_len(listing, CONST_STR_LEN("\" />\n")); } else { /* default css */ g_string_append_len(listing, CONST_STR_LEN(html_css)); } g_string_append_len(listing, CONST_STR_LEN(html_header_end)); try_append_file(vr, &listing, "HEADER.txt", dd->encode_header); g_string_append_printf(listing, html_table_start, vr->request.uri.path->str); if (0 != strcmp("/", vr->request.uri.path->str)) { g_string_append_printf(listing, html_table_row, '0', "../", "Parent Directory", (gint64)0, "", (gint64)0, "-", "Directory"); } /* list directories */ if (!dd->hide_directories) { for (i = 0; i < directories->len; i++) { sced = &g_array_index(sce->dirlist, liStatCacheEntryData, g_array_index(directories, guint, i)); localtime_r(&(sced->st.st_mtime), &tm); datebuflen = strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm); datebuf[datebuflen] = '\0'; g_string_append_len(listing, CONST_STR_LEN(" <tr group=\"1\"><td><a href=\"")); li_string_encode(sced->path->str, encoded, LI_ENCODING_URI); g_string_append_len(listing, GSTR_LEN(encoded)); g_string_append_len(listing, CONST_STR_LEN("/\">")); li_string_encode(sced->path->str, encoded, LI_ENCODING_HTML); g_string_append_len(listing, GSTR_LEN(encoded)); g_string_append_len(listing, CONST_STR_LEN("</a></td><td class=\"modified\" val=\"")); li_string_append_int(listing, sced->st.st_mtime); g_string_append_len(listing, CONST_STR_LEN("\">")); g_string_append_len(listing, datebuf, datebuflen); g_string_append_len(listing, CONST_STR_LEN("</td>" "<td class=\"size\" val=\"0\">-</td>" "<td class=\"type\">Directory</td></tr>\n")); } } /*g_string_append_len(listing, CONST_STR_LEN("<tr><td colspan=\"4\"> </td></tr>\n"));*/ /* list files */ for (i = 0; i < files->len; i++) { sced = &g_array_index(sce->dirlist, liStatCacheEntryData, g_array_index(files, guint, i)); mime_str = li_mimetype_get(vr, sced->path); localtime_r(&(sced->st.st_mtime), &tm); datebuflen = strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm); datebuf[datebuflen] = '\0'; dirlist_format_size(sizebuf, sced->st.st_size); g_string_append_len(listing, CONST_STR_LEN(" <tr group=\"2\"><td><a href=\"")); li_string_encode(sced->path->str, encoded, LI_ENCODING_URI); g_string_append_len(listing, GSTR_LEN(encoded)); g_string_append_len(listing, CONST_STR_LEN("\">")); li_string_encode(sced->path->str, encoded, LI_ENCODING_HTML); g_string_append_len(listing, GSTR_LEN(encoded)); g_string_append_len(listing, CONST_STR_LEN( "</a></td>" "<td class=\"modified\" val=\"")); li_string_append_int(listing, sced->st.st_mtime); g_string_append_len(listing, CONST_STR_LEN("\">")); g_string_append_len(listing, datebuf, datebuflen); g_string_append_len(listing, CONST_STR_LEN("</td><td class=\"size\" val=\"")); li_string_append_int(listing, sced->st.st_size); g_string_append_len(listing, CONST_STR_LEN("\">")); g_string_append(listing, sizebuf); g_string_append_len(listing, CONST_STR_LEN("</td><td class=\"type\">")); if (mime_str) { g_string_append_len(listing, GSTR_LEN(mime_str)); } else { g_string_append_len(listing, CONST_STR_LEN("application/octet-stream")); } g_string_append_len(listing, CONST_STR_LEN("</td></tr>\n")); /* g_string_append_printf(listing, html_table_row, sced->path->str, sced->path->str, (gint64)sced->st.st_mtime, datebuf, sced->st.st_size, sizebuf, mime_str ? mime_str->str : "application/octet-stream"); */ } g_string_append_len(listing, CONST_STR_LEN(html_table_end)); try_append_file(vr, &listing, "README.txt", dd->encode_readme); if (dd->include_sort) { g_string_append_len(listing, CONST_STR_LEN(javascript_sort)); } g_string_append_printf(listing, html_footer, CORE_OPTIONPTR(LI_CORE_OPTION_SERVER_TAG).string->str); li_chunkqueue_append_string(vr->direct_out, listing); g_string_free(encoded, TRUE); g_array_free(directories, TRUE); g_array_free(files, TRUE); } li_stat_cache_entry_release(vr, sce); return LI_HANDLER_GO_ON; }
/* the CollectCallback */ static void progress_collect_cb(gpointer cbdata, gpointer fdata, GPtrArray *result, gboolean complete) { guint i; GString *output; mod_progress_node *node = NULL; mod_progress_job *job = fdata; liVRequest *vr = job->vr; gboolean debug = job->debug; mod_progress_format format = job->format; UNUSED(cbdata); if (complete) { /* clear context so it doesn't get cleaned up anymore */ *(job->context) = NULL; for (i = 0; i < result->len; i++) { node = g_ptr_array_index(result, i); if (node) break; } output = g_string_sized_new(128); /* send mime-type. there seems to be no standard for javascript... using the most commong */ li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/x-javascript")); if (format == PROGRESS_FORMAT_LEGACY) { g_string_append_len(output, CONST_STR_LEN("new Object(")); } else if (format == PROGRESS_FORMAT_JSONP) { gchar *val; guint len; if (li_querystring_find(vr->request.uri.query, CONST_STR_LEN("X-Progress-Callback"), &val, &len)) { /* X-Progress-Callback specified, need to check for xss */ gchar *c; for (c = val; c != val+len; c++) { if ((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') || (*c >= '0' && *c <= '9') || *c == '.' || *c == '_') continue; break; } /* was there a bad char? */ if (c != val+len) { g_string_append_len(output, CONST_STR_LEN("progress(")); } else { g_string_append_len(output,val, len); g_string_append_c(output, '('); } } else { g_string_append_len(output, CONST_STR_LEN("progress(")); } } if (!node) { /* progress id not known */ if (debug) VR_DEBUG(vr, "progress.show: progress id \"%s\" unknown", job->id); g_string_append_len(output, CONST_STR_LEN("{\"state\": \"unknown\"}")); } else { if (debug) VR_DEBUG(vr, "progress.show: progress id \"%s\" found", job->id); if (node->vr) { /* still in progress */ g_string_append_printf(output, "{\"state\": \"running\", \"received\": %"G_GUINT64_FORMAT", \"sent\": %"G_GUINT64_FORMAT", \"request_size\": %"G_GUINT64_FORMAT", \"response_size\": %"G_GUINT64_FORMAT"}", node->bytes_in, node->bytes_out, node->request_size, node->response_size ); } else if (node->status_code == 200) { /* done, success */ g_string_append_printf(output, "{\"state\": \"done\", \"received\": %"G_GUINT64_FORMAT", \"sent\": %"G_GUINT64_FORMAT", \"request_size\": %"G_GUINT64_FORMAT", \"response_size\": %"G_GUINT64_FORMAT"}", node->bytes_in, node->bytes_out, node->request_size, node->response_size ); } else { /* done, error */ g_string_append_printf(output, "{\"state\": \"error\", \"status\": %d}", node->status_code ); } } if (format == PROGRESS_FORMAT_LEGACY || format == PROGRESS_FORMAT_JSONP) { g_string_append_c(output, ')'); } vr->response.http_status = 200; li_chunkqueue_append_string(vr->out, output); li_vrequest_handle_direct(vr); li_vrequest_joblist_append(vr); } /* free results */ for (i = 0; i < result->len; i++) { if (g_ptr_array_index(result, i)) g_slice_free(mod_progress_node, g_ptr_array_index(result, i)); } g_free(job->id); g_slice_free(mod_progress_job, job); }
static liHandlerResult auth_basic(liVRequest *vr, gpointer param, gpointer *context) { liHttpHeader *hdr; gboolean auth_ok = FALSE; AuthBasicData *bdata = param; gboolean debug = _OPTION(vr, bdata->p, 0).boolean; UNUSED(context); if (li_vrequest_is_handled(vr)) { if (debug || CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "skipping auth.basic as request is already handled with current status %i", vr->response.http_status); } return LI_HANDLER_GO_ON; } /* check for Authorization header */ hdr = li_http_header_lookup(vr->request.headers, CONST_STR_LEN("Authorization")); if (!hdr || !g_str_has_prefix(LI_HEADER_VALUE(hdr), "Basic ")) { if (debug) { VR_DEBUG(vr, "requesting authorization from client for realm \"%s\"", bdata->realm->str); } } else { gchar *decoded, *username = NULL, *password; size_t len; /* auth_info contains username:password encoded in base64 */ if (NULL != (decoded = (gchar*)g_base64_decode(LI_HEADER_VALUE(hdr) + sizeof("Basic ") - 1, &len))) { /* bogus data? */ if (NULL != (password = strchr(decoded, ':'))) { *password = '******'; password++; username = decoded; } else { g_free(decoded); } } if (!username) { if (debug) { VR_DEBUG(vr, "couldn't parse authorization info from client for realm \"%s\"", bdata->realm->str); } } else { GString user = li_const_gstring(username, password - username - 1); GString pass = li_const_gstring(password, len - (password - username)); if (bdata->backend(vr, &user, &pass, bdata, debug)) { auth_ok = TRUE; li_environment_set(&vr->env, CONST_STR_LEN("REMOTE_USER"), username, password - username - 1); li_environment_set(&vr->env, CONST_STR_LEN("AUTH_TYPE"), CONST_STR_LEN("Basic")); } else { if (debug) { VR_DEBUG(vr, "wrong authorization info from client on realm \"%s\" (user: \"%s\")", bdata->realm->str, username); } } g_free(decoded); } } g_string_truncate(vr->wrk->tmp_str, 0); g_string_append_len(vr->wrk->tmp_str, CONST_STR_LEN("Basic realm=\"")); g_string_append_len(vr->wrk->tmp_str, GSTR_LEN(bdata->realm)); g_string_append_c(vr->wrk->tmp_str, '"'); /* generate header always */ if (!auth_ok) { li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("WWW-Authenticate"), GSTR_LEN(vr->wrk->tmp_str)); /* we already checked for handled */ if (!li_vrequest_handle_direct(vr)) return LI_HANDLER_ERROR; vr->response.http_status = 401; return LI_HANDLER_GO_ON; } else { /* lets hope browser just ignore the header if status is not 401 * but this way it is easier to use a later "auth.deny;" */ li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("WWW-Authenticate"), GSTR_LEN(vr->wrk->tmp_str)); } if (debug) { VR_DEBUG(vr, "client authorization successful for realm \"%s\"", bdata->realm->str); } return LI_HANDLER_GO_ON; }
static liHandlerResult flv(liVRequest *vr, gpointer param, gpointer *context) { gchar *start; guint len; goffset pos; liHandlerResult res; gboolean cachable; struct stat st; int err; int fd = -1; UNUSED(context); UNUSED(param); if (li_vrequest_is_handled(vr)) return LI_HANDLER_GO_ON; res = li_stat_cache_get(vr, vr->physical.path, &st, &err, &fd); if (res == LI_HANDLER_WAIT_FOR_EVENT) return res; if (res == LI_HANDLER_ERROR) { /* open or fstat failed */ if (fd != -1) close(fd); if (!li_vrequest_handle_direct(vr)) return LI_HANDLER_ERROR; switch (err) { case ENOENT: case ENOTDIR: vr->response.http_status = 404; return LI_HANDLER_GO_ON; case EACCES: vr->response.http_status = 403; return LI_HANDLER_GO_ON; default: VR_ERROR(vr, "stat() or open() for '%s' failed: %s", vr->physical.path->str, g_strerror(err)); return LI_HANDLER_ERROR; } } else if (S_ISDIR(st.st_mode)) { if (fd != -1) close(fd); return LI_HANDLER_GO_ON; } else if (!S_ISREG(st.st_mode)) { if (fd != -1) close(fd); if (!li_vrequest_handle_direct(vr)) return LI_HANDLER_ERROR; vr->response.http_status = 403; } else { liChunkFile *cf; #ifdef FD_CLOEXEC fcntl(fd, F_SETFD, FD_CLOEXEC); #endif if (!li_vrequest_handle_direct(vr)) { close(fd); return LI_HANDLER_ERROR; } if (li_querystring_find(vr->request.uri.query, CONST_STR_LEN("start"), &start, &len)) { guint i; pos = 0; for (i = 0; i < len; i++) { if (start[i] >= '0' && start[i] <= '9') { pos *= 10; pos += start[i] - '0'; } } } else { pos = 0; } li_etag_set_header(vr, &st, &cachable); if (cachable) { vr->response.http_status = 304; close(fd); return LI_HANDLER_GO_ON; } vr->response.http_status = 200; li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("video/x-flv")); if (pos < 0 || pos > st.st_size) pos = 0; if (pos != 0) li_chunkqueue_append_mem(vr->direct_out, CONST_STR_LEN("FLV\x1\x1\0\0\0\x9\0\0\0\x9")); cf = li_chunkfile_new(NULL, fd, FALSE); li_chunkqueue_append_chunkfile(vr->direct_out, cf, pos, st.st_size - pos); li_chunkfile_release(cf); } return LI_HANDLER_GO_ON; }
gboolean li_response_send_headers(liConnection *con) { GString *head; liVRequest *vr = con->mainvr; if (vr->response.http_status < 100 || vr->response.http_status > 999) { VR_ERROR(vr, "wrong status: %i", vr->response.http_status); return FALSE; } head = g_string_sized_new(8*1024-1); if (0 == con->out->length && con->mainvr->backend == NULL && vr->response.http_status >= 400 && vr->response.http_status < 600) { li_response_send_error_page(con); } if ((vr->response.http_status >= 100 && vr->response.http_status < 200) || vr->response.http_status == 204 || vr->response.http_status == 205 || vr->response.http_status == 304) { /* They never have a content-body/length */ li_chunkqueue_reset(con->out); con->out->is_closed = TRUE; con->raw_out->is_closed = TRUE; } else if (con->out->is_closed) { if (vr->request.http_method != LI_HTTP_METHOD_HEAD || con->out->length > 0) { /* do not send content-length: 0 if backend already skipped content generation for HEAD */ g_string_printf(con->wrk->tmp_str, "%"L_GOFFSET_FORMAT, con->out->length); li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Length"), GSTR_LEN(con->wrk->tmp_str)); } } else if (con->info.keep_alive && vr->request.http_version == LI_HTTP_VERSION_1_1) { /* TODO: maybe someone set a content length header? */ if (!(vr->response.transfer_encoding & LI_HTTP_TRANSFER_ENCODING_CHUNKED)) { vr->response.transfer_encoding |= LI_HTTP_TRANSFER_ENCODING_CHUNKED; li_http_header_append(vr->response.headers, CONST_STR_LEN("Transfer-Encoding"), CONST_STR_LEN("chunked")); } } else { /* Unknown content length, no chunked encoding */ con->info.keep_alive = FALSE; } if (vr->request.http_method == LI_HTTP_METHOD_HEAD) { /* content-length is set, but no body */ li_chunkqueue_reset(con->out); con->out->is_closed = TRUE; con->raw_out->is_closed = TRUE; } /* Status line */ if (vr->request.http_version == LI_HTTP_VERSION_1_1) { g_string_append_len(head, CONST_STR_LEN("HTTP/1.1 ")); if (!con->info.keep_alive) li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Connection"), CONST_STR_LEN("close")); } else { g_string_append_len(head, CONST_STR_LEN("HTTP/1.0 ")); if (con->info.keep_alive) li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Connection"), CONST_STR_LEN("keep-alive")); } { guint len; gchar status_str[4]; gchar *str = li_http_status_string(vr->response.http_status, &len); li_http_status_to_str(vr->response.http_status, status_str); status_str[3] = ' '; g_string_append_len(head, status_str, 4); g_string_append_len(head, str, len); g_string_append_len(head, CONST_STR_LEN("\r\n")); } /* Append headers */ { liHttpHeader *header; GList *iter; gboolean have_date = FALSE, have_server = FALSE; for (iter = g_queue_peek_head_link(&vr->response.headers->entries); iter; iter = g_list_next(iter)) { header = (liHttpHeader*) iter->data; g_string_append_len(head, GSTR_LEN(header->data)); g_string_append_len(head, CONST_STR_LEN("\r\n")); if (!have_date && li_http_header_key_is(header, CONST_STR_LEN("date"))) have_date = TRUE; if (!have_server && li_http_header_key_is(header, CONST_STR_LEN("server"))) have_server = TRUE; } if (!have_date) { GString *d = li_worker_current_timestamp(con->wrk, LI_GMTIME, LI_TS_FORMAT_HEADER); /* HTTP/1.1 requires a Date: header */ g_string_append_len(head, CONST_STR_LEN("Date: ")); g_string_append_len(head, GSTR_LEN(d)); g_string_append_len(head, CONST_STR_LEN("\r\n")); } if (!have_server) { GString *tag = CORE_OPTIONPTR(LI_CORE_OPTION_SERVER_TAG).string; if (tag->len) { g_string_append_len(head, CONST_STR_LEN("Server: ")); g_string_append_len(head, GSTR_LEN(tag)); g_string_append_len(head, CONST_STR_LEN("\r\n")); } } } g_string_append_len(head, CONST_STR_LEN("\r\n")); li_chunkqueue_append_string(con->raw_out, head); return TRUE; }
void li_response_send_error_page(liConnection *con) { liVRequest *vr = con->mainvr; gchar status_code[3]; guint len; gchar *str; GString *html; html = g_string_sized_new(1023); g_string_append_len(html, CONST_STR_LEN( "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n" "<html>\n" " <head>\n" " <title>" )); li_http_status_to_str(con->mainvr->response.http_status, status_code); g_string_append_len(html, status_code, 3); g_string_append_len(html, CONST_STR_LEN(" - ")); str = li_http_status_string(con->mainvr->response.http_status, &len); g_string_append_len(html, str, len); g_string_append_len(html, CONST_STR_LEN( "</title>\n" " <style type=\"text/css\">\n" " body { font-size: 62.5%; }\n" " #container {\n" " font-size: 62.5%;\n" " max-width: 600px;\n" " margin: auto;\n" " margin-top: 2%;\n" " border: 4px solid #efefef;\n" " padding: 0px 20px;\n" " color: #444;\n" " font-family: Verdana,helvetica,sans-serif;\n" " font-size: 1.25em;\n" " }\n" " h1 { color: #6D84B4; font-size: 1.5em; }\n" " #footer { text-align: right; margin-top: 25px; }\n" " </style>\n" " </head>\n" " <body>\n" " <div id=\"container\">\n" " <h1>Error " )); g_string_append_len(html, status_code, 3); g_string_append_len(html, CONST_STR_LEN(" - ")); g_string_append_len(html, str, len); g_string_append_len(html, CONST_STR_LEN("</h1>\n")); str = li_response_error_description(con->mainvr->response.http_status, &len); g_string_append_len(html, str, len); g_string_append_len(html, CONST_STR_LEN(" <p id=\"footer\">")); g_string_append_len(html, GSTR_LEN(CORE_OPTIONPTR(LI_CORE_OPTION_SERVER_TAG).string)); g_string_append_len(html, CONST_STR_LEN( "</p>\n" " </div>\n" " </body>\n" "</html>\n" )); li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html; charset=utf-8")); li_chunkqueue_append_string(con->out, html); li_http_header_remove(vr->response.headers, CONST_STR_LEN("transfer-encoding")); li_http_header_remove(vr->response.headers, CONST_STR_LEN("content-encoding")); li_http_header_remove(vr->response.headers, CONST_STR_LEN("etag")); }
static liHandlerResult cache_etag_handle(liVRequest *vr, gpointer param, gpointer *context) { cache_etag_context *ctx = (cache_etag_context*) param; cache_etag_file *cfile = (cache_etag_file*) *context; GList *etag_entry; liHttpHeader *etag; struct stat st; GString *tmp_str = vr->wrk->tmp_str; liHandlerResult res; int err, fd; if (!cfile) { if (vr->request.http_method != LI_HTTP_METHOD_GET) return LI_HANDLER_GO_ON; LI_VREQUEST_WAIT_FOR_RESPONSE_HEADERS(vr); if (vr->response.http_status != 200) return LI_HANDLER_GO_ON; /* Don't cache static files if filter list is empty */ if (NULL == vr->filters_out_first && vr->backend_source->out->is_closed && 0 == vr->backend_source->out->mem_usage) return LI_HANDLER_GO_ON; etag_entry = li_http_header_find_first(vr->response.headers, CONST_STR_LEN("etag")); if (!etag_entry) return LI_HANDLER_GO_ON; /* no etag -> no caching */ if (li_http_header_find_next(etag_entry, CONST_STR_LEN("etag"))) { VR_ERROR(vr, "%s", "duplicate etag header in response, will not cache it"); return LI_HANDLER_GO_ON; } etag = (liHttpHeader*) etag_entry->data; cfile = cache_etag_file_create(createFileName(vr, ctx->path, etag)); *context = cfile; } res = li_stat_cache_get(vr, cfile->filename, &st, &err, &fd); if (res == LI_HANDLER_WAIT_FOR_EVENT) return res; if (res == LI_HANDLER_GO_ON) { liFilter *f; if (!S_ISREG(st.st_mode)) { VR_ERROR(vr, "Unexpected file type for cache file '%s' (mode %o)", cfile->filename->str, (unsigned int) st.st_mode); close(fd); return LI_HANDLER_GO_ON; /* no caching */ } cfile->hit_fd = fd; #ifdef FD_CLOEXEC fcntl(cfile->hit_fd, F_SETFD, FD_CLOEXEC); #endif if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "cache hit for '%s'", vr->request.uri.path->str); } cfile->hit_length = st.st_size; g_string_truncate(tmp_str, 0); li_string_append_int(tmp_str, st.st_size); li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Length"), GSTR_LEN(tmp_str)); f = li_vrequest_add_filter_out(vr, cache_etag_filter_hit, NULL, NULL, NULL); if (NULL != f) { li_chunkqueue_append_file_fd(f->out, NULL, 0, cfile->hit_length, cfile->hit_fd); f->out->is_closed = TRUE; cfile->hit_fd = -1; } cache_etag_file_free(cfile); *context = NULL; return LI_HANDLER_GO_ON; } if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "cache miss for '%s'", vr->request.uri.path->str); } if (!cache_etag_file_start(vr, cfile)) { cache_etag_file_free(cfile); *context = NULL; return LI_HANDLER_GO_ON; /* no caching */ } li_vrequest_add_filter_out(vr, cache_etag_filter_miss, cache_etag_filter_free, NULL, cfile); *context = NULL; return LI_HANDLER_GO_ON; }