static int cache_call_lua(server *srv, connection *con, plugin_data *p, buffer *cml_file) { buffer *b; char *c; /* cleanup basedir */ b = p->baseurl; buffer_copy_buffer(b, con->uri.path); for (c = b->ptr + buffer_string_length(b); c > b->ptr && *c != '/'; c--); if (*c == '/') { buffer_string_set_length(b, c - b->ptr + 1); } b = p->basedir; buffer_copy_buffer(b, con->physical.path); for (c = b->ptr + buffer_string_length(b); c > b->ptr && *c != '/'; c--); if (*c == '/') { buffer_string_set_length(b, c - b->ptr + 1); } /* prepare variables * - cookie-based * - get-param-based */ return cache_parse_lua(srv, con, p, cml_file); }
static void accesslog_append_escaped(buffer *dest, buffer *str) { char *ptr, *start, *end; /* replaces non-printable chars with \xHH where HH is the hex representation of the byte */ /* exceptions: " => \", \ => \\, whitespace chars => \n \t etc. */ if (buffer_string_is_empty(str)) return; buffer_string_prepare_append(dest, buffer_string_length(str)); for (ptr = start = str->ptr, end = str->ptr + buffer_string_length(str); ptr < end; ptr++) { unsigned char const c = (unsigned char) *ptr; if (c >= ' ' && c <= '~' && c != '"' && c != '\\') { /* nothing to change, add later as one block */ } else { /* copy previous part */ if (start < ptr) { buffer_append_string_len(dest, start, ptr - start); } start = ptr + 1; switch (c) { case '"': BUFFER_APPEND_STRING_CONST(dest, "\\\""); break; case '\\': BUFFER_APPEND_STRING_CONST(dest, "\\\\"); break; case '\b': BUFFER_APPEND_STRING_CONST(dest, "\\b"); break; case '\n': BUFFER_APPEND_STRING_CONST(dest, "\\n"); break; case '\r': BUFFER_APPEND_STRING_CONST(dest, "\\r"); break; case '\t': BUFFER_APPEND_STRING_CONST(dest, "\\t"); break; case '\v': BUFFER_APPEND_STRING_CONST(dest, "\\v"); break; default: { /* non printable char => \xHH */ char hh[5] = {'\\','x',0,0,0}; char h = c / 16; hh[2] = (h > 9) ? (h - 10 + 'A') : (h + '0'); h = c % 16; hh[3] = (h > 9) ? (h - 10 + 'A') : (h + '0'); buffer_append_string_len(dest, &hh[0], 4); } break; } } } if (start < end) { buffer_append_string_len(dest, start, end - start); } }
int network_writev_mem_chunks(server *srv, connection *con, int fd, chunkqueue *cq, off_t *p_max_bytes) { struct iovec chunks[MAX_CHUNKS]; size_t num_chunks; off_t max_bytes = *p_max_bytes; off_t toSend; ssize_t r; UNUSED(con); force_assert(NULL != cq->first); force_assert(MEM_CHUNK == cq->first->type); { chunk const *c; toSend = 0; num_chunks = 0; for (c = cq->first; NULL != c && MEM_CHUNK == c->type && num_chunks < MAX_CHUNKS && toSend < max_bytes; c = c->next) { size_t c_len; force_assert(c->offset >= 0 && c->offset <= (off_t)buffer_string_length(c->mem)); c_len = buffer_string_length(c->mem) - c->offset; if (c_len > 0) { toSend += c_len; chunks[num_chunks].iov_base = c->mem->ptr + c->offset; chunks[num_chunks].iov_len = c_len; ++num_chunks; } } } if (0 == num_chunks) { chunkqueue_remove_finished_chunks(cq); return 0; } r = writev(fd, chunks, num_chunks); if (r < 0) switch (errno) { case EAGAIN: case EINTR: break; case EPIPE: case ECONNRESET: return -2; default: log_error_write(srv, __FILE__, __LINE__, "ssd", "writev failed:", strerror(errno), fd); return -1; } if (r >= 0) { *p_max_bytes -= r; chunkqueue_mark_written(cq, r); } return (r > 0 && r == toSend) ? 0 : -3; }
void chunkqueue_get_memory(chunkqueue *cq, char **mem, size_t *len, size_t min_size, size_t alloc_size) { static const size_t REALLOC_MAX_SIZE = 256; chunk *c; buffer *b; char *dummy_mem; size_t dummy_len; force_assert(NULL != cq); if (NULL == mem) mem = &dummy_mem; if (NULL == len) len = &dummy_len; /* default values: */ if (0 == min_size) min_size = 1024; if (0 == alloc_size) alloc_size = 4096; if (alloc_size < min_size) alloc_size = min_size; if (NULL != cq->last && MEM_CHUNK == cq->last->type) { size_t have; b = cq->last->mem; have = buffer_string_space(b); /* unused buffer: allocate space */ if (buffer_string_is_empty(b)) { buffer_string_prepare_copy(b, alloc_size); have = buffer_string_space(b); } /* if buffer is really small just make it bigger */ else if (have < min_size && b->size <= REALLOC_MAX_SIZE) { size_t cur_len = buffer_string_length(b); size_t new_size = cur_len + min_size, append; if (new_size < alloc_size) new_size = alloc_size; append = new_size - cur_len; if (append >= min_size) { buffer_string_prepare_append(b, append); have = buffer_string_space(b); } } /* return pointer into existing buffer if large enough */ if (have >= min_size) { *mem = b->ptr + buffer_string_length(b); *len = have; return; } } /* allocate new chunk */ c = chunkqueue_get_unused_chunk(cq); c->type = MEM_CHUNK; chunkqueue_append_chunk(cq, c); b = c->mem; buffer_string_prepare_append(b, alloc_size); *mem = b->ptr + buffer_string_length(b); *len = buffer_string_space(b); }
int network_write_mem_chunk(server *srv, connection *con, int fd, chunkqueue *cq, off_t *p_max_bytes) { chunk* const c = cq->first; off_t c_len; ssize_t r; UNUSED(con); force_assert(NULL != c); force_assert(MEM_CHUNK == c->type); force_assert(c->offset >= 0 && c->offset <= (off_t)buffer_string_length(c->mem)); c_len = buffer_string_length(c->mem) - c->offset; if (c_len > *p_max_bytes) c_len = *p_max_bytes; if (0 == c_len) { chunkqueue_remove_finished_chunks(cq); return 0; } #if defined(__WIN32) if ((r = send(fd, c->mem->ptr + c->offset, c_len, 0)) < 0) { int lastError = WSAGetLastError(); switch (lastError) { case WSAEINTR: case WSAEWOULDBLOCK: break; case WSAECONNRESET: case WSAETIMEDOUT: case WSAECONNABORTED: return -2; default: log_error_write(srv, __FILE__, __LINE__, "sdd", "send failed: ", lastError, fd); return -1; } } #else /* __WIN32 */ if ((r = write(fd, c->mem->ptr + c->offset, c_len)) < 0) { switch (errno) { case EAGAIN: case EINTR: break; case EPIPE: case ECONNRESET: return -2; default: log_error_write(srv, __FILE__, __LINE__, "ssd", "write failed:", strerror(errno), fd); return -1; } } #endif /* __WIN32 */ if (r >= 0) { *p_max_bytes -= r; chunkqueue_mark_written(cq, r); } return (r > 0 && r == c_len) ? 0 : -3; }
int http_request_header_finished(server *srv, connection *con) { UNUSED(srv); if (buffer_string_length(con->request.request) < 4) return 0; if (0 == memcmp(con->request.request->ptr + buffer_string_length(con->request.request) - 4, CONST_STR_LEN("\r\n\r\n"))) return 1; if (NULL != strstr(con->request.request->ptr, "\r\n\r\n")) return 1; return 0; }
void http_response_xsendfile (server *srv, connection *con, buffer *path, const array *xdocroot) { const int status = con->http_status; int valid = 1; /* reset Content-Length, if set by backend * Content-Length might later be set to size of X-Sendfile static file, * determined by open(), fstat() to reduces race conditions if the file * is modified between stat() (stat_cache_get_entry()) and open(). */ if (con->parsed_response & HTTP_CONTENT_LENGTH) { data_string *ds = (data_string *) array_get_element(con->response.headers, "Content-Length"); if (ds) buffer_reset(ds->value); con->parsed_response &= ~HTTP_CONTENT_LENGTH; con->response.content_length = -1; } buffer_urldecode_path(path); buffer_path_simplify(path, path); if (con->conf.force_lowercase_filenames) { buffer_to_lower(path); } /* check that path is under xdocroot(s) * - xdocroot should have trailing slash appended at config time * - con->conf.force_lowercase_filenames is not a server-wide setting, * and so can not be definitively applied to xdocroot at config time*/ if (xdocroot->used) { size_t i, xlen = buffer_string_length(path); for (i = 0; i < xdocroot->used; ++i) { data_string *ds = (data_string *)xdocroot->data[i]; size_t dlen = buffer_string_length(ds->value); if (dlen <= xlen && (!con->conf.force_lowercase_filenames ? 0 == memcmp(path->ptr, ds->value->ptr, dlen) : 0 == strncasecmp(path->ptr, ds->value->ptr, dlen))) { break; } } if (i == xdocroot->used) { log_error_write(srv, __FILE__, __LINE__, "SBs", "X-Sendfile (", path, ") not under configured x-sendfile-docroot(s)"); con->http_status = 403; valid = 0; } } if (valid) http_response_send_file(srv, con, path); if (con->http_status >= 400 && status < 300) { con->mode = DIRECT; } else if (0 != status && 200 != status) { con->http_status = status; } }
void buffer_append_string_buffer(buffer *b, const buffer *src) { if (NULL == src) { buffer_append_string_len(b, NULL, 0); } else { buffer_append_string_len(b, src->ptr, buffer_string_length(src)); } }
static int split_get_params(array *get_params, buffer *qrystr) { size_t is_key = 1, klen = 0; char *key = qrystr->ptr, *val = NULL; if (buffer_string_is_empty(qrystr)) return 0; for (size_t i = 0, len = buffer_string_length(qrystr); i <= len; ++i) { switch(qrystr->ptr[i]) { case '=': if (is_key) { val = qrystr->ptr + i + 1; klen = (size_t)(qrystr->ptr + i - key); is_key = 0; } break; case '&': case '\0': /* fin symbol */ if (!is_key) { /* we need at least a = since the last & */ array_insert_key_value(get_params, key, klen, val, qrystr->ptr + i - val); } key = qrystr->ptr + i + 1; val = NULL; is_key = 1; break; } } return 0; }
static buffer * cgi_get_handler(array *a, buffer *fn) { size_t k, s_len = buffer_string_length(fn); for (k = 0; k < a->used; ++k) { data_string *ds = (data_string *)a->data[k]; size_t ct_len = buffer_string_length(ds->key); if (buffer_is_empty(ds->key)) continue; if (s_len < ct_len) continue; if (0 == strncmp(fn->ptr + s_len - ct_len, ds->key->ptr, ct_len)) { return ds->value; } } return NULL; }
void buffer_copy_buffer(buffer *b, const buffer *src) { if (NULL == src || 0 == src->used) { buffer_string_prepare_copy(b, 0); b->used = 0; /* keep special empty state for now */ } else { buffer_copy_string_len(b, src->ptr, buffer_string_length(src)); } }
static int proxy_create_env(server *srv, handler_ctx *hctx) { size_t i; connection *con = hctx->remote_conn; buffer *b; /* build header */ b = buffer_init(); /* 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 (!buffer_string_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->uri.scheme->ptr); /* request header */ for (i = 0; i < con->request.headers->used; i++) { data_string *ds; ds = (data_string *)con->request.headers->data[i]; if (!buffer_is_empty(ds->value) && !buffer_is_empty(ds->key)) { 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 += buffer_string_length(b); chunkqueue_append_buffer(hctx->wb, b); buffer_free(b); /* body */ if (con->request.content_length) { chunkqueue *req_cq = con->request_content_queue; chunkqueue_steal(hctx->wb, req_cq, req_cq->bytes_in); } return 0; }
static int mod_evhost_parse_host(connection *con,array *host) { register char *ptr = con->uri.authority->ptr + buffer_string_length(con->uri.authority); char *colon = ptr; /* needed to filter out the colon (if exists) */ int first = 1; data_string *ds; int i; /* first, find the domain + tld */ for(; ptr > con->uri.authority->ptr; ptr--) { if(*ptr == '.') { if(first) first = 0; else break; } else if(*ptr == ':') { colon = ptr; first = 1; } } ds = data_string_init(); buffer_copy_string_len(ds->key,CONST_STR_LEN("%0")); /* if we stopped at a dot, skip the dot */ if (*ptr == '.') ptr++; buffer_copy_string_len(ds->value, ptr, colon-ptr); array_insert_unique(host,(data_unset *)ds); /* if the : is not the start of the authority, go on parsing the hostname */ if (colon != con->uri.authority->ptr) { for(ptr = colon - 1, i = 1; ptr > con->uri.authority->ptr; ptr--) { if(*ptr == '.') { if (ptr != colon - 1) { /* is something between the dots */ ds = data_string_init(); buffer_copy_string_len(ds->key,CONST_STR_LEN("%")); buffer_append_int(ds->key, i++); buffer_copy_string_len(ds->value,ptr+1,colon-ptr-1); array_insert_unique(host,(data_unset *)ds); } colon = ptr; } } /* if the . is not the first charactor of the hostname */ if (colon != ptr) { ds = data_string_init(); buffer_copy_string_len(ds->key,CONST_STR_LEN("%")); buffer_append_int(ds->key, i /* ++ */); buffer_copy_string_len(ds->value,ptr,colon-ptr); array_insert_unique(host,(data_unset *)ds); } } 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); } }
static int build_doc_root(server *srv, connection *con, plugin_data *p, buffer *out, buffer *host) { stat_cache_entry *sce = NULL; force_assert(!buffer_string_is_empty(p->conf.server_root)); buffer_string_prepare_copy(out, 127); buffer_copy_buffer(out, p->conf.server_root); if (!buffer_string_is_empty(host)) { /* a hostname has to start with a alpha-numerical character * and must not contain a slash "/" */ char *dp; buffer_append_slash(out); if (NULL == (dp = strchr(host->ptr, ':'))) { buffer_append_string_buffer(out, host); } else { buffer_append_string_len(out, host->ptr, dp - host->ptr); } } buffer_append_slash(out); if (buffer_string_length(p->conf.document_root) > 1 && p->conf.document_root->ptr[0] == '/') { buffer_append_string_len(out, p->conf.document_root->ptr + 1, buffer_string_length(p->conf.document_root) - 1); } else { buffer_append_string_buffer(out, p->conf.document_root); buffer_append_slash(out); } if (HANDLER_ERROR == stat_cache_get_entry(srv, con, out, &sce)) { if (p->conf.debug) { log_error_write(srv, __FILE__, __LINE__, "sb", strerror(errno), out); } return -1; } else if (!S_ISDIR(sce->st.st_mode)) { return -1; } return 0; }
int log_error_write_multiline_buffer(server *srv, const char *filename, unsigned int line, buffer *multiline, const char *fmt, ...) { va_list ap; size_t prefix_len; buffer *b = srv->errorlog_buf; char *pos, *end, *current_line; if (buffer_string_is_empty(multiline)) return 0; if (-1 == log_buffer_prepare(b, srv, filename, line)) return 0; va_start(ap, fmt); log_buffer_append_printf(b, fmt, ap); va_end(ap); prefix_len = buffer_string_length(b); current_line = pos = multiline->ptr; end = multiline->ptr + buffer_string_length(multiline); for ( ; pos <= end ; ++pos) { switch (*pos) { case '\n': case '\r': case '\0': /* handles end of string */ if (current_line < pos) { /* truncate to prefix */ buffer_string_set_length(b, prefix_len); buffer_append_string_len(b, current_line, pos - current_line); log_write(srv, b); } current_line = pos + 1; break; default: break; } } return 0; }
static int buffer_copy_dirname(buffer *dst, buffer *file) { size_t i; if (buffer_string_is_empty(file)) return -1; for (i = buffer_string_length(file); i > 0; i--) { if (file->ptr[i] == '/') { buffer_copy_string_len(dst, file->ptr, i); return 0; } } return -1; }
static int split_get_params(array *get_params, buffer *qrystr) { size_t is_key = 1; size_t i, len; char *key = NULL, *val = NULL; key = qrystr->ptr; /* we need the \0 */ len = buffer_string_length(qrystr); for (i = 0; i <= len; i++) { switch(qrystr->ptr[i]) { case '=': if (is_key) { val = qrystr->ptr + i + 1; qrystr->ptr[i] = '\0'; is_key = 0; } break; case '&': case '\0': /* fin symbol */ if (!is_key) { data_string *ds; /* we need at least a = since the last & */ /* terminate the value */ qrystr->ptr[i] = '\0'; if (NULL == (ds = (data_string *)array_get_unused_element(get_params, TYPE_STRING))) { ds = data_string_init(); } buffer_copy_string_len(ds->key, key, strlen(key)); buffer_copy_string_len(ds->value, val, strlen(val)); array_insert_unique(get_params, (data_unset *)ds); } key = qrystr->ptr + i + 1; val = NULL; is_key = 1; break; } } return 0; }
static int mod_authn_gssapi_create_krb5_ccache(server *srv, connection *con, plugin_data *p, krb5_context kcontext, krb5_principal princ, krb5_ccache *ccache) { buffer * const kccname = buffer_init_string("FILE:/tmp/krb5cc_gssapi_XXXXXX"); char * const ccname = kccname->ptr + sizeof("FILE:")-1; const size_t ccnamelen = buffer_string_length(kccname)-(sizeof("FILE:")-1); /*(future: might consider using server.upload-dirs instead of /tmp)*/ /* coverity[secure_temp : FALSE] */ int fd = mkstemp(ccname); if (fd < 0) { log_error_write(srv, __FILE__, __LINE__, "ss", "mkstemp():", strerror(errno)); buffer_free(kccname); return -1; } close(fd); do { krb5_error_code problem; problem = krb5_cc_resolve(kcontext, kccname->ptr, ccache); if (problem) { mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_resolve", NULL, kcontext, problem); break; } problem = krb5_cc_initialize(kcontext, *ccache, princ); if (problem) { mod_authn_gssapi_log_krb5_error(srv, __FILE__, __LINE__, "krb5_cc_initialize", kccname->ptr, kcontext, problem); break; } con->plugin_ctx[p->id] = kccname; array_set_key_value(con->environment, CONST_STR_LEN("KRB5CCNAME"), ccname, ccnamelen); array_set_key_value(con->request.headers, CONST_STR_LEN("X-Forwarded-Keytab"), ccname, ccnamelen); return 0; } while (0); if (*ccache) { krb5_cc_destroy(kcontext, *ccache); *ccache = NULL; } unlink(ccname); buffer_free(kccname); return -1; }
static off_t chunk_remaining_length(const chunk *c) { off_t len = 0; switch (c->type) { case MEM_CHUNK: len = buffer_string_length(c->mem); break; case FILE_CHUNK: len = c->file.length; break; default: force_assert(c->type == MEM_CHUNK || c->type == FILE_CHUNK); break; } force_assert(c->offset <= len); return len - c->offset; }
static void test_buffer_string_space(void) { buffer *b = buffer_init(); size_t space; space = buffer_string_space(b); assert(0 == space); buffer_copy_string_len(b, CONST_STR_LEN("")); space = buffer_string_space(b); assert(space > 0); assert(space + buffer_string_length(b) == b->size - 1); buffer_commit(b, b->size - 1); assert(b->used == b->size); space = buffer_string_space(b); assert(0 == space); buffer_free(b); }
static int cache_export_get_params(lua_State *L, int tbl, buffer *qrystr) { size_t is_key = 1; size_t i, len; char *key = NULL, *val = NULL; key = qrystr->ptr; /* we need the \0 */ len = buffer_string_length(qrystr); for (i = 0; i <= len; i++) { switch(qrystr->ptr[i]) { case '=': if (is_key) { val = qrystr->ptr + i + 1; qrystr->ptr[i] = '\0'; is_key = 0; } break; case '&': case '\0': /* fin symbol */ if (!is_key) { /* we need at least a = since the last & */ /* terminate the value */ qrystr->ptr[i] = '\0'; c_to_lua_push(L, tbl, key, strlen(key), val, strlen(val)); } key = qrystr->ptr + i + 1; val = NULL; is_key = 1; break; } } return 0; }
static void data_string_print(const data_unset *d, int depth) { data_string *ds = (data_string *)d; size_t i, len; UNUSED(depth); /* empty and uninitialized strings */ if (buffer_string_is_empty(ds->value)) { fputs("\"\"", stdout); return; } /* print out the string as is, except prepend " with backslash */ putc('"', stdout); len = buffer_string_length(ds->value); for (i = 0; i < len; i++) { unsigned char c = ds->value->ptr[i]; if (c == '"') { fputs("\\\"", stdout); } else { putc(c, stdout); } } putc('"', stdout); }
static int cgi_demux_response(server *srv, handler_ctx *hctx) { plugin_data *p = hctx->plugin_data; connection *con = hctx->remote_conn; while(1) { int n; int toread; #if defined(__WIN32) buffer_string_prepare_copy(hctx->response, 4 * 1024); #else if (ioctl(con->fd, FIONREAD, &toread) || toread <= 4*1024) { buffer_string_prepare_copy(hctx->response, 4 * 1024); } else { if (toread > MAX_READ_LIMIT) toread = MAX_READ_LIMIT; buffer_string_prepare_copy(hctx->response, toread); } #endif if (-1 == (n = read(hctx->fd, hctx->response->ptr, hctx->response->size - 1))) { if (errno == EAGAIN || errno == EINTR) { /* would block, wait for signal */ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); return FDEVENT_HANDLED_NOT_FINISHED; } /* error */ log_error_write(srv, __FILE__, __LINE__, "sdd", strerror(errno), con->fd, hctx->fd); return FDEVENT_HANDLED_ERROR; } if (n == 0) { /* read finished */ return FDEVENT_HANDLED_FINISHED; } buffer_commit(hctx->response, n); /* split header from body */ if (con->file_started == 0) { int is_header = 0; int is_header_end = 0; size_t last_eol = 0; size_t i, header_len; buffer_append_string_buffer(hctx->response_header, hctx->response); /** * we have to handle a few cases: * * nph: * * HTTP/1.0 200 Ok\n * Header: Value\n * \n * * CGI: * Header: Value\n * Status: 200\n * \n * * and different mixes of \n and \r\n combinations * * Some users also forget about CGI and just send a response and hope * we handle it. No headers, no header-content seperator * */ /* nph (non-parsed headers) */ if (0 == strncmp(hctx->response_header->ptr, "HTTP/1.", 7)) is_header = 1; header_len = buffer_string_length(hctx->response_header); for (i = 0; !is_header_end && i < header_len; i++) { char c = hctx->response_header->ptr[i]; switch (c) { case ':': /* we found a colon * * looks like we have a normal header */ is_header = 1; break; case '\n': /* EOL */ if (is_header == 0) { /* we got a EOL but we don't seem to got a HTTP header */ is_header_end = 1; break; } /** * check if we saw a \n(\r)?\n sequence */ if (last_eol > 0 && ((i - last_eol == 1) || (i - last_eol == 2 && hctx->response_header->ptr[i - 1] == '\r'))) { is_header_end = 1; break; } last_eol = i; break; } } if (is_header_end) { if (!is_header) { /* no header, but a body */ if (0 != http_chunk_append_buffer(srv, con, hctx->response_header)) { return FDEVENT_HANDLED_ERROR; } } else { const char *bstart; size_t blen; /* the body starts after the EOL */ bstart = hctx->response_header->ptr + i; blen = header_len - i; /** * i still points to the char after the terminating EOL EOL * * put it on the last \n again */ i--; /* string the last \r?\n */ if (i > 0 && (hctx->response_header->ptr[i - 1] == '\r')) { i--; } buffer_string_set_length(hctx->response_header, i); /* parse the response header */ cgi_response_parse(srv, con, p, hctx->response_header); if (con->http_status >= 300 && con->http_status < 400) { /*(con->parsed_response & HTTP_LOCATION)*/ data_string *ds; if (NULL != (ds = (data_string *) array_get_element(con->response.headers, "Location")) && ds->value->ptr[0] == '/') { if (++con->loops_per_request > 5) { log_error_write(srv, __FILE__, __LINE__, "sb", "too many internal loops while processing request:", con->request.orig_uri); con->http_status = 500; /* Internal Server Error */ con->mode = DIRECT; return FDEVENT_HANDLED_FINISHED; } buffer_copy_buffer(con->request.uri, ds->value); if (con->request.content_length) { if (con->request.content_length != con->request_content_queue->bytes_in) { con->keep_alive = 0; } con->request.content_length = 0; chunkqueue_reset(con->request_content_queue); } if (con->http_status != 307 && con->http_status != 308) { /* Note: request body (if any) sent to initial dynamic handler * and is not available to the internal redirect */ con->request.http_method = HTTP_METHOD_GET; } connection_response_reset(srv, con); /*(includes con->http_status = 0)*/ con->mode = DIRECT; return FDEVENT_HANDLED_COMEBACK; } } if (hctx->conf.xsendfile_allow) { data_string *ds; if (NULL != (ds = (data_string *) array_get_element(con->response.headers, "X-Sendfile"))) { http_response_xsendfile(srv, con, ds->value, hctx->conf.xsendfile_docroot); return FDEVENT_HANDLED_FINISHED; } } if (blen > 0) { if (0 != http_chunk_append_mem(srv, con, bstart, blen)) { return FDEVENT_HANDLED_ERROR; } } } con->file_started = 1; } else { /*(reuse MAX_HTTP_REQUEST_HEADER as max size for response headers from backends)*/ if (header_len > MAX_HTTP_REQUEST_HEADER) { log_error_write(srv, __FILE__, __LINE__, "sb", "response headers too large for", con->uri.path); con->http_status = 502; /* Bad Gateway */ con->mode = DIRECT; return FDEVENT_HANDLED_FINISHED; } } } else { if (0 != http_chunk_append_buffer(srv, con, hctx->response)) { return FDEVENT_HANDLED_ERROR; } if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN) && chunkqueue_length(con->write_queue) > 65536 - 4096) { if (!con->is_writable) { /*(defer removal of FDEVENT_IN interest since * connection_state_machine() might be able to send data * immediately, unless !con->is_writable, where * connection_state_machine() might not loop back to call * mod_cgi_handle_subrequest())*/ fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); } break; } } #if 0 log_error_write(srv, __FILE__, __LINE__, "ddss", con->fd, hctx->fd, connection_get_state(con->state), b->ptr); #endif } return FDEVENT_HANDLED_NOT_FINISHED; }
static int cgi_write_request(server *srv, handler_ctx *hctx, int fd) { connection *con = hctx->remote_conn; chunkqueue *cq = con->request_content_queue; chunk *c; /* old comment: windows doesn't support select() on pipes - wouldn't be easy to fix for all platforms. * solution: if this is still a problem on windows, then substitute * socketpair() for pipe() and closesocket() for close() on windows. */ for (c = cq->first; c; c = cq->first) { ssize_t r = -1; switch(c->type) { case FILE_CHUNK: r = cgi_write_file_chunk_mmap(srv, con, fd, cq); break; case MEM_CHUNK: if ((r = write(fd, c->mem->ptr + c->offset, buffer_string_length(c->mem) - c->offset)) < 0) { switch(errno) { case EAGAIN: case EINTR: /* ignore and try again */ r = 0; break; case EPIPE: case ECONNRESET: /* connection closed */ r = -2; break; default: /* fatal error */ log_error_write(srv, __FILE__, __LINE__, "ss", "write failed due to: ", strerror(errno)); r = -1; break; } } else if (r > 0) { chunkqueue_mark_written(cq, r); } break; } if (0 == r) break; /*(might block)*/ switch (r) { case -1: /* fatal error */ return -1; case -2: /* connection reset */ log_error_write(srv, __FILE__, __LINE__, "s", "failed to send post data to cgi, connection closed by CGI"); /* skip all remaining data */ chunkqueue_mark_written(cq, chunkqueue_length(cq)); break; default: break; } } if (cq->bytes_out == (off_t)con->request.content_length) { /* sent all request body input */ /* close connection to the cgi-script */ if (-1 == hctx->fdtocgi) { /*(received request body sent in initial send to pipe buffer)*/ --srv->cur_fds; if (close(fd)) { log_error_write(srv, __FILE__, __LINE__, "sds", "cgi stdin close failed ", fd, strerror(errno)); } } else { cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/ } } else { off_t cqlen = cq->bytes_in - cq->bytes_out; if (cq->bytes_in != con->request.content_length && cqlen < 65536 - 16384) { /*(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST)*/ if (!(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_POLLIN)) { con->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_POLLIN; con->is_readable = 1; /* trigger optimistic read from client */ } } if (-1 == hctx->fdtocgi) { /*(not registered yet)*/ hctx->fdtocgi = fd; hctx->fde_ndx_tocgi = -1; fdevent_register(srv->ev, hctx->fdtocgi, cgi_handle_fdevent_send, hctx); } if (0 == cqlen) { /*(chunkqueue_is_empty(cq))*/ if ((fdevent_event_get_interest(srv->ev, hctx->fdtocgi) & FDEVENT_OUT)) { fdevent_event_set(srv->ev, &(hctx->fde_ndx_tocgi), hctx->fdtocgi, 0); } } else { /* more request body remains to be sent to CGI so register for fdevents */ fdevent_event_set(srv->ev, &(hctx->fde_ndx_tocgi), hctx->fdtocgi, FDEVENT_OUT); } } return 0; }
static handler_t mod_redirect_uri_handler(server *srv, connection *con, void *p_data) { #ifdef HAVE_PCRE_H plugin_data *p = p_data; size_t i; /* * REWRITE URL * * e.g. redirect /base/ to /index.php?section=base * */ mod_redirect_patch_connection(srv, con, p); buffer_copy_buffer(p->match_buf, con->request.uri); for (i = 0; i < p->conf.redirect->used; i++) { pcre *match; pcre_extra *extra; const char *pattern; size_t pattern_len; int n; pcre_keyvalue *kv = p->conf.redirect->kv[i]; # define N 10 int ovec[N * 3]; match = kv->key; extra = kv->key_extra; pattern = kv->value->ptr; pattern_len = buffer_string_length(kv->value); if ((n = pcre_exec(match, extra, CONST_BUF_LEN(p->match_buf), 0, 0, ovec, 3 * N)) < 0) { if (n != PCRE_ERROR_NOMATCH) { log_error_write(srv, __FILE__, __LINE__, "sd", "execution error while matching: ", n); return HANDLER_ERROR; } } else { const char **list; size_t start; size_t k; /* it matched */ pcre_get_substring_list(p->match_buf->ptr, ovec, n, &list); /* search for $[0-9] */ buffer_reset(p->location); start = 0; for (k = 0; k + 1 < pattern_len; k++) { if (pattern[k] == '$' || pattern[k] == '%') { /* got one */ size_t num = pattern[k + 1] - '0'; buffer_append_string_len(p->location, pattern + start, k - start); if (!isdigit((unsigned char)pattern[k + 1])) { /* enable escape: "%%" => "%", "%a" => "%a", "$$" => "$" */ buffer_append_string_len(p->location, pattern+k, pattern[k] == pattern[k+1] ? 1 : 2); } else if (pattern[k] == '$') { /* n is always > 0 */ if (num < (size_t)n) { buffer_append_string(p->location, list[num]); } } else if (p->conf.context == NULL) { /* we have no context, we are global */ log_error_write(srv, __FILE__, __LINE__, "sb", "used a rewrite containing a %[0-9]+ in the global scope, ignored:", kv->value); } else { config_append_cond_match_buffer(con, p->conf.context, p->location, num); } k++; start = k + 1; } } buffer_append_string_len(p->location, pattern + start, pattern_len - start); pcre_free(list); response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(p->location)); con->http_status = p->conf.redirect_code > 99 && p->conf.redirect_code < 1000 ? p->conf.redirect_code : 301; con->mode = DIRECT; con->file_finished = 1; return HANDLER_FINISHED; } } #undef N #else UNUSED(srv); UNUSED(con); UNUSED(p_data); #endif return HANDLER_GO_ON; }
handler_t stat_cache_get_entry(server *srv, connection *con, buffer *name, stat_cache_entry **ret_sce) { #ifdef HAVE_FAM_H fam_dir_entry *fam_dir = NULL; int dir_ndx = -1; splay_tree *dir_node = NULL; #endif stat_cache_entry *sce = NULL; stat_cache *sc; struct stat st; size_t k; int fd; struct stat lst; #ifdef DEBUG_STAT_CACHE size_t i; #endif int file_ndx; splay_tree *file_node = NULL; *ret_sce = NULL; /* * check if the directory for this file has changed */ sc = srv->stat_cache; buffer_copy_buffer(sc->hash_key, name); buffer_append_int(sc->hash_key, con->conf.follow_symlink); file_ndx = hashme(sc->hash_key); sc->files = splaytree_splay(sc->files, file_ndx); #ifdef DEBUG_STAT_CACHE for (i = 0; i < ctrl.used; i++) { if (ctrl.ptr[i] == file_ndx) break; } #endif if (sc->files && (sc->files->key == file_ndx)) { #ifdef DEBUG_STAT_CACHE /* it was in the cache */ force_assert(i < ctrl.used); #endif /* we have seen this file already and * don't stat() it again in the same second */ file_node = sc->files; sce = file_node->data; /* check if the name is the same, we might have a collision */ if (buffer_is_equal(name, sce->name)) { if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_SIMPLE) { if (sce->stat_ts == srv->cur_ts) { *ret_sce = sce; return HANDLER_GO_ON; } } } else { /* oops, a collision, * * file_node is used by the FAM check below to see if we know this file * and if we can save a stat(). * * BUT, the sce is not reset here as the entry into the cache is ok, we * it is just not pointing to our requested file. * * */ file_node = NULL; } } else { #ifdef DEBUG_STAT_CACHE if (i != ctrl.used) { log_error_write(srv, __FILE__, __LINE__, "xSB", file_ndx, "was already inserted but not found in cache, ", name); } force_assert(i == ctrl.used); #endif } #ifdef HAVE_FAM_H /* dir-check */ if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM) { if (0 != buffer_copy_dirname(sc->dir_name, name)) { log_error_write(srv, __FILE__, __LINE__, "sb", "no '/' found in filename:", name); return HANDLER_ERROR; } buffer_copy_buffer(sc->hash_key, sc->dir_name); buffer_append_int(sc->hash_key, con->conf.follow_symlink); dir_ndx = hashme(sc->hash_key); sc->dirs = splaytree_splay(sc->dirs, dir_ndx); if (sc->dirs && (sc->dirs->key == dir_ndx)) { dir_node = sc->dirs; } if (dir_node && file_node) { /* we found a file */ sce = file_node->data; fam_dir = dir_node->data; if (fam_dir->version == sce->dir_version) { /* the stat()-cache entry is still ok */ *ret_sce = sce; return HANDLER_GO_ON; } } } #endif /* * *lol* * - open() + fstat() on a named-pipe results in a (intended) hang. * - stat() if regular file + open() to see if we can read from it is better * * */ if (-1 == stat(name->ptr, &st)) { return HANDLER_ERROR; } if (S_ISREG(st.st_mode)) { /* fix broken stat/open for symlinks to reg files with appended slash on freebsd,osx */ if (name->ptr[buffer_string_length(name) - 1] == '/') { errno = ENOTDIR; return HANDLER_ERROR; } /* try to open the file to check if we can read it */ if (-1 == (fd = open(name->ptr, O_RDONLY))) { return HANDLER_ERROR; } close(fd); } if (NULL == sce) { #ifdef DEBUG_STAT_CACHE int osize = splaytree_size(sc->files); #endif sce = stat_cache_entry_init(); buffer_copy_buffer(sce->name, name); sc->files = splaytree_insert(sc->files, file_ndx, sce); #ifdef DEBUG_STAT_CACHE if (ctrl.size == 0) { ctrl.size = 16; ctrl.used = 0; ctrl.ptr = malloc(ctrl.size * sizeof(*ctrl.ptr)); } else if (ctrl.size == ctrl.used) { ctrl.size += 16; ctrl.ptr = realloc(ctrl.ptr, ctrl.size * sizeof(*ctrl.ptr)); } ctrl.ptr[ctrl.used++] = file_ndx; force_assert(sc->files); force_assert(sc->files->data == sce); force_assert(osize + 1 == splaytree_size(sc->files)); #endif } sce->st = st; sce->stat_ts = srv->cur_ts; /* catch the obvious symlinks * * this is not a secure check as we still have a race-condition between * the stat() and the open. We can only solve this by * 1. open() the file * 2. fstat() the fd * * and keeping the file open for the rest of the time. But this can * only be done at network level. * * per default it is not a symlink * */ #ifdef HAVE_LSTAT sce->is_symlink = 0; /* we want to only check for symlinks if we should block symlinks. */ if (!con->conf.follow_symlink) { if (stat_cache_lstat(srv, name, &lst) == 0) { #ifdef DEBUG_STAT_CACHE log_error_write(srv, __FILE__, __LINE__, "sb", "found symlink", name); #endif sce->is_symlink = 1; } /* * we assume "/" can not be symlink, so * skip the symlink stuff if our path is / **/ else if (buffer_string_length(name) > 1) { buffer *dname; char *s_cur; dname = buffer_init(); buffer_copy_buffer(dname, name); while ((s_cur = strrchr(dname->ptr, '/'))) { buffer_string_set_length(dname, s_cur - dname->ptr); if (dname->ptr == s_cur) { #ifdef DEBUG_STAT_CACHE log_error_write(srv, __FILE__, __LINE__, "s", "reached /"); #endif break; } #ifdef DEBUG_STAT_CACHE log_error_write(srv, __FILE__, __LINE__, "sbs", "checking if", dname, "is a symlink"); #endif if (stat_cache_lstat(srv, dname, &lst) == 0) { sce->is_symlink = 1; #ifdef DEBUG_STAT_CACHE log_error_write(srv, __FILE__, __LINE__, "sb", "found symlink", dname); #endif break; }; }; buffer_free(dname); }; }; #endif if (S_ISREG(st.st_mode)) { /* determine mimetype */ buffer_reset(sce->content_type); #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR) if (con->conf.use_xattr) { stat_cache_attr_get(sce->content_type, name->ptr); } #endif /* xattr did not set a content-type. ask the config */ if (buffer_string_is_empty(sce->content_type)) { size_t namelen = buffer_string_length(name); for (k = 0; k < con->conf.mimetypes->used; k++) { data_string *ds = (data_string *)con->conf.mimetypes->data[k]; buffer *type = ds->key; size_t typelen = buffer_string_length(type); if (buffer_is_empty(type)) continue; /* check if the right side is the same */ if (typelen > namelen) continue; if (0 == strncasecmp(name->ptr + namelen - typelen, type->ptr, typelen)) { buffer_copy_buffer(sce->content_type, ds->value); break; } } } etag_create(sce->etag, &(sce->st), con->etag_flags); } else if (S_ISDIR(st.st_mode)) { etag_create(sce->etag, &(sce->st), con->etag_flags); } #ifdef HAVE_FAM_H if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM) { /* is this directory already registered ? */ if (!dir_node) { fam_dir = fam_dir_entry_init(); buffer_copy_buffer(fam_dir->name, sc->dir_name); fam_dir->version = 1; fam_dir->req = calloc(1, sizeof(FAMRequest)); if (0 != FAMMonitorDirectory(&sc->fam, fam_dir->name->ptr, fam_dir->req, fam_dir)) { log_error_write(srv, __FILE__, __LINE__, "sbsbs", "monitoring dir failed:", fam_dir->name, "file:", name, FamErrlist[FAMErrno]); fam_dir_entry_free(&sc->fam, fam_dir); fam_dir = NULL; } else { int osize = 0; if (sc->dirs) { osize = sc->dirs->size; } sc->dirs = splaytree_insert(sc->dirs, dir_ndx, fam_dir); force_assert(sc->dirs); force_assert(sc->dirs->data == fam_dir); force_assert(osize == (sc->dirs->size - 1)); } } else { fam_dir = dir_node->data; } /* bind the fam_fc to the stat() cache entry */ if (fam_dir) { sce->dir_version = fam_dir->version; } } #endif *ret_sce = sce; return HANDLER_GO_ON; }
static handler_t deflate_compress_response(server *srv, connection *con, handler_ctx *hctx) { off_t len, max; int close_stream; /* move all chunk from write_queue into our in_queue, then adjust * counters since con->write_queue is reused for compressed output */ len = chunkqueue_length(con->write_queue); chunkqueue_remove_finished_chunks(con->write_queue); chunkqueue_append_chunkqueue(hctx->in_queue, con->write_queue); con->write_queue->bytes_in -= len; con->write_queue->bytes_out -= len; max = chunkqueue_length(hctx->in_queue); #if 0 /* calculate max bytes to compress for this call */ if (p->conf.sync_flush && max > (len = p->conf.work_block_size << 10)) { max = len; } #endif /* Compress chunks from in_queue into chunks for write_queue */ while (max) { chunk *c = hctx->in_queue->first; switch(c->type) { case MEM_CHUNK: len = buffer_string_length(c->mem) - c->offset; if (len > max) len = max; if (mod_deflate_compress(srv, con, hctx, (unsigned char *)c->mem->ptr+c->offset, len) < 0) { log_error_write(srv, __FILE__, __LINE__, "s", "compress failed."); return HANDLER_ERROR; } break; case FILE_CHUNK: len = c->file.length - c->offset; if (len > max) len = max; if ((len = mod_deflate_file_chunk(srv, con, hctx, c, len)) < 0) { log_error_write(srv, __FILE__, __LINE__, "s", "compress file chunk failed."); return HANDLER_ERROR; } break; default: log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known"); return HANDLER_ERROR; } max -= len; chunkqueue_mark_written(hctx->in_queue, len); } /*(currently should always be true)*/ /*(current implementation requires response be complete)*/ close_stream = (con->file_finished && chunkqueue_is_empty(hctx->in_queue)); if (mod_deflate_stream_flush(srv, con, hctx, close_stream) < 0) { log_error_write(srv, __FILE__, __LINE__, "s", "flush error"); return HANDLER_ERROR; } return close_stream ? HANDLER_FINISHED : HANDLER_GO_ON; }
static int load_next_chunk(server *srv, connection *con, chunkqueue *cq, off_t max_bytes, const char **data, size_t *data_len) { chunk * const c = cq->first; #define LOCAL_SEND_BUFSIZE (64 * 1024) /* this is a 64k sendbuffer * * it has to stay at the same location all the time to satisfy the needs * of SSL_write to pass the SAME parameter in case of a _WANT_WRITE * * the buffer is allocated once, is NOT realloced and is NOT freed at shutdown * -> we expect a 64k block to 'leak' in valgrind * */ static char *local_send_buffer = NULL; force_assert(NULL != c); switch (c->type) { case MEM_CHUNK: { size_t have; force_assert(c->offset >= 0 && c->offset <= (off_t)buffer_string_length(c->mem)); have = buffer_string_length(c->mem) - c->offset; if ((off_t) have > max_bytes) have = max_bytes; *data = c->mem->ptr + c->offset; *data_len = have; } return 0; case FILE_CHUNK: if (NULL == local_send_buffer) { local_send_buffer = malloc(LOCAL_SEND_BUFSIZE); force_assert(NULL != local_send_buffer); } if (0 != network_open_file_chunk(srv, con, cq)) return -1; { off_t offset, toSend; force_assert(c->offset >= 0 && c->offset <= c->file.length); offset = c->file.start + c->offset; toSend = c->file.length - c->offset; if (toSend > LOCAL_SEND_BUFSIZE) toSend = LOCAL_SEND_BUFSIZE; if (toSend > max_bytes) toSend = max_bytes; if (-1 == lseek(c->file.fd, offset, SEEK_SET)) { log_error_write(srv, __FILE__, __LINE__, "ss", "lseek: ", strerror(errno)); return -1; } if (-1 == (toSend = read(c->file.fd, local_send_buffer, toSend))) { log_error_write(srv, __FILE__, __LINE__, "ss", "read: ", strerror(errno)); return -1; } *data = local_send_buffer; *data_len = toSend; } return 0; } return -1; }
static int http_list_directory(server *srv, connection *con, plugin_data *p, buffer *dir) { DIR *dp; buffer *out; struct dirent *dent; struct stat st; char *path, *path_file; size_t i; int hide_dotfiles = p->conf.hide_dot_files; dirls_list_t dirs, files, *list; dirls_entry_t *tmp; char sizebuf[sizeof("999.9K")]; char datebuf[sizeof("2005-Jan-01 22:23:24")]; size_t k; const char *content_type; long name_max; #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR) char attrval[128]; int attrlen; #endif #ifdef HAVE_LOCALTIME_R struct tm tm; #endif if (buffer_string_is_empty(dir)) return -1; i = buffer_string_length(dir); #ifdef HAVE_PATHCONF if (0 >= (name_max = pathconf(dir->ptr, _PC_NAME_MAX))) { /* some broken fs (fuse) return 0 instead of -1 */ #ifdef NAME_MAX name_max = NAME_MAX; #else name_max = 255; /* stupid default */ #endif } #elif defined __WIN32 name_max = FILENAME_MAX; #else name_max = NAME_MAX; #endif path = malloc(i + name_max + 1); force_assert(NULL != path); memcpy(path, dir->ptr, i+1); path_file = path + i; if (NULL == (dp = opendir(path))) { log_error_write(srv, __FILE__, __LINE__, "sbs", "opendir failed:", dir, strerror(errno)); free(path); return -1; } dirs.ent = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE); force_assert(dirs.ent); dirs.size = DIRLIST_BLOB_SIZE; dirs.used = 0; files.ent = (dirls_entry_t**) malloc(sizeof(dirls_entry_t*) * DIRLIST_BLOB_SIZE); force_assert(files.ent); files.size = DIRLIST_BLOB_SIZE; files.used = 0; while ((dent = readdir(dp)) != NULL) { unsigned short exclude_match = 0; if (dent->d_name[0] == '.') { if (hide_dotfiles) continue; if (dent->d_name[1] == '\0') continue; if (dent->d_name[1] == '.' && dent->d_name[2] == '\0') continue; } if (p->conf.hide_readme_file && !buffer_string_is_empty(p->conf.show_readme)) { if (strcmp(dent->d_name, p->conf.show_readme->ptr) == 0) continue; } if (p->conf.hide_header_file && !buffer_string_is_empty(p->conf.show_header)) { if (strcmp(dent->d_name, p->conf.show_header->ptr) == 0) continue; } /* compare d_name against excludes array * elements, skipping any that match. */ #ifdef HAVE_PCRE_H for(i = 0; i < p->conf.excludes->used; i++) { int n; #define N 10 int ovec[N * 3]; pcre *regex = p->conf.excludes->ptr[i]->regex; if ((n = pcre_exec(regex, NULL, dent->d_name, strlen(dent->d_name), 0, 0, ovec, 3 * N)) < 0) { if (n != PCRE_ERROR_NOMATCH) { log_error_write(srv, __FILE__, __LINE__, "sd", "execution error while matching:", n); /* aborting would require a lot of manual cleanup here. * skip instead (to not leak names that break pcre matching) */ exclude_match = 1; break; } } else { exclude_match = 1; break; } } if (exclude_match) { continue; } #endif i = strlen(dent->d_name); /* NOTE: the manual says, d_name is never more than NAME_MAX * so this should actually not be a buffer-overflow-risk */ if (i > (size_t)name_max) continue; memcpy(path_file, dent->d_name, i + 1); if (stat(path, &st) != 0) continue; list = &files; if (S_ISDIR(st.st_mode)) list = &dirs; if (list->used == list->size) { list->size += DIRLIST_BLOB_SIZE; list->ent = (dirls_entry_t**) realloc(list->ent, sizeof(dirls_entry_t*) * list->size); force_assert(list->ent); } tmp = (dirls_entry_t*) malloc(sizeof(dirls_entry_t) + 1 + i); tmp->mtime = st.st_mtime; tmp->size = st.st_size; tmp->namelen = i; memcpy(DIRLIST_ENT_NAME(tmp), dent->d_name, i + 1); list->ent[list->used++] = tmp; } closedir(dp); if (dirs.used) http_dirls_sort(dirs.ent, dirs.used); if (files.used) http_dirls_sort(files.ent, files.used); out = buffer_init(); http_list_directory_header(srv, con, p, out); /* directories */ for (i = 0; i < dirs.used; i++) { tmp = dirs.ent[i]; #ifdef HAVE_LOCALTIME_R localtime_r(&(tmp->mtime), &tm); strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm); #else strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime))); #endif buffer_append_string_len(out, CONST_STR_LEN("<tr class=\"d\"><td class=\"n\"><a href=\"")); buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART); buffer_append_string_len(out, CONST_STR_LEN("/\">")); buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML); buffer_append_string_len(out, CONST_STR_LEN("</a>/</td><td class=\"m\">")); buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1); buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">- </td><td class=\"t\">Directory</td></tr>\n")); free(tmp); } /* files */ for (i = 0; i < files.used; i++) { tmp = files.ent[i]; content_type = NULL; #if defined(HAVE_XATTR) if (con->conf.use_xattr) { memcpy(path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1); attrlen = sizeof(attrval) - 1; if (attr_get(path, srv->srvconf.xattr_name->ptr, attrval, &attrlen, 0) == 0) { attrval[attrlen] = '\0'; content_type = attrval; } } #elif defined(HAVE_EXTATTR) if (con->conf.use_xattr) { memcpy(path_file, DIRLIST_ENT_NAME(tmp), tmp->namelen + 1); if(-1 != (attrlen = extattr_get_file(path, EXTATTR_NAMESPACE_USER, srv->srvconf.xattr_name->ptr, attrval, sizeof(attrval)-1))) { attrval[attrlen] = '\0'; content_type = attrval; } } #endif if (content_type == NULL) { content_type = "application/octet-stream"; for (k = 0; k < con->conf.mimetypes->used; k++) { data_string *ds = (data_string *)con->conf.mimetypes->data[k]; size_t ct_len; if (buffer_is_empty(ds->key)) continue; ct_len = buffer_string_length(ds->key); if (tmp->namelen < ct_len) continue; if (0 == strncasecmp(DIRLIST_ENT_NAME(tmp) + tmp->namelen - ct_len, ds->key->ptr, ct_len)) { content_type = ds->value->ptr; break; } } } #ifdef HAVE_LOCALTIME_R localtime_r(&(tmp->mtime), &tm); strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", &tm); #else strftime(datebuf, sizeof(datebuf), "%Y-%b-%d %H:%M:%S", localtime(&(tmp->mtime))); #endif http_list_directory_sizefmt(sizebuf, sizeof(sizebuf), tmp->size); buffer_append_string_len(out, CONST_STR_LEN("<tr><td class=\"n\"><a href=\"")); buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_REL_URI_PART); buffer_append_string_len(out, CONST_STR_LEN("\">")); buffer_append_string_encoded(out, DIRLIST_ENT_NAME(tmp), tmp->namelen, ENCODING_MINIMAL_XML); buffer_append_string_len(out, CONST_STR_LEN("</a></td><td class=\"m\">")); buffer_append_string_len(out, datebuf, sizeof(datebuf) - 1); buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"s\">")); buffer_append_string(out, sizebuf); buffer_append_string_len(out, CONST_STR_LEN("</td><td class=\"t\">")); buffer_append_string(out, content_type); buffer_append_string_len(out, CONST_STR_LEN("</td></tr>\n")); free(tmp); } free(files.ent); free(dirs.ent); free(path); http_list_directory_footer(srv, con, p, out); /* Insert possible charset to Content-Type */ if (buffer_string_is_empty(p->conf.encoding)) { response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html")); } else { buffer_copy_string_len(p->content_charset, CONST_STR_LEN("text/html; charset=")); buffer_append_string_buffer(p->content_charset, p->conf.encoding); response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->content_charset)); } con->file_finished = 1; chunkqueue_append_buffer(con->write_queue, out); buffer_free(out); return 0; }