blob: a975e55086f4a723b9f982bbf312c625801ac5cc [file] [log] [blame]
/* 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;
}