int http_chunk_append_file(server * srv, connection * con, buffer * fn, off_t offset, off_t len) { chunkqueue *cq; if (!con) return -1; cq = con->write_queue; if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { http_chunk_append_len(srv, con, len); } chunkqueue_append_file(cq, fn, offset, len); if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED && len > 0) { chunkqueue_append_mem(cq, "\r\n", 2 + 1); } return 0; }
void chunkqueue_steal(chunkqueue *dest, chunkqueue *src, off_t len) { while (len > 0) { chunk *c = src->first; off_t clen = 0, use; if (NULL == c) break; switch (c->type) { case MEM_CHUNK: clen = buffer_string_length(c->mem); break; case FILE_CHUNK: clen = c->file.length; break; } force_assert(clen >= c->offset); clen -= c->offset; use = len >= clen ? clen : len; src->bytes_out += use; dest->bytes_in += use; len -= use; if (0 == clen) { /* drop empty chunk */ src->first = c->next; if (c == src->last) src->last = NULL; chunkqueue_push_unused_chunk(src, c); continue; } if (use == clen) { /* move complete chunk */ src->first = c->next; if (c == src->last) src->last = NULL; chunkqueue_append_chunk(dest, c); continue; } /* partial chunk with length "use" */ switch (c->type) { case MEM_CHUNK: chunkqueue_append_mem(dest, c->mem->ptr + c->offset, use); break; case FILE_CHUNK: /* tempfile flag is in "last" chunk after the split */ chunkqueue_append_file(dest, c->file.name, c->file.start + c->offset, use); break; } c->offset += use; force_assert(0 == len); } }
int http_chunk_append_file(server *srv, connection *con, buffer *fn, off_t offset, off_t len) { chunkqueue *cq; if (!con) return -1; Cdbg(DBE, "enter http_chunk_append_file..with fd=[%d], fn=[%s], offset=[%d], len=[%d]", con->fd, fn->ptr, offset, len); cq = con->write_queue; if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED) { http_chunk_append_len(srv, con, len); } chunkqueue_append_file(cq, fn, offset, len); if (con->response.transfer_encoding & HTTP_TRANSFER_ENCODING_CHUNKED && len > 0) { chunkqueue_append_mem(cq, "\r\n", 2 + 1); } Cdbg(DBE, "leave"); return 0; }
/** * move the content of chunk to another chunkqueue. return total bytes copied/stolen. */ off_t chunkqueue_steal_chunk(chunkqueue *cq, chunk *c) { /* we are copying the whole buffer, just steal it */ off_t total = 0; buffer *b, btmp; if (!cq) return 0; if (chunk_is_done(c)) return 0; switch (c->type) { case MEM_CHUNK: total = c->mem->used - c->offset - 1; if (c->offset == 0) { b = chunkqueue_get_append_buffer(cq); btmp = *b; *b = *(c->mem); *(c->mem) = btmp; } else { chunkqueue_append_mem(cq, c->mem->ptr + c->offset, total); chunk_set_done(c); } break; case FILE_CHUNK: total = c->file.length - c->offset; if (c->file.is_temp) { chunkqueue_steal_tempfile(cq, c); } else { chunkqueue_append_file(cq, c->file.name, c->file.start + c->offset, c->file.length - c->offset); chunk_set_done(c); } break; case UNUSED_CHUNK: return 0; } return total; }
/** * walk through the content array * * content = { "<pre>", { file = "/content" } , "</pre>" } * * header["Content-Type"] = "text/html" * * return 200 */ static int magnet_attach_content(server *srv, connection *con, lua_State *L, int lighty_table_ndx) { force_assert(lua_istable(L, lighty_table_ndx)); lua_getfield(L, lighty_table_ndx, "content"); /* lighty.content */ if (lua_istable(L, -1)) { int i; /* content is found, and is a table */ for (i = 1; ; i++) { lua_rawgeti(L, -1, i); /* -1 is the value and should be the value ... aka a table */ if (lua_isstring(L, -1)) { const_buffer data = magnet_checkconstbuffer(L, -1); chunkqueue_append_mem(con->write_queue, data.ptr, data.len); } else if (lua_istable(L, -1)) { lua_getfield(L, -1, "filename"); lua_getfield(L, -2, "length"); lua_getfield(L, -3, "offset"); if (lua_isstring(L, -3)) { /* filename has to be a string */ buffer *fn; stat_cache_entry *sce; handler_t res; fn = magnet_checkbuffer(L, -3); res = stat_cache_get_entry(srv, con, fn, &sce); if (HANDLER_GO_ON == res) { off_t off = (off_t) luaL_optinteger(L, -1, 0); off_t len = (off_t) luaL_optinteger(L, -2, (lua_Integer) sce->st.st_size); if (off < 0) { buffer_free(fn); return luaL_error(L, "offset for '%s' is negative", lua_tostring(L, -3)); } if (len < off) { buffer_free(fn); return luaL_error(L, "offset > length for '%s'", lua_tostring(L, -3)); } chunkqueue_append_file(con->write_queue, fn, off, len - off); } buffer_free(fn); } else { return luaL_error(L, "content[%d] is a table and requires the field \"filename\"", i); } lua_pop(L, 3); } else if (lua_isnil(L, -1)) { /* end of list */ lua_pop(L, 1); break; } else { return luaL_error(L, "content[%d] is neither a string nor a table: ", i); } lua_pop(L, 1); /* pop the content[...] entry value */ } } else { return luaL_error(L, "lighty.content has to be a table"); } lua_pop(L, 1); /* pop lighty.content */ return 0; }
/** * walk through the content array * * content = { "<pre>", { file = "/content" } , "</pre>" } * * header["Content-Type"] = "text/html" * * return 200 */ static int magnet_attach_content(server *srv, connection *con, plugin_data *p, lua_State *L) { UNUSED(p); /** * get the environment of the function */ assert(lua_isfunction(L, -1)); lua_getfenv(L, -1); /* -1 is the function */ lua_getfield(L, -1, "lighty"); /* lighty.* from the env */ assert(lua_istable(L, -1)); lua_getfield(L, -1, "content"); /* lighty.content */ if (lua_istable(L, -1)) { int i; /* header is found, and is a table */ for (i = 1; ; i++) { lua_rawgeti(L, -1, i); /* -1 is the value and should be the value ... aka a table */ if (lua_isstring(L, -1)) { size_t s_len = 0; const char *s = lua_tolstring(L, -1, &s_len); chunkqueue_append_mem(con->write_queue, s, s_len + 1); } else if (lua_istable(L, -1)) { lua_getfield(L, -1, "filename"); lua_getfield(L, -2, "length"); lua_getfield(L, -3, "offset"); if (lua_isstring(L, -3)) { /* filename has to be a string */ buffer *fn = buffer_init(); stat_cache_entry *sce; buffer_copy_string(fn, lua_tostring(L, -3)); if (HANDLER_GO_ON == stat_cache_get_entry(srv, con, fn, &sce)) { off_t off = 0; off_t len = 0; if (lua_isnumber(L, -1)) { off = lua_tonumber(L, -1); } if (lua_isnumber(L, -2)) { len = lua_tonumber(L, -2); } else { len = sce->st.st_size; } if (off < 0) { return luaL_error(L, "offset for '%s' is negative", fn->ptr); } if (len < off) { return luaL_error(L, "offset > length for '%s'", fn->ptr); } chunkqueue_append_file(con->write_queue, fn, off, len - off); } buffer_free(fn); } else { lua_pop(L, 3 + 2); /* correct the stack */ return luaL_error(L, "content[%d] is a table and requires the field \"filename\"", i); } lua_pop(L, 3); } else if (lua_isnil(L, -1)) { /* oops, end of list */ lua_pop(L, 1); break; } else { lua_pop(L, 4); return luaL_error(L, "content[%d] is neither a string nor a table: ", i); } lua_pop(L, 1); /* pop the content[...] table */ } } else { return luaL_error(L, "lighty.content has to be a table"); } lua_pop(L, 1); /* pop the header-table */ lua_pop(L, 1); /* pop the lighty-table */ lua_pop(L, 1); /* php the function env */ return 0; }
/* * 处理静态页面。 * */ static handler_t response_handle_static_file(server *srv, connection *con) { if (NULL == srv || NULL == con) { return HANDLER_ERROR; } if (con -> http_status >= 400 && con -> http_status < 600) { //请求处理过程中有错误, //不需要处理静态文件,服务器返回错误提示。 return HANDLER_FINISHED; } buffer *file = con -> physical.real_path; log_error_write(srv, __FILE__, __LINE__, "sb", "Static file:", file); struct stat s; //获取文件的长度。 if (-1 == lstat(file -> ptr, &s)) { //这也不会出错。。。 switch(errno) { case EACCES: /* * 无法获得资源。权限不够。 */ con -> http_status = 403; buffer_reset(con -> physical.path); return -1; case ENOENT: /* * 资源不存在。 */ con -> http_status = 404; buffer_reset(con->physical.path); return -1; case ENOTDIR: /* * 资源不存在。 */ return -1; default: con -> http_status = 500; log_error_write(srv, __FILE__, __LINE__, "ss", "stat error. ", strerror(errno)); return -1; } } buffer_reset(con -> tmp_buf); buffer_append_long(con -> tmp_buf, s.st_size); http_response_insert_header(srv, con, CONST_STR_LEN("Content-Length") , con -> tmp_buf -> ptr, con -> tmp_buf -> used - 1); chunkqueue_append_file(con -> write_queue, file, 0, s.st_size); log_error_write(srv, __FILE__, __LINE__, "sd", "static file len:" , s.st_size); /* * 根据文件的扩展名确定Content-Type */ char *ext; ext = file -> ptr + file -> used - 1; while(*ext != '/' && *ext != '.') { //文件路径中至少包含一个'/',因此不会越界。 --ext; } if(*ext == '/') { /* * 资源没有扩展名。使用默认类型。 */ log_error_write(srv, __FILE__, __LINE__, "s", "File has no extention name..."); http_response_insert_header(srv, con, CONST_STR_LEN("Content-Type") , CONST_STR_LEN("application/octet-stream")); } else { content_type_map *c; int done = 0; for (c = srv -> srvconf.c_t_map; c -> file_ext; ++c) { if(0 == strncasecmp(ext + 1 , c -> file_ext + 1, strlen(c -> file_ext) - 1)) { log_error_write(srv, __FILE__, __LINE__, "ssss", "File ext:", ext, "Content_t:", c -> content_type); http_response_insert_header(srv, con, CONST_STR_LEN("Content-Type") , c -> content_type, strlen(c -> content_type)); done = 1; break; } } if(!done) { //未知扩展名。 http_response_insert_header(srv, con, CONST_STR_LEN("Content-Type") , CONST_STR_LEN("application/octet-stream")); } } return HANDLER_FINISHED; }
int cache_parse_lua(server *srv, connection *con, plugin_data *p, buffer *fn) { lua_State *L; readme rm; int ret = -1; buffer *b = buffer_init(); int header_tbl = 0; rm.done = 0; stream_open(&rm.st, fn); /* push the lua file to the interpreter and see what happends */ L = luaL_newstate(); luaL_openlibs(L); /* register functions */ lua_register(L, "md5", f_crypto_md5); lua_register(L, "file_mtime", f_file_mtime); lua_register(L, "file_isreg", f_file_isreg); lua_register(L, "file_isdir", f_file_isreg); lua_register(L, "dir_files", f_dir_files); #ifdef HAVE_MEMCACHE_H lua_pushliteral(L, "memcache_get_long"); lua_pushlightuserdata(L, p->conf.mc); lua_pushcclosure(L, f_memcache_get_long, 1); lua_settable(L, LUA_GLOBALSINDEX); lua_pushliteral(L, "memcache_get_string"); lua_pushlightuserdata(L, p->conf.mc); lua_pushcclosure(L, f_memcache_get_string, 1); lua_settable(L, LUA_GLOBALSINDEX); lua_pushliteral(L, "memcache_exists"); lua_pushlightuserdata(L, p->conf.mc); lua_pushcclosure(L, f_memcache_exists, 1); lua_settable(L, LUA_GLOBALSINDEX); #endif /* register CGI environment */ lua_pushliteral(L, "request"); lua_newtable(L); lua_settable(L, LUA_GLOBALSINDEX); lua_pushliteral(L, "request"); header_tbl = lua_gettop(L); lua_gettable(L, LUA_GLOBALSINDEX); c_to_lua_push(L, header_tbl, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri)); c_to_lua_push(L, header_tbl, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path)); c_to_lua_push(L, header_tbl, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(con->physical.path)); c_to_lua_push(L, header_tbl, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root)); if (!buffer_is_empty(con->request.pathinfo)) { c_to_lua_push(L, header_tbl, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo)); } c_to_lua_push(L, header_tbl, CONST_STR_LEN("CWD"), CONST_BUF_LEN(p->basedir)); c_to_lua_push(L, header_tbl, CONST_STR_LEN("BASEURL"), CONST_BUF_LEN(p->baseurl)); /* register GET parameter */ lua_pushliteral(L, "get"); lua_newtable(L); lua_settable(L, LUA_GLOBALSINDEX); lua_pushliteral(L, "get"); header_tbl = lua_gettop(L); lua_gettable(L, LUA_GLOBALSINDEX); buffer_copy_string_buffer(b, con->uri.query); cache_export_get_params(L, header_tbl, b); buffer_reset(b); /* 2 default constants */ lua_pushliteral(L, "CACHE_HIT"); lua_pushnumber(L, 0); lua_settable(L, LUA_GLOBALSINDEX); lua_pushliteral(L, "CACHE_MISS"); lua_pushnumber(L, 1); lua_settable(L, LUA_GLOBALSINDEX); /* load lua program */ if (lua_load(L, load_file, &rm, fn->ptr) || lua_pcall(L,0,1,0)) { log_error_write(srv, __FILE__, __LINE__, "s", lua_tostring(L,-1)); goto error; } /* get return value */ ret = (int)lua_tonumber(L, -1); lua_pop(L, 1); /* fetch the data from lua */ lua_to_c_get_string(L, "trigger_handler", p->trigger_handler); if (0 == lua_to_c_get_string(L, "output_contenttype", b)) { response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(b)); } if (ret == 0) { /* up to now it is a cache-hit, check if all files exist */ int curelem; time_t mtime = 0; if (!lua_to_c_is_table(L, "output_include")) { log_error_write(srv, __FILE__, __LINE__, "s", "output_include is missing or not a table"); ret = -1; goto error; } lua_pushstring(L, "output_include"); curelem = lua_gettop(L); lua_gettable(L, LUA_GLOBALSINDEX); /* HOW-TO build a etag ? * as we don't just have one file we have to take the stat() * from all base files, merge them and build the etag from * it later. * * The mtime of the content is the mtime of the freshest base file * * */ lua_pushnil(L); /* first key */ while (lua_next(L, curelem) != 0) { stat_cache_entry *sce = NULL; /* key' is at index -2 and value' at index -1 */ if (lua_isstring(L, -1)) { const char *s = lua_tostring(L, -1); /* the file is relative, make it absolute */ if (s[0] != '/') { buffer_copy_string_buffer(b, p->basedir); buffer_append_string(b, lua_tostring(L, -1)); } else { buffer_copy_string(b, lua_tostring(L, -1)); } if (HANDLER_ERROR == stat_cache_get_entry(srv, con, b, &sce)) { /* stat failed */ switch(errno) { case ENOENT: /* a file is missing, call the handler to generate it */ if (!buffer_is_empty(p->trigger_handler)) { ret = 1; /* cache-miss */ log_error_write(srv, __FILE__, __LINE__, "s", "a file is missing, calling handler"); break; } else { /* handler not set -> 500 */ ret = -1; log_error_write(srv, __FILE__, __LINE__, "s", "a file missing and no handler set"); break; } break; default: break; } } else { chunkqueue_append_file(con->write_queue, b, 0, sce->st.st_size); if (sce->st.st_mtime > mtime) mtime = sce->st.st_mtime; } } else { /* not a string */ ret = -1; log_error_write(srv, __FILE__, __LINE__, "s", "not a string"); break; } lua_pop(L, 1); /* removes value'; keeps key' for next iteration */ } lua_settop(L, curelem - 1); if (ret == 0) { data_string *ds; char timebuf[sizeof("Sat, 23 Jul 2005 21:20:01 GMT")]; buffer tbuf; con->file_finished = 1; ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"); /* no Last-Modified specified */ if ((mtime) && (NULL == ds)) { strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&mtime)); response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), timebuf, sizeof(timebuf) - 1); tbuf.ptr = timebuf; tbuf.used = sizeof(timebuf); tbuf.size = sizeof(timebuf); } else if (ds) { tbuf.ptr = ds->value->ptr; tbuf.used = ds->value->used; tbuf.size = ds->value->size; } else { tbuf.size = 0; tbuf.used = 0; tbuf.ptr = NULL; } if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, &tbuf)) { /* ok, the client already has our content, * no need to send it again */ chunkqueue_reset(con->write_queue); ret = 0; /* cache-hit */ } } else { chunkqueue_reset(con->write_queue); } } if (ret == 1 && !buffer_is_empty(p->trigger_handler)) { /* cache-miss */ buffer_copy_string_buffer(con->uri.path, p->baseurl); buffer_append_string_buffer(con->uri.path, p->trigger_handler); buffer_copy_string_buffer(con->physical.path, p->basedir); buffer_append_string_buffer(con->physical.path, p->trigger_handler); chunkqueue_reset(con->write_queue); } error: lua_close(L); stream_close(&rm.st); buffer_free(b); return ret /* cache-error */; }
/* * copy/steal max_len bytes from chunk chain. return total bytes copied/stolen. * */ off_t chunkqueue_steal_chunks_len(chunkqueue *out, chunk *c, off_t max_len) { off_t total = 0; off_t we_have = 0, we_want = 0; buffer *b; if (!out || !c) return 0; /* copy/steal chunks */ for (; c && max_len > 0; c = c->next) { switch (c->type) { case FILE_CHUNK: we_have = c->file.length - c->offset; if (we_have == 0) break; if (we_have > max_len) we_have = max_len; chunkqueue_append_file(out, c->file.name, c->offset, we_have); c->offset += we_have; max_len -= we_have; total += we_have; /* steal the tempfile * * This is tricky: * - we reference the tempfile from the in-queue several times * if the chunk is larger than max_len * - we can't simply cleanup the in-queue as soon as possible * as it would remove the tempfiles * - the idea is to 'steal' the tempfiles and attach the is_temp flag to the last * referencing chunk of the fastcgi-write-queue * */ if (c->offset == c->file.length) { chunk *out_c; out_c = out->last; /* the last of the out-queue should be a FILE_CHUNK (we just created it) * and the incoming side should have given use a temp-file-chunk */ assert(out_c->type == FILE_CHUNK); assert(c->file.is_temp == 1); out_c->file.is_temp = 1; c->file.is_temp = 0; } break; case MEM_CHUNK: /* skip empty chunks */ if (c->mem->used == 0) break; we_have = c->mem->used - c->offset - 1; if (we_have == 0) break; we_want = we_have < max_len ? we_have : max_len; if (we_have == we_want) { /* steal whole chunk */ chunkqueue_steal_chunk(out, c); } else { /* copy unused data from chunk */ b = chunkqueue_get_append_buffer(out); buffer_copy_string_len(b, c->mem->ptr + c->offset, we_want); c->offset += we_want; } total += we_want; max_len -= we_want; break; default: break; } } return total; }
static int proxy_create_env(server *srv, handler_ctx *hctx) { size_t i; connection *con = hctx->remote_conn; buffer *b; /* build header */ b = chunkqueue_get_append_buffer(hctx->wb); /* request line */ buffer_copy_string(b, get_http_method_name(con->request.http_method)); buffer_append_string_len(b, CONST_STR_LEN(" ")); buffer_append_string_buffer(b, con->request.uri); buffer_append_string_len(b, CONST_STR_LEN(" HTTP/1.0\r\n")); proxy_append_header(con, "X-Forwarded-For", (char *)inet_ntop_cache_get_ip(srv, &(con->dst_addr))); /* http_host is NOT is just a pointer to a buffer * which is NULL if it is not set */ if (con->request.http_host && !buffer_is_empty(con->request.http_host)) { proxy_set_header(con, "X-Host", con->request.http_host->ptr); } proxy_set_header(con, "X-Forwarded-Proto", con->conf.is_ssl ? "https" : "http"); /* request header */ for (i = 0; i < con->request.headers->used; i++) { data_string *ds; ds = (data_string *)con->request.headers->data[i]; if (ds->value->used && ds->key->used) { if (buffer_is_equal_string(ds->key, CONST_STR_LEN("Connection"))) continue; if (buffer_is_equal_string(ds->key, CONST_STR_LEN("Proxy-Connection"))) continue; buffer_append_string_buffer(b, ds->key); buffer_append_string_len(b, CONST_STR_LEN(": ")); buffer_append_string_buffer(b, ds->value); buffer_append_string_len(b, CONST_STR_LEN("\r\n")); } } buffer_append_string_len(b, CONST_STR_LEN("\r\n")); hctx->wb->bytes_in += b->used - 1; /* body */ if (con->request.content_length) { chunkqueue *req_cq = con->request_content_queue; chunk *req_c; off_t offset; /* something to send ? */ for (offset = 0, req_c = req_cq->first; offset != req_cq->bytes_in; req_c = req_c->next) { off_t weWant = req_cq->bytes_in - offset; off_t weHave = 0; /* we announce toWrite octects * now take all the request_content chunk that we need to fill this request * */ switch (req_c->type) { case FILE_CHUNK: weHave = req_c->file.length - req_c->offset; if (weHave > weWant) weHave = weWant; chunkqueue_append_file(hctx->wb, req_c->file.name, req_c->offset, weHave); req_c->offset += weHave; req_cq->bytes_out += weHave; hctx->wb->bytes_in += weHave; break; case MEM_CHUNK: /* append to the buffer */ weHave = req_c->mem->used - 1 - req_c->offset; if (weHave > weWant) weHave = weWant; b = chunkqueue_get_append_buffer(hctx->wb); buffer_append_memory(b, req_c->mem->ptr + req_c->offset, weHave); b->used++; /* add virtual \0 */ req_c->offset += weHave; req_cq->bytes_out += weHave; hctx->wb->bytes_in += weHave; break; default: break; } offset += weHave; } } return 0; }
static int http_response_parse_range(server *srv, connection *con, plugin_data *p) { int multipart = 0; char *boundary = "fkj49sn38dcn3"; data_string *ds; stat_cache_entry *sce = NULL; buffer *content_type = NULL; buffer *range = NULL; http_req_range *ranges, *r; if (NULL != (ds = (data_string *)array_get_element(con->request.headers, CONST_STR_LEN("Range")))) { range = ds->value; } else { /* we don't have a Range header */ return -1; } if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) { SEGFAULT("stat_cache_get_entry(%s) returned %d", SAFE_BUF_STR(con->physical.path), HANDLER_ERROR); } con->response.content_length = 0; if (NULL != (ds = (data_string *)array_get_element(con->response.headers, CONST_STR_LEN("Content-Type")))) { content_type = ds->value; } /* start the range-header parser * bytes=<num> */ ranges = p->ranges; http_request_range_reset(ranges); switch (http_request_range_parse(range, ranges)) { case PARSE_ERROR: return -1; /* no range valid Range Header */ case PARSE_SUCCESS: break; default: TRACE("%s", "foobar"); return -1; } if (ranges->next) { multipart = 1; } /* patch the '-1' */ for (r = ranges; r; r = r->next) { if (r->start == -1) { /* -<end> * * the last <end> bytes */ r->start = sce->st.st_size - r->end; r->end = sce->st.st_size - 1; } if (r->end == -1) { /* <start>- * all but the first <start> bytes */ r->end = sce->st.st_size - 1; } if (r->end > sce->st.st_size - 1) { /* RFC 2616 - 14.35.1 * * if last-byte-pos not present or > size-of-file * take the size-of-file * * */ r->end = sce->st.st_size - 1; } if (r->start > sce->st.st_size - 1) { /* RFC 2616 - 14.35.1 * * if first-byte-pos > file-size, 416 */ con->http_status = 416; return -1; } if (r->start > r->end) { /* RFC 2616 - 14.35.1 * * if last-byte-pos is present, it has to be >= first-byte-pos * * invalid ranges have to be handle as no Range specified * */ return -1; } } if (r) { /* we ran into an range violation */ return -1; } if (multipart) { buffer *b; for (r = ranges; r; r = r->next) { /* write boundary-header */ b = chunkqueue_get_append_buffer(con->send); buffer_copy_string_len(b, CONST_STR_LEN("\r\n--")); buffer_append_string(b, boundary); /* write Content-Range */ buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Range: bytes ")); buffer_append_off_t(b, r->start); buffer_append_string_len(b, CONST_STR_LEN("-")); buffer_append_off_t(b, r->end); buffer_append_string_len(b, CONST_STR_LEN("/")); buffer_append_off_t(b, sce->st.st_size); buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Type: ")); buffer_append_string_buffer(b, content_type); /* write END-OF-HEADER */ buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n")); con->response.content_length += b->used - 1; con->send->bytes_in += b->used - 1; chunkqueue_append_file(con->send, con->physical.path, r->start, r->end - r->start + 1); con->response.content_length += r->end - r->start + 1; con->send->bytes_in += r->end - r->start + 1; } /* add boundary end */ b = chunkqueue_get_append_buffer(con->send); buffer_copy_string_len(b, "\r\n--", 4); buffer_append_string(b, boundary); buffer_append_string_len(b, "--\r\n", 4); con->response.content_length += b->used - 1; con->send->bytes_in += b->used - 1; /* set header-fields */ buffer_copy_string_len(p->range_buf, CONST_STR_LEN("multipart/byteranges; boundary=")); buffer_append_string(p->range_buf, boundary); /* overwrite content-type */ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->range_buf)); } else { r = ranges; chunkqueue_append_file(con->send, con->physical.path, r->start, r->end - r->start + 1); con->response.content_length += r->end - r->start + 1; con->send->bytes_in += r->end - r->start + 1; buffer_copy_string_len(p->range_buf, CONST_STR_LEN("bytes ")); buffer_append_off_t(p->range_buf, r->start); buffer_append_string_len(p->range_buf, CONST_STR_LEN("-")); buffer_append_off_t(p->range_buf, r->end); buffer_append_string_len(p->range_buf, CONST_STR_LEN("/")); buffer_append_off_t(p->range_buf, sce->st.st_size); response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(p->range_buf)); } /* ok, the file is set-up */ return 0; }
static int mod_ssi_handle_request(server *srv, connection *con, plugin_data *p) { stream s; #ifdef HAVE_PCRE_H int i, n; #define N 10 int ovec[N * 3]; #endif stat_cache_entry *sce = NULL; /* get a stream to the file */ array_reset(p->ssi_vars); array_reset(p->ssi_cgi_env); buffer_copy_string_len(p->timefmt, CONST_STR_LEN("%a, %d %b %Y %H:%M:%S %Z")); p->sizefmt = 0; build_ssi_cgi_vars(srv, con, p); p->if_is_false = 0; /* Reset the modified time of included files */ include_file_last_mtime = 0; if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) { log_error_write(srv, __FILE__, __LINE__, "SB", "stat_cache_get_entry failed: ", con->physical.path); return -1; } if (-1 == stream_open(&s, con->physical.path)) { log_error_write(srv, __FILE__, __LINE__, "sb", "stream-open: ", con->physical.path); return -1; } /** * <!--#element attribute=value attribute=value ... --> * * config DONE * errmsg -- missing * sizefmt DONE * timefmt DONE * echo DONE * var DONE * encoding -- missing * exec DONE * cgi -- never * cmd DONE * fsize DONE * file DONE * virtual DONE * flastmod DONE * file DONE * virtual DONE * include DONE * file DONE * virtual DONE * printenv DONE * set DONE * var DONE * value DONE * * if DONE * elif DONE * else DONE * endif DONE * * * expressions * AND, OR DONE * comp DONE * ${...} -- missing * $... DONE * '...' DONE * ( ... ) DONE * * * * ** all DONE ** * DATE_GMT * The current date in Greenwich Mean Time. * DATE_LOCAL * The current date in the local time zone. * DOCUMENT_NAME * The filename (excluding directories) of the document requested by the user. * DOCUMENT_URI * The (%-decoded) URL path of the document requested by the user. Note that in the case of nested include files, this is not then URL for the current document. * LAST_MODIFIED * The last modification date of the document requested by the user. * USER_NAME * Contains the owner of the file which included it. * */ #ifdef HAVE_PCRE_H for (i = 0; (n = pcre_exec(p->ssi_regex, NULL, s.start, s.size, i, 0, ovec, N * 3)) > 0; i = ovec[1]) { const char **l; /* take everything from last offset to current match pos */ if (!p->if_is_false) chunkqueue_append_file(con->write_queue, con->physical.path, i, ovec[0] - i); pcre_get_substring_list(s.start, ovec, n, &l); process_ssi_stmt(srv, con, p, l, n, sce); pcre_free_substring_list(l); } switch(n) { case PCRE_ERROR_NOMATCH: /* copy everything/the rest */ chunkqueue_append_file(con->write_queue, con->physical.path, i, s.size - i); break; default: log_error_write(srv, __FILE__, __LINE__, "sd", "execution error while matching: ", n); break; } #endif stream_close(&s); con->file_started = 1; con->file_finished = 1; con->mode = p->id; if (p->conf.content_type->used <= 1) { response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); } else { response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->conf.content_type)); } { /* Generate "ETag" & "Last-Modified" headers */ time_t lm_time = 0; buffer *mtime = NULL; etag_mutate(con->physical.etag, sce->etag); response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag)); if (sce->st.st_mtime > include_file_last_mtime) lm_time = sce->st.st_mtime; else lm_time = include_file_last_mtime; mtime = strftime_cache_get(srv, lm_time); response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime)); } /* Reset the modified time of included files */ include_file_last_mtime = 0; /* reset physical.path */ buffer_reset(con->physical.path); return 0; }
static int process_ssi_stmt(server *srv, connection *con, plugin_data *p, const char **l, size_t n, stat_cache_entry *sce) { size_t i, ssicmd = 0; char buf[255]; buffer *b = NULL; struct { const char *var; enum { SSI_UNSET, SSI_ECHO, SSI_FSIZE, SSI_INCLUDE, SSI_FLASTMOD, SSI_CONFIG, SSI_PRINTENV, SSI_SET, SSI_IF, SSI_ELIF, SSI_ELSE, SSI_ENDIF, SSI_EXEC } type; } ssicmds[] = { { "echo", SSI_ECHO }, { "include", SSI_INCLUDE }, { "flastmod", SSI_FLASTMOD }, { "fsize", SSI_FSIZE }, { "config", SSI_CONFIG }, { "printenv", SSI_PRINTENV }, { "set", SSI_SET }, { "if", SSI_IF }, { "elif", SSI_ELIF }, { "endif", SSI_ENDIF }, { "else", SSI_ELSE }, { "exec", SSI_EXEC }, { NULL, SSI_UNSET } }; for (i = 0; ssicmds[i].var; i++) { if (0 == strcmp(l[1], ssicmds[i].var)) { ssicmd = ssicmds[i].type; break; } } switch(ssicmd) { case SSI_ECHO: { /* echo */ int var = 0; /* int enc = 0; */ const char *var_val = NULL; struct { const char *var; enum { SSI_ECHO_UNSET, SSI_ECHO_DATE_GMT, SSI_ECHO_DATE_LOCAL, SSI_ECHO_DOCUMENT_NAME, SSI_ECHO_DOCUMENT_URI, SSI_ECHO_LAST_MODIFIED, SSI_ECHO_USER_NAME } type; } echovars[] = { { "DATE_GMT", SSI_ECHO_DATE_GMT }, { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL }, { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME }, { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI }, { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED }, { "USER_NAME", SSI_ECHO_USER_NAME }, { NULL, SSI_ECHO_UNSET } }; /* struct { const char *var; enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type; } encvars[] = { { "url", SSI_ENC_URL }, { "none", SSI_ENC_NONE }, { "entity", SSI_ENC_ENTITY }, { NULL, SSI_ENC_UNSET } }; */ for (i = 2; i < n; i += 2) { if (0 == strcmp(l[i], "var")) { int j; var_val = l[i+1]; for (j = 0; echovars[j].var; j++) { if (0 == strcmp(l[i+1], echovars[j].var)) { var = echovars[j].type; break; } } } else if (0 == strcmp(l[i], "encoding")) { /* int j; for (j = 0; encvars[j].var; j++) { if (0 == strcmp(l[i+1], encvars[j].var)) { enc = encvars[j].type; break; } } */ } else { log_error_write(srv, __FILE__, __LINE__, "sss", "ssi: unknow attribute for ", l[1], l[i]); } } if (p->if_is_false) break; if (!var_val) { log_error_write(srv, __FILE__, __LINE__, "sss", "ssi: ", l[1], "var is missing"); break; } switch(var) { case SSI_ECHO_USER_NAME: { struct passwd *pw; b = chunkqueue_get_append_buffer(con->write_queue); #ifdef HAVE_PWD_H if (NULL == (pw = getpwuid(sce->st.st_uid))) { buffer_copy_long(b, sce->st.st_uid); } else { buffer_copy_string(b, pw->pw_name); } #else buffer_copy_long(b, sce->st.st_uid); #endif break; } case SSI_ECHO_LAST_MODIFIED: { time_t t = sce->st.st_mtime; b = chunkqueue_get_append_buffer(con->write_queue); if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) { buffer_copy_string_len(b, CONST_STR_LEN("(none)")); } else { buffer_copy_string(b, buf); } break; } case SSI_ECHO_DATE_LOCAL: { time_t t = time(NULL); b = chunkqueue_get_append_buffer(con->write_queue); if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) { buffer_copy_string_len(b, CONST_STR_LEN("(none)")); } else { buffer_copy_string(b, buf); } break; } case SSI_ECHO_DATE_GMT: { time_t t = time(NULL); b = chunkqueue_get_append_buffer(con->write_queue); if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, gmtime(&t))) { buffer_copy_string_len(b, CONST_STR_LEN("(none)")); } else { buffer_copy_string(b, buf); } break; } case SSI_ECHO_DOCUMENT_NAME: { char *sl; b = chunkqueue_get_append_buffer(con->write_queue); if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) { buffer_copy_string_buffer(b, con->physical.path); } else { buffer_copy_string(b, sl + 1); } break; } case SSI_ECHO_DOCUMENT_URI: { b = chunkqueue_get_append_buffer(con->write_queue); buffer_copy_string_buffer(b, con->uri.path); break; } default: { data_string *ds; /* check if it is a cgi-var */ b = chunkqueue_get_append_buffer(con->write_queue); if (NULL != (ds = (data_string *)array_get_element(p->ssi_cgi_env, var_val))) { buffer_copy_string_buffer(b, ds->value); } else { buffer_copy_string_len(b, CONST_STR_LEN("(none)")); } break; } } break; } case SSI_INCLUDE: case SSI_FLASTMOD: case SSI_FSIZE: { const char * file_path = NULL, *virt_path = NULL; struct stat st; char *sl; for (i = 2; i < n; i += 2) { if (0 == strcmp(l[i], "file")) { file_path = l[i+1]; } else if (0 == strcmp(l[i], "virtual")) { virt_path = l[i+1]; } else { log_error_write(srv, __FILE__, __LINE__, "sss", "ssi: unknow attribute for ", l[1], l[i]); } } if (!file_path && !virt_path) { log_error_write(srv, __FILE__, __LINE__, "sss", "ssi: ", l[1], "file or virtual are missing"); break; } if (file_path && virt_path) { log_error_write(srv, __FILE__, __LINE__, "sss", "ssi: ", l[1], "only one of file and virtual is allowed here"); break; } if (p->if_is_false) break; if (file_path) { /* current doc-root */ if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) { buffer_copy_string_len(p->stat_fn, CONST_STR_LEN("/")); } else { buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, sl - con->physical.path->ptr + 1); } buffer_copy_string(srv->tmp_buf, file_path); buffer_urldecode_path(srv->tmp_buf); buffer_path_simplify(srv->tmp_buf, srv->tmp_buf); buffer_append_string_buffer(p->stat_fn, srv->tmp_buf); } else { /* virtual */ if (virt_path[0] == '/') { buffer_copy_string(p->stat_fn, virt_path); } else { /* there is always a / */ sl = strrchr(con->uri.path->ptr, '/'); buffer_copy_string_len(p->stat_fn, con->uri.path->ptr, sl - con->uri.path->ptr + 1); buffer_append_string(p->stat_fn, virt_path); } buffer_urldecode_path(p->stat_fn); buffer_path_simplify(srv->tmp_buf, p->stat_fn); /* we have an uri */ buffer_copy_string_buffer(p->stat_fn, con->physical.doc_root); buffer_append_string_buffer(p->stat_fn, srv->tmp_buf); } if (0 == stat(p->stat_fn->ptr, &st)) { time_t t = st.st_mtime; switch (ssicmd) { case SSI_FSIZE: b = chunkqueue_get_append_buffer(con->write_queue); if (p->sizefmt) { int j = 0; const char *abr[] = { " B", " kB", " MB", " GB", " TB", NULL }; off_t s = st.st_size; for (j = 0; s > 1024 && abr[j+1]; s /= 1024, j++); buffer_copy_off_t(b, s); buffer_append_string(b, abr[j]); } else { buffer_copy_off_t(b, st.st_size); } break; case SSI_FLASTMOD: b = chunkqueue_get_append_buffer(con->write_queue); if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) { buffer_copy_string_len(b, CONST_STR_LEN("(none)")); } else { buffer_copy_string(b, buf); } break; case SSI_INCLUDE: chunkqueue_append_file(con->write_queue, p->stat_fn, 0, st.st_size); /* Keep the newest mtime of included files */ if (st.st_mtime > include_file_last_mtime) include_file_last_mtime = st.st_mtime; break; } } else { log_error_write(srv, __FILE__, __LINE__, "sbs", "ssi: stating failed ", p->stat_fn, strerror(errno)); } break; } case SSI_SET: { const char *key = NULL, *val = NULL; for (i = 2; i < n; i += 2) { if (0 == strcmp(l[i], "var")) { key = l[i+1]; } else if (0 == strcmp(l[i], "value")) { val = l[i+1]; } else { log_error_write(srv, __FILE__, __LINE__, "sss", "ssi: unknow attribute for ", l[1], l[i]); } } if (p->if_is_false) break; if (key && val) { data_string *ds; if (NULL == (ds = (data_string *)array_get_unused_element(p->ssi_vars, TYPE_STRING))) { ds = data_string_init(); } buffer_copy_string(ds->key, key); buffer_copy_string(ds->value, val); array_insert_unique(p->ssi_vars, (data_unset *)ds); } else { log_error_write(srv, __FILE__, __LINE__, "sss", "ssi: var and value have to be set in", l[0], l[1]); } break; } case SSI_CONFIG: if (p->if_is_false) break; for (i = 2; i < n; i += 2) { if (0 == strcmp(l[i], "timefmt")) { buffer_copy_string(p->timefmt, l[i+1]); } else if (0 == strcmp(l[i], "sizefmt")) { if (0 == strcmp(l[i+1], "abbrev")) { p->sizefmt = 1; } else if (0 == strcmp(l[i+1], "abbrev")) { p->sizefmt = 0; } else { log_error_write(srv, __FILE__, __LINE__, "sssss", "ssi: unknow value for attribute '", l[i], "' for ", l[1], l[i+1]); } } else { log_error_write(srv, __FILE__, __LINE__, "sss", "ssi: unknow attribute for ", l[1], l[i]); } } break; case SSI_PRINTENV: if (p->if_is_false) break; b = chunkqueue_get_append_buffer(con->write_queue); for (i = 0; i < p->ssi_vars->used; i++) { data_string *ds = (data_string *)p->ssi_vars->data[p->ssi_vars->sorted[i]]; buffer_append_string_buffer(b, ds->key); buffer_append_string_len(b, CONST_STR_LEN("=")); buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML); buffer_append_string_len(b, CONST_STR_LEN("\n")); } for (i = 0; i < p->ssi_cgi_env->used; i++) { data_string *ds = (data_string *)p->ssi_cgi_env->data[p->ssi_cgi_env->sorted[i]]; buffer_append_string_buffer(b, ds->key); buffer_append_string_len(b, CONST_STR_LEN("=")); buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML); buffer_append_string_len(b, CONST_STR_LEN("\n")); } break; case SSI_EXEC: { const char *cmd = NULL; pid_t pid; int from_exec_fds[2]; for (i = 2; i < n; i += 2) { if (0 == strcmp(l[i], "cmd")) { cmd = l[i+1]; } else { log_error_write(srv, __FILE__, __LINE__, "sss", "ssi: unknow attribute for ", l[1], l[i]); } } if (p->if_is_false) break; /* create a return pipe and send output to the html-page * * as exec is assumed evil it is implemented synchronously */ if (!cmd) break; #ifdef HAVE_FORK if (pipe(from_exec_fds)) { log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed: ", strerror(errno)); return -1; } /* fork, execve */ switch (pid = fork()) { case 0: { /* move stdout to from_rrdtool_fd[1] */ close(STDOUT_FILENO); dup2(from_exec_fds[1], STDOUT_FILENO); close(from_exec_fds[1]); /* not needed */ close(from_exec_fds[0]); /* close stdin */ close(STDIN_FILENO); execl("/bin/sh", "sh", "-c", cmd, (char *)NULL); log_error_write(srv, __FILE__, __LINE__, "sss", "spawing exec failed:", strerror(errno), cmd); /* */ SEGFAULT(); break; } case -1: /* error */ log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno)); break; default: { /* father */ int status; ssize_t r; int was_interrupted = 0; close(from_exec_fds[1]); /* wait for the client to end */ /* * OpenBSD and Solaris send a EINTR on SIGCHILD even if we ignore it */ do { if (-1 == waitpid(pid, &status, 0)) { if (errno == EINTR) { was_interrupted++; } else { was_interrupted = 0; log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed:", strerror(errno)); } } else if (WIFEXITED(status)) { int toread; /* read everything from client and paste it into the output */ was_interrupted = 0; while(1) { if (ioctl(from_exec_fds[0], FIONREAD, &toread)) { log_error_write(srv, __FILE__, __LINE__, "s", "unexpected end-of-file (perhaps the ssi-exec process died)"); return -1; } if (toread > 0) { b = chunkqueue_get_append_buffer(con->write_queue); buffer_prepare_copy(b, toread + 1); if ((r = read(from_exec_fds[0], b->ptr, b->size - 1)) < 0) { /* read failed */ break; } else { b->used = r; b->ptr[b->used++] = '\0'; } } else { break; } } } else { was_interrupted = 0; log_error_write(srv, __FILE__, __LINE__, "s", "process exited abnormally"); } } while (was_interrupted > 0 && was_interrupted < 4); /* if waitpid() gets interrupted, retry, but max 4 times */ close(from_exec_fds[0]); break; } } #else return -1; #endif break; } case SSI_IF: { const char *expr = NULL; for (i = 2; i < n; i += 2) { if (0 == strcmp(l[i], "expr")) { expr = l[i+1]; } else { log_error_write(srv, __FILE__, __LINE__, "sss", "ssi: unknow attribute for ", l[1], l[i]); } } if (!expr) { log_error_write(srv, __FILE__, __LINE__, "sss", "ssi: ", l[1], "expr missing"); break; } if ((!p->if_is_false) && ((p->if_is_false_level == 0) || (p->if_level < p->if_is_false_level))) { switch (ssi_eval_expr(srv, con, p, expr)) { case -1: case 0: p->if_is_false = 1; p->if_is_false_level = p->if_level; break; case 1: p->if_is_false = 0; break; } } p->if_level++; break; } case SSI_ELSE: p->if_level--; if (p->if_is_false) { if ((p->if_level == p->if_is_false_level) && (p->if_is_false_endif == 0)) { p->if_is_false = 0; } } else { p->if_is_false = 1; p->if_is_false_level = p->if_level; } p->if_level++; break; case SSI_ELIF: { const char *expr = NULL; for (i = 2; i < n; i += 2) { if (0 == strcmp(l[i], "expr")) { expr = l[i+1]; } else { log_error_write(srv, __FILE__, __LINE__, "sss", "ssi: unknow attribute for ", l[1], l[i]); } } if (!expr) { log_error_write(srv, __FILE__, __LINE__, "sss", "ssi: ", l[1], "expr missing"); break; } p->if_level--; if (p->if_level == p->if_is_false_level) { if ((p->if_is_false) && (p->if_is_false_endif == 0)) { switch (ssi_eval_expr(srv, con, p, expr)) { case -1: case 0: p->if_is_false = 1; p->if_is_false_level = p->if_level; break; case 1: p->if_is_false = 0; break; } } else { p->if_is_false = 1; p->if_is_false_level = p->if_level; p->if_is_false_endif = 1; } } p->if_level++; break; } case SSI_ENDIF: p->if_level--; if (p->if_level == p->if_is_false_level) { p->if_is_false = 0; p->if_is_false_endif = 0; } break; default: log_error_write(srv, __FILE__, __LINE__, "ss", "ssi: unknow ssi-command:", l[1]); break; } return 0; }
static int http_response_parse_range(server *srv, connection *con, plugin_data *p) { int multipart = 0; int error; off_t start, end; const char *s, *minus; char *boundary = "fkj49sn38dcn3"; data_string *ds; stat_cache_entry *sce = NULL; buffer *content_type = NULL; if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) { SEGFAULT(); } start = 0; end = sce->st.st_size - 1; con->response.content_length = 0; if (NULL != (ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) { content_type = ds->value; } for (s = con->request.http_range, error = 0; !error && *s && NULL != (minus = strchr(s, '-')); ) { char *err; off_t la, le; if (s == minus) { /* -<stop> */ le = strtoll(s, &err, 10); if (le == 0) { /* RFC 2616 - 14.35.1 */ con->http_status = 416; error = 1; } else if (*err == '\0') { /* end */ s = err; end = sce->st.st_size - 1; start = sce->st.st_size + le; } else if (*err == ',') { multipart = 1; s = err + 1; end = sce->st.st_size - 1; start = sce->st.st_size + le; } else { error = 1; } } else if (*(minus+1) == '\0' || *(minus+1) == ',') { /* <start>- */ la = strtoll(s, &err, 10); if (err == minus) { /* ok */ if (*(err + 1) == '\0') { s = err + 1; end = sce->st.st_size - 1; start = la; } else if (*(err + 1) == ',') { multipart = 1; s = err + 2; end = sce->st.st_size - 1; start = la; } else { error = 1; } } else { /* error */ error = 1; } } else { /* <start>-<stop> */ la = strtoll(s, &err, 10); if (err == minus) { le = strtoll(minus+1, &err, 10); /* RFC 2616 - 14.35.1 */ if (la > le) { error = 1; } if (*err == '\0') { /* ok, end*/ s = err; end = le; start = la; } else if (*err == ',') { multipart = 1; s = err + 1; end = le; start = la; } else { /* error */ error = 1; } } else { /* error */ error = 1; } } if (!error) { if (start < 0) start = 0; /* RFC 2616 - 14.35.1 */ if (end > sce->st.st_size - 1) end = sce->st.st_size - 1; if (start > sce->st.st_size - 1) { error = 1; con->http_status = 416; } } if (!error) { if (multipart) { /* write boundary-header */ buffer *b; b = chunkqueue_get_append_buffer(con->write_queue); buffer_copy_string_len(b, CONST_STR_LEN("\r\n--")); buffer_append_string(b, boundary); /* write Content-Range */ buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Range: bytes ")); buffer_append_off_t(b, start); buffer_append_string_len(b, CONST_STR_LEN("-")); buffer_append_off_t(b, end); buffer_append_string_len(b, CONST_STR_LEN("/")); buffer_append_off_t(b, sce->st.st_size); buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Type: ")); buffer_append_string_buffer(b, content_type); /* write END-OF-HEADER */ buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n")); con->response.content_length += b->used - 1; } chunkqueue_append_file(con->write_queue, con->physical.path, start, end - start + 1); con->response.content_length += end - start + 1; } } /* something went wrong */ if (error) return -1; if (multipart) { /* add boundary end */ buffer *b; b = chunkqueue_get_append_buffer(con->write_queue); buffer_copy_string_len(b, "\r\n--", 4); buffer_append_string(b, boundary); buffer_append_string_len(b, "--\r\n", 4); con->response.content_length += b->used - 1; /* set header-fields */ buffer_copy_string_len(p->range_buf, CONST_STR_LEN("multipart/byteranges; boundary=")); buffer_append_string(p->range_buf, boundary); /* overwrite content-type */ response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->range_buf)); } else { /* add Content-Range-header */ buffer_copy_string_len(p->range_buf, CONST_STR_LEN("bytes ")); buffer_append_off_t(p->range_buf, start); buffer_append_string_len(p->range_buf, CONST_STR_LEN("-")); buffer_append_off_t(p->range_buf, end); buffer_append_string_len(p->range_buf, CONST_STR_LEN("/")); buffer_append_off_t(p->range_buf, sce->st.st_size); response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(p->range_buf)); } /* ok, the file is set-up */ return 0; }