| /* Copyright 2000-2004 The Apache Software Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "apr.h" |
| #include "apr_lib.h" |
| #include "apr_strings.h" |
| #include "apr_buckets.h" |
| #include "apr_md5.h" |
| #include "apr_network_io.h" |
| #include "apr_pools.h" |
| #include "apr_strings.h" |
| #include "apr_uri.h" |
| #include "apr_date.h" |
| #include "apr_fnmatch.h" |
| #define APR_WANT_STRFUNC |
| #include "apr_want.h" |
| |
| #include "apr_hooks.h" |
| #include "apr_optional_hooks.h" |
| #include "apr_buckets.h" |
| |
| #include "httpd_wrap.h" |
| |
| int AP_DECLARE_DATA ap_default_loglevel = DEFAULT_LOGLEVEL; |
| |
| static const char *levels[] = { |
| "emerg", |
| "alert", |
| "crit", |
| "error", |
| "warn", |
| "notice", |
| "info", |
| "debug", |
| NULL |
| }; |
| |
| static void log_error_core(const char *file, int line, int level, |
| apr_status_t status, |
| const char *fmt, va_list args) |
| { |
| FILE *stream; |
| char timstr[32]; |
| char errstr[MAX_STRING_LEN]; |
| |
| /* Skip the loging for lower levels */ |
| if (level < 0 || level > ap_default_loglevel) |
| return; |
| if (level < APLOG_WARNING) |
| stream = stderr; |
| else |
| stream = stdout; |
| apr_ctime(&timstr[0], apr_time_now()); |
| fprintf(stream, "[%s] [%s] ", timstr, levels[level]); |
| if (file && level == APLOG_DEBUG) { |
| #ifndef WIN32 |
| char *e = strrchr(file, '/'); |
| #else |
| char *e = strrchr(file, '\\'); |
| #endif |
| if (e) |
| fprintf(stream, "%s (%d) ", e + 1, line); |
| } |
| |
| if (status != 0) { |
| if (status < APR_OS_START_EAIERR) { |
| fprintf(stream, "(%d)", status); |
| } |
| else if (status < APR_OS_START_SYSERR) { |
| fprintf(stream, "(EAI %d)", status - APR_OS_START_EAIERR); |
| } |
| else if (status < 100000 + APR_OS_START_SYSERR) { |
| fprintf(stream, "(OS %d)", status - APR_OS_START_SYSERR); |
| } |
| else { |
| fprintf(stream, "(os 0x%08x)", status - APR_OS_START_SYSERR); |
| } |
| apr_strerror(status, errstr, MAX_STRING_LEN); |
| fprintf(stream, " %s ", errstr); |
| } |
| |
| apr_vsnprintf(errstr, MAX_STRING_LEN, fmt, args); |
| fputs(errstr, stream); |
| fputs("\n", stream); |
| if (level < APLOG_WARNING) |
| fflush(stream); |
| |
| } |
| |
| AP_DECLARE(void) ap_log_error(const char *file, int line, int level, |
| apr_status_t status, const server_rec *s, |
| const char *fmt, ...) |
| { |
| va_list args; |
| |
| va_start(args, fmt); |
| log_error_core(file, line, level, status, fmt, args); |
| va_end(args); |
| } |
| |
| AP_DECLARE(void) ap_log_perror(const char *file, int line, int level, |
| apr_status_t status, apr_pool_t *p, |
| const char *fmt, ...) |
| { |
| va_list args; |
| |
| va_start(args, fmt); |
| log_error_core(file, line, level, status, fmt, args); |
| va_end(args); |
| } |
| |
| |
| AP_DECLARE(void) ap_log_rerror(const char *file, int line, int level, |
| apr_status_t status, const request_rec *r, |
| const char *fmt, ...) |
| { |
| va_list args; |
| |
| va_start(args, fmt); |
| log_error_core(file, line, level, status, fmt, args); |
| va_end(args); |
| } |
| |
| AP_DECLARE(request_rec *) ap_wrap_create_request(conn_rec *conn) |
| { |
| request_rec *r; |
| apr_pool_t *p; |
| |
| apr_pool_create(&p, conn->pool); |
| apr_pool_tag(p, "request"); |
| r = apr_pcalloc(p, sizeof(request_rec)); |
| r->pool = p; |
| r->connection = conn; |
| r->server = conn->base_server; |
| |
| r->user = NULL; |
| r->ap_auth_type = NULL; |
| |
| r->headers_in = apr_table_make(r->pool, 25); |
| r->subprocess_env = apr_table_make(r->pool, 25); |
| r->headers_out = apr_table_make(r->pool, 12); |
| r->err_headers_out = apr_table_make(r->pool, 5); |
| r->notes = apr_table_make(r->pool, 5); |
| |
| |
| r->status = HTTP_REQUEST_TIME_OUT; /* Until we get a request */ |
| r->the_request = NULL; |
| |
| r->status = HTTP_OK; /* Until further notice. */ |
| return r; |
| } |
| |
| AP_DECLARE(process_rec *) ap_wrap_create_process(int argc, const char * const *argv) |
| { |
| process_rec *process; |
| apr_pool_t *cntx; |
| apr_status_t stat; |
| |
| stat = apr_pool_create(&cntx, NULL); |
| if (stat != APR_SUCCESS) { |
| /* XXX From the time that we took away the NULL pool->malloc mapping |
| * we have been unable to log here without segfaulting. |
| */ |
| ap_log_error(APLOG_MARK, APLOG_ERR, stat, NULL, |
| "apr_pool_create() failed to create " |
| "initial context"); |
| apr_terminate(); |
| exit(1); |
| } |
| |
| apr_pool_tag(cntx, "process"); |
| |
| process = apr_palloc(cntx, sizeof(process_rec)); |
| process->pool = cntx; |
| |
| apr_pool_create(&process->pconf, process->pool); |
| apr_pool_tag(process->pconf, "pconf"); |
| process->argc = argc; |
| process->argv = argv; |
| process->short_name = apr_filepath_name_get(argv[0]); |
| return process; |
| } |
| |
| AP_DECLARE(server_rec *) ap_wrap_create_server(process_rec *process, apr_pool_t *p) |
| { |
| apr_status_t rv; |
| server_rec *s = (server_rec *) apr_pcalloc(p, sizeof(server_rec)); |
| |
| s->process = process; |
| s->port = 0; |
| s->server_admin = DEFAULT_ADMIN; |
| s->server_hostname = NULL; |
| s->loglevel = DEFAULT_LOGLEVEL; |
| s->limit_req_line = DEFAULT_LIMIT_REQUEST_LINE; |
| s->limit_req_fieldsize = DEFAULT_LIMIT_REQUEST_FIELDSIZE; |
| s->limit_req_fields = DEFAULT_LIMIT_REQUEST_FIELDS; |
| s->timeout = apr_time_from_sec(DEFAULT_TIMEOUT); |
| s->keep_alive_timeout = apr_time_from_sec(DEFAULT_KEEPALIVE_TIMEOUT); |
| s->keep_alive_max = DEFAULT_KEEPALIVE; |
| s->keep_alive = 1; |
| s->addrs = apr_pcalloc(p, sizeof(server_addr_rec)); |
| |
| /* NOT virtual host; don't match any real network interface */ |
| rv = apr_sockaddr_info_get(&s->addrs->host_addr, |
| NULL, APR_INET, 0, 0, p); |
| |
| s->addrs->host_port = 0; /* matches any port */ |
| s->addrs->virthost = ""; /* must be non-NULL */ |
| |
| return s; |
| } |
| |
| AP_DECLARE(conn_rec *) ap_run_create_connection(apr_pool_t *ptrans, |
| server_rec *server, |
| apr_socket_t *csd, long id, void *sbh, |
| apr_bucket_alloc_t *alloc) |
| { |
| apr_status_t rv; |
| conn_rec *c = (conn_rec *) apr_pcalloc(ptrans, sizeof(conn_rec)); |
| |
| c->sbh = sbh; |
| |
| c->conn_config = ap_create_conn_config(ptrans); |
| /* Got a connection structure, so initialize what fields we can |
| * (the rest are zeroed out by pcalloc). |
| */ |
| c->notes = apr_table_make(ptrans, 5); |
| |
| c->pool = ptrans; |
| |
| /* Socket is used only for backend connections |
| * Since we don't have client socket skip the |
| * creation of adresses. They will be default |
| * to 127.0.0.1:0 both local and remote |
| */ |
| if (csd) { |
| if ((rv = apr_socket_addr_get(&c->local_addr, APR_LOCAL, csd)) |
| != APR_SUCCESS) { |
| ap_log_error(APLOG_MARK, APLOG_INFO, rv, server, |
| "apr_socket_addr_get(APR_LOCAL)"); |
| apr_socket_close(csd); |
| return NULL; |
| } |
| if ((rv = apr_socket_addr_get(&c->remote_addr, APR_REMOTE, csd)) |
| != APR_SUCCESS) { |
| ap_log_error(APLOG_MARK, APLOG_INFO, rv, server, |
| "apr_socket_addr_get(APR_REMOTE)"); |
| apr_socket_close(csd); |
| return NULL; |
| } |
| } |
| else { |
| /* localhost should be reachable on all platforms */ |
| if ((rv = apr_sockaddr_info_get(&c->local_addr, "localhost", |
| APR_UNSPEC, 0, |
| APR_IPV4_ADDR_OK, |
| c->pool)) |
| != APR_SUCCESS) { |
| ap_log_error(APLOG_MARK, APLOG_INFO, rv, server, |
| "apr_sockaddr_info_get()"); |
| return NULL; |
| } |
| c->remote_addr = c->local_addr; |
| } |
| apr_sockaddr_ip_get(&c->local_ip, c->local_addr); |
| apr_sockaddr_ip_get(&c->remote_ip, c->remote_addr); |
| c->base_server = server; |
| |
| c->id = id; |
| c->bucket_alloc = alloc; |
| |
| return c; |
| } |
| |
| AP_DECLARE(const char *) ap_get_remote_host(conn_rec *conn, void *dir_config, |
| int type, int *str_is_ip) |
| { |
| int ignored_str_is_ip; |
| |
| if (!str_is_ip) { /* caller doesn't want to know */ |
| str_is_ip = &ignored_str_is_ip; |
| } |
| *str_is_ip = 0; |
| |
| /* |
| * Return the desired information; either the remote DNS name, if found, |
| * or either NULL (if the hostname was requested) or the IP address |
| * (if any identifier was requested). |
| */ |
| if (conn->remote_host != NULL && conn->remote_host[0] != '\0') { |
| return conn->remote_host; |
| } |
| else { |
| if (type == REMOTE_HOST || type == REMOTE_DOUBLE_REV) { |
| return NULL; |
| } |
| else { |
| *str_is_ip = 1; |
| return conn->remote_ip; |
| } |
| } |
| } |
| |
| AP_DECLARE(const char *) ap_get_server_name(request_rec *r) |
| { |
| /* default */ |
| return r->server->server_hostname; |
| } |
| |
| AP_DECLARE(void) ap_set_content_type(request_rec *r, const char *ct) |
| { |
| r->content_type = ct; |
| } |
| |
| |
| #define UNKNOWN_METHOD (-1) |
| |
| static int lookup_builtin_method(const char *method, apr_size_t len) |
| { |
| /* Note: the following code was generated by the "shilka" tool from |
| the "cocom" parsing/compilation toolkit. It is an optimized lookup |
| based on analysis of the input keywords. Postprocessing was done |
| on the shilka output, but the basic structure and analysis is |
| from there. Should new HTTP methods be added, then manual insertion |
| into this code is fine, or simply re-running the shilka tool on |
| the appropriate input. */ |
| |
| /* Note: it is also quite reasonable to just use our method_registry, |
| but I'm assuming (probably incorrectly) we want more speed here |
| (based on the optimizations the previous code was doing). */ |
| |
| switch (len) |
| { |
| case 3: |
| switch (method[0]) |
| { |
| case 'P': |
| return (method[1] == 'U' |
| && method[2] == 'T' |
| ? M_PUT : UNKNOWN_METHOD); |
| case 'G': |
| return (method[1] == 'E' |
| && method[2] == 'T' |
| ? M_GET : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 4: |
| switch (method[0]) |
| { |
| case 'H': |
| return (method[1] == 'E' |
| && method[2] == 'A' |
| && method[3] == 'D' |
| ? M_GET : UNKNOWN_METHOD); |
| case 'P': |
| return (method[1] == 'O' |
| && method[2] == 'S' |
| && method[3] == 'T' |
| ? M_POST : UNKNOWN_METHOD); |
| case 'M': |
| return (method[1] == 'O' |
| && method[2] == 'V' |
| && method[3] == 'E' |
| ? M_MOVE : UNKNOWN_METHOD); |
| case 'L': |
| return (method[1] == 'O' |
| && method[2] == 'C' |
| && method[3] == 'K' |
| ? M_LOCK : UNKNOWN_METHOD); |
| case 'C': |
| return (method[1] == 'O' |
| && method[2] == 'P' |
| && method[3] == 'Y' |
| ? M_COPY : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 5: |
| switch (method[2]) |
| { |
| case 'T': |
| return (memcmp(method, "PATCH", 5) == 0 |
| ? M_PATCH : UNKNOWN_METHOD); |
| case 'R': |
| return (memcmp(method, "MERGE", 5) == 0 |
| ? M_MERGE : UNKNOWN_METHOD); |
| case 'C': |
| return (memcmp(method, "MKCOL", 5) == 0 |
| ? M_MKCOL : UNKNOWN_METHOD); |
| case 'B': |
| return (memcmp(method, "LABEL", 5) == 0 |
| ? M_LABEL : UNKNOWN_METHOD); |
| case 'A': |
| return (memcmp(method, "TRACE", 5) == 0 |
| ? M_TRACE : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 6: |
| switch (method[0]) |
| { |
| case 'U': |
| switch (method[5]) |
| { |
| case 'K': |
| return (memcmp(method, "UNLOCK", 6) == 0 |
| ? M_UNLOCK : UNKNOWN_METHOD); |
| case 'E': |
| return (memcmp(method, "UPDATE", 6) == 0 |
| ? M_UPDATE : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| case 'R': |
| return (memcmp(method, "REPORT", 6) == 0 |
| ? M_REPORT : UNKNOWN_METHOD); |
| case 'D': |
| return (memcmp(method, "DELETE", 6) == 0 |
| ? M_DELETE : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 7: |
| switch (method[1]) |
| { |
| case 'P': |
| return (memcmp(method, "OPTIONS", 7) == 0 |
| ? M_OPTIONS : UNKNOWN_METHOD); |
| case 'O': |
| return (memcmp(method, "CONNECT", 7) == 0 |
| ? M_CONNECT : UNKNOWN_METHOD); |
| case 'H': |
| return (memcmp(method, "CHECKIN", 7) == 0 |
| ? M_CHECKIN : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 8: |
| switch (method[0]) |
| { |
| case 'P': |
| return (memcmp(method, "PROPFIND", 8) == 0 |
| ? M_PROPFIND : UNKNOWN_METHOD); |
| case 'C': |
| return (memcmp(method, "CHECKOUT", 8) == 0 |
| ? M_CHECKOUT : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 9: |
| return (memcmp(method, "PROPPATCH", 9) == 0 |
| ? M_PROPPATCH : UNKNOWN_METHOD); |
| |
| case 10: |
| switch (method[0]) |
| { |
| case 'U': |
| return (memcmp(method, "UNCHECKOUT", 10) == 0 |
| ? M_UNCHECKOUT : UNKNOWN_METHOD); |
| case 'M': |
| return (memcmp(method, "MKACTIVITY", 10) == 0 |
| ? M_MKACTIVITY : UNKNOWN_METHOD); |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| case 11: |
| return (memcmp(method, "MKWORKSPACE", 11) == 0 |
| ? M_MKWORKSPACE : UNKNOWN_METHOD); |
| |
| case 15: |
| return (memcmp(method, "VERSION-CONTROL", 15) == 0 |
| ? M_VERSION_CONTROL : UNKNOWN_METHOD); |
| |
| case 16: |
| return (memcmp(method, "BASELINE-CONTROL", 16) == 0 |
| ? M_BASELINE_CONTROL : UNKNOWN_METHOD); |
| |
| default: |
| return UNKNOWN_METHOD; |
| } |
| |
| /* NOTREACHED */ |
| } |
| |
| /* Get the method number associated with the given string, assumed to |
| * contain an HTTP method. Returns M_INVALID if not recognized. |
| * |
| * This is the first step toward placing method names in a configurable |
| * list. Hopefully it (and other routines) can eventually be moved to |
| * something like a mod_http_methods.c, complete with config stuff. |
| */ |
| AP_DECLARE(int) ap_method_number_of(const char *method) |
| { |
| apr_size_t len = strlen(method); |
| int which = lookup_builtin_method(method, len); |
| |
| if (which != UNKNOWN_METHOD) |
| return which; |
| |
| return M_INVALID; |
| } |
| |
| static ap_conf_vector_t *create_empty_config(apr_pool_t *p) |
| { |
| void *conf_vector = apr_pcalloc(p, sizeof(void *) * |
| (DYNAMIC_MODULE_LIMIT)); |
| return conf_vector; |
| } |
| |
| AP_CORE_DECLARE(ap_conf_vector_t *) ap_create_request_config(apr_pool_t *p) |
| { |
| return create_empty_config(p); |
| } |
| |
| AP_CORE_DECLARE(ap_conf_vector_t *) ap_create_conn_config(apr_pool_t *p) |
| { |
| return create_empty_config(p); |
| } |
| |
| AP_DECLARE(apr_status_t) ap_wrap_make_request(request_rec *r, const char *url, |
| const char *method, |
| const char *content_type, |
| const char *content_encoding, |
| apr_size_t content_length, char *content) |
| { |
| apr_status_t rc; |
| |
| if (!url) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Missing url"); |
| return APR_EINVAL; |
| } |
| if (!method) |
| method = "GET"; |
| if ((r->method_number = lookup_builtin_method(method, strlen(method))) == |
| UNKNOWN_METHOD) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Unknown HTTP metdod %s", |
| method); |
| return APR_EINVAL; |
| } |
| r->method = method; |
| r->protocol = AP_SERVER_PROTOCOL; |
| r->proto_num = HTTP_VERSION(1, 1); |
| r->request_config = ap_create_request_config(r->pool); |
| |
| if ((rc = apr_uri_parse(r->pool, url, &r->parsed_uri)) != APR_SUCCESS) { |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, "error parsing uri"); |
| return APR_EINVAL; |
| } |
| r->user = r->parsed_uri.user; |
| r->content_type = content_type; |
| r->content_encoding = content_encoding; |
| r->uri = r->parsed_uri.path; |
| if (r->parsed_uri.query) |
| r->unparsed_uri = apr_pstrcat(r->pool, r->parsed_uri.path, "?", |
| r->parsed_uri.query, NULL); |
| else |
| r->unparsed_uri = r->uri; |
| if (!r->parsed_uri.port) |
| r->parsed_uri.port = r->server->port; |
| |
| if (r->parsed_uri.hostname) { |
| if (r->parsed_uri.port) |
| apr_table_addn(r->headers_in, "Host", |
| apr_psprintf(r->pool, "%s:%d", r->parsed_uri.hostname, r->parsed_uri.port)); |
| else |
| apr_table_addn(r->headers_in, "Host", r->parsed_uri.hostname); |
| } |
| if (r->content_type) |
| apr_table_addn(r->headers_in, "Content-Type", r->content_type); |
| if (r->content_encoding) |
| apr_table_addn(r->headers_in, "Transfer-Encoding", r->content_encoding); |
| if (content_length) |
| apr_table_addn(r->headers_in, "Content-Length", apr_itoa(r->pool, |
| (int)content_length)); |
| apr_table_addn(r->headers_in, "Accept", "*/*"); |
| apr_table_addn(r->headers_in, "Pragma", "no-cache"); |
| apr_table_addn(r->headers_in, "User-Agent", "httpd-wrap/1.0"); |
| apr_table_addn(r->headers_in, "Accept-Charset", "iso-8859-2"); |
| apr_table_addn(r->headers_in, "Accept-Language", "hr"); |
| |
| /* Create a simple bucket brigade for post data inside connection */ |
| if (content) { |
| apr_bucket *e; |
| if (!r->connection->bucket_alloc) |
| r->connection->bucket_alloc = apr_bucket_alloc_create(r->connection->pool); |
| r->connection->bb = apr_brigade_create(r->connection->pool, |
| r->connection->bucket_alloc); |
| e = apr_bucket_transient_create(content, content_length, r->connection->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(r->connection->bb, e); |
| |
| } |
| return APR_SUCCESS; |
| } |
| |
| AP_DECLARE(int) ap_setup_client_block(request_rec *r, int read_policy) |
| { |
| const char *lenp = apr_table_get(r->headers_in, "Content-Length"); |
| |
| r->remaining = 0; |
| r->read_length = 0; |
| if (lenp) |
| r->remaining = atoi(lenp); |
| return OK; |
| } |
| |
| AP_DECLARE(int) ap_should_client_block(request_rec *r) |
| { |
| /* First check if we have already read the request body */ |
| /* Since we are not using chunked skip the readed checking */ |
| if (r->read_length) |
| return 0; |
| else |
| return 1; |
| } |
| |
| AP_DECLARE(long) ap_get_client_block(request_rec *r, char *buffer, |
| apr_size_t bufsiz) |
| { |
| apr_status_t rv; |
| |
| if (r->remaining < 0 || r->remaining == 0) |
| return 0; |
| |
| rv = apr_brigade_flatten(r->connection->bb, buffer, &bufsiz); |
| |
| if (rv != APR_SUCCESS) { |
| return -1; |
| } |
| |
| r->read_length += bufsiz; |
| r->remaining -= bufsiz; |
| |
| /* Remove the readed part */ |
| if (bufsiz) { |
| apr_bucket *e; |
| e = APR_BRIGADE_FIRST(r->connection->bb); |
| apr_bucket_split(e, bufsiz); |
| e = APR_BRIGADE_FIRST(r->connection->bb); |
| APR_BUCKET_REMOVE(e); |
| } |
| |
| return (long)bufsiz; |
| |
| } |
| |
| /* No op */ |
| AP_DECLARE(int) ap_discard_request_body(request_rec *r) |
| { |
| return OK; |
| } |
| |