cmake_minimum_required(VERSION 2.6)
PROJECT(uhttpd C)
-ADD_DEFINITIONS(-Os -Wall -Werror -Wmissing-declarations --std=gnu99 -g3)
+ADD_DEFINITIONS(-O2 -Wall -Werror -Wmissing-declarations --std=gnu99 -g3)
IF(APPLE)
INCLUDE_DIRECTORIES(/opt/local/include)
LINK_DIRECTORIES(/opt/local/lib)
ENDIF()
-ADD_EXECUTABLE(uhttpd main.c listen.c client.c utils.c file.c auth.c)
+ADD_EXECUTABLE(uhttpd main.c listen.c client.c utils.c file.c auth.c cgi.c relay.c proc.c)
TARGET_LINK_LIBRARIES(uhttpd ubox ubus)
--- /dev/null
+/*
+ * uhttpd - Tiny single-threaded httpd
+ *
+ * Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+ * Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
+ *
+ * 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 <libubox/blobmsg.h>
+#include "uhttpd.h"
+
+static LIST_HEAD(interpreters);
+
+void uh_interpreter_add(const char *ext, const char *path)
+{
+ struct interpreter *in;
+ char *new_ext, *new_path;
+
+ in = calloc_a(sizeof(*in),
+ &new_ext, strlen(ext) + 1,
+ &new_path, strlen(path) + 1);
+
+ in->ext = strcpy(new_ext, ext);
+ in->path = strcpy(new_path, path);
+ list_add_tail(&in->list, &interpreters);
+}
+
+static void cgi_main(struct client *cl, struct path_info *pi, int fd)
+{
+ struct interpreter *ip = pi->ip;
+ struct env_var *var;
+
+ dup2(fd, 0);
+ dup2(fd, 1);
+ close(fd);
+ clearenv();
+ setenv("PATH", conf.cgi_path, 1);
+
+ for (var = uh_get_process_vars(cl, pi); var->name; var++) {
+ if (!var->value)
+ continue;
+
+ setenv(var->name, var->value, 1);
+ }
+
+ chdir(pi->root);
+
+ if (ip)
+ execl(ip->path, ip->path, pi->phys, NULL);
+ else
+ execl(pi->phys, pi->phys, NULL);
+
+ printf("Status: 500 Internal Server Error\r\n\r\n"
+ "Unable to launch the requested CGI program:\n"
+ " %s: %s\n", ip ? ip->path : pi->phys, strerror(errno));
+}
+
+static void cgi_handle_request(struct client *cl, const char *url, struct path_info *pi)
+{
+ unsigned int mode = S_IFREG | S_IXOTH;
+
+ if (!pi->ip && !((pi->stat.st_mode & mode) == mode)) {
+ uh_client_error(cl, 403, "Forbidden",
+ "You don't have permission to access %s on this server.",
+ url);
+ return;
+ }
+
+ if (!uh_create_process(cl, pi, cgi_main)) {
+ uh_client_error(cl, 500, "Internal Server Error",
+ "Failed to create CGI process: %s", strerror(errno));
+ return;
+ }
+
+ return;
+}
+
+static bool check_cgi_path(struct path_info *pi, const char *url)
+{
+ struct interpreter *ip;
+ const char *path = pi->phys;
+ int path_len = strlen(path);
+
+ list_for_each_entry(ip, &interpreters, list) {
+ int len = strlen(ip->ext);
+
+ if (len >= path_len)
+ continue;
+
+ if (strcmp(path + path_len - len, ip->ext) != 0)
+ continue;
+
+ pi->ip = ip;
+ return true;
+ }
+
+ pi->ip = NULL;
+ return uh_path_match(conf.cgi_prefix, url);
+}
+
+struct dispatch_handler cgi_dispatch = {
+ .check_path = check_cgi_path,
+ .handle_request = cgi_handle_request,
+};
static void client_header_complete(struct client *cl)
{
- uh_handle_file_request(cl);
+ uh_handle_request(cl);
}
static int client_parse_header(struct client *cl, char *data)
static char _tag[128];
static LIST_HEAD(index_files);
+static LIST_HEAD(dispatch_handlers);
struct index_file {
struct list_head list;
file_write_cb(cl);
}
-static void uh_file_request(struct client *cl, struct path_info *pi, const char *url)
+static void uh_file_request(struct client *cl, const char *url, struct path_info *pi)
{
static const struct blobmsg_policy hdr_policy[__HDR_MAX] = {
[HDR_IF_MODIFIED_SINCE] = { "if-modified-since", BLOBMSG_TYPE_STRING },
goto error;
}
+ cl->dispatch.file.hdr = NULL;
return;
error:
uh_client_error(cl, 403, "Forbidden",
"You don't have permission to access %s on this server.",
url);
+ cl->dispatch.file.hdr = NULL;
+}
+
+void uh_dispatch_add(struct dispatch_handler *d)
+{
+ list_add_tail(&d->list, &dispatch_handlers);
+}
+
+static struct dispatch_handler *
+dispatch_find(const char *url, struct path_info *pi)
+{
+ struct dispatch_handler *d;
+
+ list_for_each_entry(d, &dispatch_handlers, list) {
+ if (pi) {
+ if (d->check_url)
+ continue;
+
+ if (d->check_path(pi, url))
+ return d;
+ } else {
+ if (d->check_path)
+ continue;
+
+ if (d->check_url(url))
+ return d;
+ }
+ }
+
+ return NULL;
}
static bool __handle_file_request(struct client *cl, const char *url)
{
+ struct dispatch_handler *d;
struct path_info *pi;
pi = uh_path_lookup(cl, url);
if (!pi)
return false;
- if (!pi->redirected) {
- uh_file_request(cl, pi, url);
- cl->dispatch.file.hdr = NULL;
- }
+ if (pi->redirected)
+ return true;
+
+ d = dispatch_find(url, pi);
+ if (d)
+ d->handle_request(cl, url, pi);
+ else
+ uh_file_request(cl, url, pi);
return true;
}
-void uh_handle_file_request(struct client *cl)
+void uh_handle_request(struct client *cl)
{
- if (__handle_file_request(cl, cl->request.url) ||
+ struct dispatch_handler *d;
+ const char *url = cl->request.url;
+
+ d = dispatch_find(url, NULL);
+ if (d) {
+ d->handle_request(cl, url, NULL);
+ return;
+ }
+
+ if (__handle_file_request(cl, url) ||
__handle_file_request(cl, conf.error_handler))
return;
" -u string URL prefix for HTTP/JSON handler\n"
" -U file Override ubus socket path\n"
#endif
-#ifdef HAVE_CGI
" -x string URL prefix for CGI handler, default is '/cgi-bin'\n"
" -i .ext=path Use interpreter at path for files with the given extension\n"
-#endif
-#if defined(HAVE_CGI) || defined(HAVE_LUA) || defined(HAVE_UBUS)
" -t seconds CGI, Lua and UBUS script timeout in seconds, default is 60\n"
-#endif
" -T seconds Network timeout in seconds, default is 30\n"
" -d string URL decode given string\n"
" -r string Specify basic auth realm\n"
uh_index_add("default.htm");
}
+static void fixup_prefix(char *str)
+{
+ int len;
+
+ if (!str || !str[0])
+ return;
+
+ len = strlen(str) - 1;
+
+ while (len > 0 && str[len] == '/')
+ len--;
+
+ str[len + 1] = 0;
+}
+
int main(int argc, char **argv)
{
bool nofork = false;
BUILD_BUG_ON(sizeof(uh_buf) < PATH_MAX);
+ uh_dispatch_add(&cgi_dispatch);
init_defaults();
signal(SIGPIPE, SIG_IGN);
conf.max_requests = atoi(optarg);
break;
+ case 'x':
+ fixup_prefix(optarg);
+ conf.cgi_prefix = optarg;
+ break;
+
+ case 'i':
+ port = strchr(optarg, '=');
+ if (optarg[0] != '.' || !port) {
+ fprintf(stderr, "Error: Invalid interpreter: %s\n",
+ optarg);
+ exit(1);
+ }
+
+ *port++ = 0;
+ uh_interpreter_add(optarg, port);
+ break;
+
case 't':
conf.script_timeout = atoi(optarg);
break;
--- /dev/null
+/*
+ * uhttpd - Tiny single-threaded httpd
+ *
+ * Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+ * Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
+ *
+ * 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 <libubox/blobmsg.h>
+#include "uhttpd.h"
+
+#define __headers \
+ __header(accept) \
+ __header(accept_charset) \
+ __header(accept_encoding) \
+ __header(accept_language) \
+ __header(authorization) \
+ __header(connection) \
+ __header(cookie) \
+ __header(host) \
+ __header(referer) \
+ __header(user_agent) \
+ __header(content_type) \
+ __header(content_length)
+
+#undef __header
+#define __header __enum_header
+enum client_hdr {
+ __headers
+ __HDR_MAX,
+};
+
+#undef __header
+#define __header __blobmsg_header
+static const struct blobmsg_policy hdr_policy[__HDR_MAX] = {
+ __headers
+};
+
+static const struct {
+ const char *name;
+ int idx;
+} proc_header_env[] = {
+ { "HTTP_ACCEPT", HDR_accept },
+ { "HTTP_ACCEPT_CHARSET", HDR_accept_charset },
+ { "HTTP_ACCEPT_ENCODING", HDR_accept_encoding },
+ { "HTTP_ACCEPT_LANGUAGE", HDR_accept_language },
+ { "HTTP_AUTHORIZATION", HDR_authorization },
+ { "HTTP_CONNECTION", HDR_connection },
+ { "HTTP_COOKIE", HDR_cookie },
+ { "HTTP_HOST", HDR_host },
+ { "HTTP_REFERER", HDR_referer },
+ { "HTTP_USER_AGENT", HDR_user_agent },
+ { "CONTENT_TYPE", HDR_content_type },
+ { "CONTENT_LENGTH", HDR_content_length },
+};
+
+enum extra_vars {
+ /* no update needed */
+ _VAR_GW,
+ _VAR_SOFTWARE,
+
+ /* updated by uh_get_process_vars */
+ VAR_SCRIPT_NAME,
+ VAR_SCRIPT_FILE,
+ VAR_DOCROOT,
+ VAR_QUERY,
+ VAR_REQUEST,
+ VAR_PROTO,
+ VAR_METHOD,
+ VAR_PATH_INFO,
+ VAR_USER,
+ VAR_REDIRECT,
+
+ __VAR_MAX,
+};
+
+static struct env_var extra_vars[] = {
+ [_VAR_GW] = { "GATEWAY_INTERFACE", "CGI/1.1" },
+ [_VAR_SOFTWARE] = { "SERVER_SOFTWARE", "uhttpd" },
+ [VAR_SCRIPT_NAME] = { "SCRIPT_NAME" },
+ [VAR_SCRIPT_FILE] = { "SCRIPT_FILENAME" },
+ [VAR_DOCROOT] = { "DOCUMENT_ROOT" },
+ [VAR_QUERY] = { "QUERY_STRING" },
+ [VAR_REQUEST] = { "REQUEST_URI" },
+ [VAR_PROTO] = { "SERVER_PROTOCOL" },
+ [VAR_METHOD] = { "REQUEST_METHOD" },
+ [VAR_PATH_INFO] = { "PATH_INFO" },
+ [VAR_USER] = { "REMOTE_USER" },
+ [VAR_REDIRECT] = { "REDIRECT_STATUS" },
+};
+
+struct env_var *uh_get_process_vars(struct client *cl, struct path_info *pi)
+{
+ struct http_request *req = &cl->request;
+ struct blob_attr *data = cl->hdr.head;
+ struct env_var *vars = (void *) uh_buf;
+ struct blob_attr *tb[__HDR_MAX];
+ static char buf[4];
+ int len;
+ int i;
+
+ len = ARRAY_SIZE(proc_header_env);
+ len += ARRAY_SIZE(extra_vars);
+ len *= sizeof(struct env_var);
+
+ BUILD_BUG_ON(sizeof(uh_buf) < len);
+
+ extra_vars[VAR_SCRIPT_NAME].value = pi->name;
+ extra_vars[VAR_SCRIPT_FILE].value = pi->phys;
+ extra_vars[VAR_DOCROOT].value = pi->root;
+ extra_vars[VAR_QUERY].value = pi->query ? pi->query : "";
+ extra_vars[VAR_REQUEST].value = req->url;
+ extra_vars[VAR_PROTO].value = http_versions[req->version];
+ extra_vars[VAR_METHOD].value = http_methods[req->method];
+ extra_vars[VAR_PATH_INFO].value = pi->info;
+ extra_vars[VAR_USER].value = req->realm ? req->realm->user : NULL;
+
+ snprintf(buf, sizeof(buf), "%d", req->redirect_status);
+ extra_vars[VAR_REDIRECT].value = buf;
+
+ blobmsg_parse(hdr_policy, __HDR_MAX, tb, blob_data(data), blob_len(data));
+ for (i = 0; i < ARRAY_SIZE(proc_header_env); i++) {
+ struct blob_attr *cur;
+
+ cur = tb[proc_header_env[i].idx];
+ vars[i].name = proc_header_env[i].name;
+ vars[i].value = cur ? blobmsg_data(cur) : "";
+ }
+
+ memcpy(&vars[i], extra_vars, sizeof(extra_vars));
+ i += ARRAY_SIZE(extra_vars);
+ vars[i].name = NULL;
+ vars[i].value = NULL;
+
+ return vars;
+}
+
+static void proc_close_fds(struct client *cl)
+{
+ close(cl->dispatch.proc.r.sfd.fd.fd);
+}
+
+static void proc_handle_close(struct relay *r, int ret)
+{
+ if (r->header_cb) {
+ uh_client_error(r->cl, 502, "Bad Gateway",
+ "The process did not produce any response");
+ return;
+ }
+
+ uh_request_done(r->cl);
+}
+
+static void proc_handle_header(struct relay *r, const char *name, const char *val)
+{
+ static char status_buf[64];
+ struct client *cl = r->cl;
+ char *sep;
+ char buf[4];
+
+ if (strcmp(name, "Status")) {
+ sep = strchr(val, ' ');
+ if (sep != val + 3)
+ return;
+
+ memcpy(buf, val, 3);
+ buf[3] = 0;
+ snprintf(status_buf, sizeof(status_buf), "%s", sep + 1);
+ cl->dispatch.proc.status_msg = status_buf;
+ return;
+ }
+
+ blobmsg_add_string(&cl->dispatch.proc.hdr, name, val);
+}
+
+static void proc_handle_header_end(struct relay *r)
+{
+ struct client *cl = r->cl;
+ struct blob_attr *cur;
+ int rem;
+
+ uh_http_header(cl, cl->dispatch.proc.status_code, cl->dispatch.proc.status_msg);
+ blob_for_each_attr(cur, cl->dispatch.proc.hdr.head, rem)
+ ustream_printf(cl->us, "%s: %s\r\n", blobmsg_name(cur), blobmsg_data(cur));
+
+ ustream_printf(cl->us, "\r\n");
+}
+
+static void proc_free(struct client *cl)
+{
+ uh_relay_free(&cl->dispatch.proc.r);
+}
+
+bool uh_create_process(struct client *cl, struct path_info *pi,
+ void (*cb)(struct client *cl, struct path_info *pi, int fd))
+{
+ int fds[2];
+ int pid;
+
+ blob_buf_init(&cl->dispatch.proc.hdr, 0);
+ cl->dispatch.proc.status_code = 200;
+ cl->dispatch.proc.status_msg = "OK";
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds))
+ return false;
+
+ pid = fork();
+ if (pid < 0) {
+ close(fds[0]);
+ close(fds[1]);
+ return false;
+ }
+
+ if (!pid) {
+ close(fds[0]);
+ uh_close_fds();
+ cb(cl, pi, fds[1]);
+ exit(0);
+ }
+
+ close(fds[1]);
+ uh_relay_open(cl, &cl->dispatch.proc.r, fds[0], pid);
+ cl->dispatch.free = proc_free;
+ cl->dispatch.close_fds = proc_close_fds;
+ cl->dispatch.proc.r.header_cb = proc_handle_header;
+ cl->dispatch.proc.r.header_end = proc_handle_header_end;
+ cl->dispatch.proc.r.close = proc_handle_close;
+
+ return true;
+}
--- /dev/null
+/*
+ * uhttpd - Tiny single-threaded httpd
+ *
+ * Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
+ * Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
+ *
+ * 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 <signal.h>
+#include "uhttpd.h"
+
+void uh_relay_free(struct relay *r)
+{
+ if (!r->cl)
+ return;
+
+ if (r->proc.pending)
+ kill(r->proc.pid, SIGKILL);
+
+ uloop_process_delete(&r->proc);
+ ustream_free(&r->sfd.stream);
+ close(r->sfd.fd.fd);
+
+ r->cl = NULL;
+}
+
+void uh_relay_close(struct relay *r, int ret)
+{
+ struct ustream *us = &r->sfd.stream;
+
+ if (!us->notify_read)
+ return;
+
+ us->notify_read = NULL;
+ us->notify_write = NULL;
+ us->notify_state = NULL;
+
+ if (r->close)
+ r->close(r, ret);
+}
+
+static void relay_error(struct relay *r)
+{
+ struct ustream *s = &r->sfd.stream;
+ int len;
+
+ s->eof = true;
+ ustream_get_read_buf(s, &len);
+ if (len)
+ ustream_consume(s, len);
+ ustream_state_change(s);
+}
+
+static void relay_process_headers(struct relay *r)
+{
+ struct ustream *s = &r->sfd.stream;
+ char *buf, *newline;
+ int len;
+
+ if (!r->header_cb)
+ return;
+
+ while (r->header_cb) {
+ int line_len;
+ char *val;
+
+ buf = ustream_get_read_buf(s, &len);
+ newline = strchr(buf, '\n');
+ if (!newline)
+ break;
+
+ line_len = newline + 1 - buf;
+ if (newline > buf && newline[-1] == '\r') {
+ newline--;
+ line_len++;
+ }
+
+ *newline = 0;
+ if (newline == buf) {
+ r->header_cb = NULL;
+ if (r->header_end)
+ r->header_end(r);
+ break;
+ }
+
+ val = uh_split_header(buf);
+ if (!val) {
+ relay_error(r);
+ return;
+ }
+
+ r->header_cb(r, buf, val);
+ ustream_consume(s, line_len);
+ }
+}
+
+static void relay_read_cb(struct ustream *s, int bytes)
+{
+ struct relay *r = container_of(s, struct relay, sfd.stream);
+ struct client *cl = r->cl;
+ struct ustream *us = cl->us;
+ char *buf;
+ int len;
+
+ relay_process_headers(r);
+
+ if (r->header_cb) {
+ /*
+ * if eof, ensure that remaining data is discarded, so the
+ * state change cb will tear down the stream
+ */
+ if (s->eof)
+ relay_error(r);
+ return;
+ }
+
+ if (!s->eof && ustream_pending_data(us, true)) {
+ ustream_set_read_blocked(s, true);
+ return;
+ }
+
+ buf = ustream_get_read_buf(s, &len);
+ uh_chunk_write(cl, buf, len);
+ ustream_consume(s, len);
+}
+
+static void relay_close_if_done(struct relay *r)
+{
+ struct ustream *s = &r->sfd.stream;
+
+ if (!s->eof || ustream_pending_data(s, false))
+ return;
+
+ uh_relay_close(r, r->ret);
+}
+
+static void relay_state_cb(struct ustream *s)
+{
+ struct relay *r = container_of(s, struct relay, sfd.stream);
+
+ if (r->process_done)
+ relay_close_if_done(r);
+}
+
+static void relay_proc_cb(struct uloop_process *proc, int ret)
+{
+ struct relay *r = container_of(proc, struct relay, proc);
+
+ r->process_done = true;
+ r->ret = ret;
+ relay_close_if_done(r);
+}
+
+void uh_relay_open(struct client *cl, struct relay *r, int fd, int pid)
+{
+ struct ustream *us = &r->sfd.stream;
+
+ r->cl = cl;
+ ustream_fd_init(&r->sfd, fd);
+ us->notify_read = relay_read_cb;
+ us->notify_state = relay_state_cb;
+ us->string_data = true;
+
+ r->proc.pid = pid;
+ r->proc.cb = relay_proc_cb;
+ uloop_process_add(&r->proc);
+}
#define UH_LIMIT_CLIENTS 64
#define UH_LIMIT_HEADERS 64
+#define __enum_header(_name) HDR_##_name,
+#define __blobmsg_header(_name) [HDR_##_name] = { .name = #_name, .type = BLOBMSG_TYPE_STRING },
+
+struct client;
+
struct config {
const char *docroot;
const char *realm;
int script_timeout;
};
-struct path_info {
- const char *root;
- const char *phys;
- const char *name;
- const char *info;
- const char *query;
- int redirected;
- struct stat stat;
-};
-
struct auth_realm {
struct list_head list;
char *path;
const struct auth_realm *realm;
};
-struct http_response {
- int statuscode;
- char *statusmsg;
- char *headers[UH_LIMIT_HEADERS];
-};
-
enum client_state {
CLIENT_STATE_INIT,
CLIENT_STATE_HEADER,
CLIENT_STATE_CLOSE,
};
+struct interpreter {
+ struct list_head list;
+ char *path;
+ char *ext;
+};
+
+struct path_info {
+ const char *root;
+ const char *phys;
+ const char *name;
+ const char *info;
+ const char *query;
+ int redirected;
+ struct stat stat;
+ struct interpreter *ip;
+};
+
+struct env_var {
+ const char *name;
+ const char *value;
+};
+
+struct relay {
+ struct ustream_fd sfd;
+ struct uloop_process proc;
+ struct client *cl;
+
+ bool process_done;
+ int ret;
+ int header_ofs;
+
+ void (*header_cb)(struct relay *r, const char *name, const char *value);
+ void (*header_end)(struct relay *r);
+ void (*close)(struct relay *r, int ret);
+};
+
+struct dispatch_handler {
+ struct list_head list;
+
+ bool (*check_url)(const char *url);
+ bool (*check_path)(struct path_info *pi, const char *url);
+ void (*handle_request)(struct client *cl, const char *url, struct path_info *pi);
+};
+
struct client {
struct list_head list;
int id;
enum client_state state;
struct http_request request;
- struct http_response response;
struct sockaddr_in6 servaddr;
struct sockaddr_in6 peeraddr;
struct blob_attr **hdr;
int fd;
} file;
+ struct {
+ struct blob_buf hdr;
+ struct relay r;
+ int status_code;
+ char *status_msg;
+ } proc;
};
} dispatch;
};
extern struct config conf;
extern const char * const http_versions[];
extern const char * const http_methods[];
+extern struct dispatch_handler cgi_dispatch;
void uh_index_add(const char *filename);
void __printf(4, 5)
uh_client_error(struct client *cl, int code, const char *summary, const char *fmt, ...);
-void uh_handle_file_request(struct client *cl);
+void uh_handle_request(struct client *cl);
void uh_auth_add(const char *path, const char *user, const char *pass);
void uh_close_listen_fds(void);
void uh_close_fds(void);
+void uh_interpreter_add(const char *ext, const char *path);
+void uh_dispatch_add(struct dispatch_handler *d);
+
+void uh_relay_open(struct client *cl, struct relay *r, int fd, int pid);
+void uh_relay_close(struct relay *r, int ret);
+void uh_relay_free(struct relay *r);
+
+struct env_var *uh_get_process_vars(struct client *cl, struct path_info *pi);
+bool uh_create_process(struct client *cl, struct path_info *pi,
+ void (*cb)(struct client *cl, struct path_info *pi, int fd));
+
#endif