mirror of
https://github.com/NishiOwO/ircservices5.git
synced 2025-04-21 16:54:38 +00:00
425 lines
12 KiB
C
425 lines
12 KiB
C
/* HTTP server common utility routines.
|
|
*
|
|
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
|
* E-mail: <achurch@achurch.org>
|
|
* Parts written by Andrew Kempe and others.
|
|
* This program is free but copyrighted software; see the file GPL.txt for
|
|
* details.
|
|
*/
|
|
|
|
#include "services.h"
|
|
#include "modules.h"
|
|
#include "http.h"
|
|
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
|
|
/* List of response texts for each response code, and inline functions to
|
|
* retrieve them.
|
|
*/
|
|
|
|
static struct {
|
|
int code;
|
|
const char *text;
|
|
const char *desc;
|
|
} http_response_text[] = {
|
|
|
|
{ 100, "Continue" },
|
|
{ 101, "Switching Protocols" },
|
|
|
|
{ 200, "OK" },
|
|
{ 201, "Created" },
|
|
{ 202, "Accepted" },
|
|
{ 203, "Non-Authoritative Information" },
|
|
{ 204, "No Content" },
|
|
{ 205, "Reset Content" },
|
|
{ 206, "Partial Content" },
|
|
|
|
{ 300, "Multiple Choices" },
|
|
{ 301, "Moved Permanently" },
|
|
{ 302, "Found" },
|
|
{ 303, "See Other" },
|
|
{ 304, "Not Modified" },
|
|
{ 305, "Use Proxy" },
|
|
{ 307, "Temporary Redirect" },
|
|
|
|
{ 400, "Bad Request",
|
|
"Your browser sent a request this server could not understand." },
|
|
{ 401, "Unauthorized",
|
|
"You are not authorized to access this resource." },
|
|
{ 402, "Payment Required" },
|
|
{ 403, "Forbidden",
|
|
"You are not permitted to access this resource." },
|
|
{ 404, "Not Found",
|
|
"The requested resource could not be found." },
|
|
{ 405, "Method Not Allowed",
|
|
"Your browser sent an invalid method for this resource." },
|
|
{ 406, "Not Acceptable" },
|
|
{ 407, "Proxy Authentication Required" },
|
|
{ 408, "Request Timeout" },
|
|
{ 409, "Conflict" },
|
|
{ 410, "Gone" },
|
|
{ 411, "Length Required",
|
|
"Your browser sent an invalid request to the server." },
|
|
{ 412, "Precondition Failed" },
|
|
{ 413, "Request Entity Too Large" },
|
|
{ 414, "Request-URI Too Large" },
|
|
{ 415, "Unsupported Media Type" },
|
|
{ 416, "Requested Range Not Satisfiable" },
|
|
{ 417, "Expectation Failed" },
|
|
|
|
{ 500, "Internal Server Error",
|
|
"An internal server error has occurred." },
|
|
{ 501, "Not Implemented",
|
|
"The requested method is not implemented by this server." },
|
|
{ 502, "Bad Gateway" },
|
|
{ 503, "Service Unavailable"
|
|
"Your request cannot currently be processed. Please try again"
|
|
" later." },
|
|
{ 504, "Gateway Timeout" },
|
|
{ 505, "HTTP Version Not Supported" },
|
|
|
|
{ -1 }
|
|
};
|
|
|
|
static inline const char *http_lookup_response(int code)
|
|
{
|
|
int i;
|
|
for (i = 0; http_response_text[i].code > 0; i++) {
|
|
if (http_response_text[i].code == code)
|
|
return http_response_text[i].text;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static inline const char *http_lookup_description(int code)
|
|
{
|
|
int i;
|
|
for (i = 0; http_response_text[i].code > 0; i++) {
|
|
if (http_response_text[i].code == code)
|
|
return http_response_text[i].desc;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
|
|
/* Return the value of the named header from the client request. If the
|
|
* request did not include the named header, return NULL. If `header' is
|
|
* NULL, return the next header with the same name as the previous call
|
|
* which included a non-NULL `header' or NULL if none, a la strtok().
|
|
*/
|
|
|
|
char *http_get_header(Client *c, const char *header)
|
|
{
|
|
int i;
|
|
static const char *last_header = NULL;
|
|
static int last_return;
|
|
|
|
if (!c) {
|
|
module_log("BUG: http_get_header(): client is NULL!");
|
|
return NULL;
|
|
}
|
|
if (!header) {
|
|
if (!last_header)
|
|
return NULL;
|
|
header = last_header;
|
|
i = (last_return>=c->headers_count) ? c->headers_count : last_return+1;
|
|
} else {
|
|
i = 0;
|
|
}
|
|
last_header = header;
|
|
while (i < c->headers_count) {
|
|
if (stricmp(c->headers[i], header) == 0) {
|
|
last_return = i;
|
|
return c->headers[i] + strlen(c->headers[i]) + 1;
|
|
}
|
|
i++;
|
|
}
|
|
last_return = i;
|
|
return NULL;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Return the value of the named variable from the client request. If the
|
|
* request did not include the named variable, return NULL. If `variable'
|
|
* is NULL, return the next variable with the same name as the previous
|
|
* call which included a non-NULL `variable' or NULL if none, a la strtok().
|
|
* Variable names are assumed to be case-insensitive.
|
|
*/
|
|
|
|
char *http_get_variable(Client *c, const char *variable)
|
|
{
|
|
int i;
|
|
static const char *last_variable = NULL;
|
|
static int last_return;
|
|
|
|
if (!c) {
|
|
module_log("BUG: http_get_variable(): client is NULL!");
|
|
return NULL;
|
|
}
|
|
if (!variable) {
|
|
if (!last_variable)
|
|
return NULL;
|
|
variable = last_variable;
|
|
i = (last_return>=c->variables_count) ? c->variables_count
|
|
: last_return+1;
|
|
} else {
|
|
i = 0;
|
|
}
|
|
last_variable = variable;
|
|
while (i < c->variables_count) {
|
|
if (stricmp(c->variables[i], variable) == 0) {
|
|
last_return = i;
|
|
return c->variables[i] + strlen(c->variables[i]) + 1;
|
|
}
|
|
i++;
|
|
}
|
|
last_return = i;
|
|
return NULL;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* HTML-quote (&...;) any HTML-special characters (<,>,&) in `str', and
|
|
* place the result in `outbuf', truncating to `outsize' bytes (including
|
|
* trailing null). &...; entities inserted by this routine will never be
|
|
* truncated. Returns `outbuf' on success, NULL on error (invalid
|
|
* parameter).
|
|
*/
|
|
|
|
char *http_quote_html(const char *str, char *outbuf, int32 outsize)
|
|
{
|
|
char *retval = outbuf;
|
|
|
|
if (!str || !outbuf || outsize <= 0) {
|
|
if (outsize <= 0)
|
|
module_log("BUG: http_quote_html(): bad outsize (%d)!", outsize);
|
|
else
|
|
module_log("BUG: http_quote_html(): %s is NULL!",
|
|
!str ? "str" : "outbuf");
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
while (*str && outsize > 1) {
|
|
switch (*str) {
|
|
case '&':
|
|
if (outsize < 6) {
|
|
outsize = 0;
|
|
break;
|
|
}
|
|
memcpy(outbuf, "&", 5);
|
|
outbuf += 5;
|
|
outsize -= 5;
|
|
break;
|
|
case '<':
|
|
if (outsize < 5) {
|
|
outsize = 0;
|
|
break;
|
|
}
|
|
memcpy(outbuf, "<", 5);
|
|
outbuf += 4;
|
|
outsize -= 4;
|
|
break;
|
|
case '>':
|
|
if (outsize < 5) {
|
|
outsize = 0;
|
|
break;
|
|
}
|
|
memcpy(outbuf, ">", 5);
|
|
outbuf += 4;
|
|
outsize -= 4;
|
|
break;
|
|
default:
|
|
*outbuf++ = *str;
|
|
outsize--;
|
|
break;
|
|
}
|
|
str++;
|
|
}
|
|
*outbuf = 0;
|
|
return retval;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* URL-quote (%nn) any special characters (anything except A-Z a-z 0-9 - . _)
|
|
* and place the result in `outbuf', truncating to `outsize' bytes (including
|
|
* trailing null). %nn tokens inserted by this routine will never be
|
|
* truncated. Returns `outbuf' on success, NULL on error (invalid parameter).
|
|
*/
|
|
|
|
char *http_quote_url(const char *str, char *outbuf, int32 outsize)
|
|
{
|
|
char *retval = outbuf;
|
|
|
|
if (!str || !outbuf || outsize <= 0) {
|
|
if (outsize <= 0)
|
|
module_log("BUG: http_quote_url(): bad outsize (%d)!", outsize);
|
|
else
|
|
module_log("BUG: http_quote_url(): %s is NULL!",
|
|
!str ? "str" : "outbuf");
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
while (*str && outsize > 1) {
|
|
if ((*str < 'A' || *str > 'Z')
|
|
&& (*str < 'a' || *str > 'z')
|
|
&& (*str < '0' || *str > '9')
|
|
&& *str != '-'
|
|
&& *str != '.'
|
|
&& *str != '_'
|
|
) {
|
|
if (*str == ' ') {
|
|
*outbuf++ = '+';
|
|
outsize--;
|
|
} else {
|
|
if (outsize < 4) {
|
|
outsize = 0;
|
|
break;
|
|
}
|
|
sprintf(outbuf, "%%%.02X", (unsigned char)*str);
|
|
outbuf += 3;
|
|
outsize -= 3;
|
|
}
|
|
} else {
|
|
*outbuf++ = *str;
|
|
outsize--;
|
|
}
|
|
str++;
|
|
}
|
|
*outbuf = 0;
|
|
return retval;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Remove URL-quoting (%nn) from the string in the given buffer, and return
|
|
* the buffer, or NULL on failure (invalid parameter). If the string ends
|
|
* with an incomplete %nn token, it is discarded; invalid %nn tokens (i.e.
|
|
* "nn" is not a pair of hex digits) are also discarded. Overwrites the
|
|
* buffer.
|
|
*/
|
|
|
|
char *http_unquote_url(char *buf)
|
|
{
|
|
char *retval = buf, *out = buf, *s;
|
|
char hexbuf[3] = {0,0,0};
|
|
|
|
if (!buf) {
|
|
module_log("BUG: http_unquote_url(): buf is NULL!");
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
while (*buf) {
|
|
if (*buf == '%') {
|
|
if (!buf[1] || !buf[2])
|
|
break;
|
|
hexbuf[0] = buf[1];
|
|
hexbuf[1] = buf[2];
|
|
buf += 3;
|
|
*out = strtol(hexbuf, &s, 16);
|
|
if (!*s) /* i.e. both digits were valid */
|
|
out++;
|
|
/* else discard character */
|
|
} else if (*buf++ == '+') {
|
|
*out++ = ' ';
|
|
} else {
|
|
*out++ = buf[-1];
|
|
}
|
|
}
|
|
*out = 0;
|
|
return retval;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Send an HTTP response line with the given code, plus the Date header. */
|
|
|
|
void http_send_response(Client *c, int code)
|
|
{
|
|
const char *text;
|
|
time_t t;
|
|
char datebuf[64];
|
|
|
|
if (!c) {
|
|
module_log("BUG: http_send_response(): client is NULL!");
|
|
return;
|
|
} else if (code < 0 || code > 999) {
|
|
module_log("BUG: http_send_response(): code is invalid! (%d)", code);
|
|
return;
|
|
}
|
|
text = http_lookup_response(code);
|
|
if (text)
|
|
sockprintf(c->socket, "HTTP/1.1 %03d %s\r\n", code, text);
|
|
else
|
|
sockprintf(c->socket, "HTTP/1.1 %03d Code %03d\r\n", code, code);
|
|
time(&t);
|
|
if (strftime(datebuf, sizeof(datebuf), "%a, %d %b %Y %H:%M:%S GMT",
|
|
gmtime(&t)) > 0)
|
|
sockprintf(c->socket, "Date: %s\r\n", datebuf);
|
|
else
|
|
module_log("http_send_response(): strftime() failed");
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Send an error message with the given code and body text, and close the
|
|
* client connection. If `format' is NULL, the response text for the given
|
|
* code ("Error NNN" if the code is unknown) and descriptive text, if any,
|
|
* are used as the body text. Note that the client data structure will be
|
|
* unusable after calling this routine.
|
|
*/
|
|
|
|
void http_error(Client *c, int code, const char *format, ...)
|
|
{
|
|
if (!c) {
|
|
module_log("BUG: http_error(): client is NULL!");
|
|
return;
|
|
} else if (code < 0 || code > 999) {
|
|
module_log("BUG: http_error(): code is invalid! (%d)", code);
|
|
http_error(c, HTTP_F_INTERNAL_SERVER_ERROR, NULL);
|
|
return;
|
|
}
|
|
http_send_response(c, code);
|
|
sockprintf(c->socket,
|
|
"Content-Type: text/html\r\nConnection: close\r\n\r\n");
|
|
if (c->method != METHOD_HEAD) {
|
|
if (format) {
|
|
va_list args;
|
|
va_start(args, format);
|
|
vsockprintf(c->socket, format, args);
|
|
va_end(args);
|
|
} else {
|
|
const char *text, *desc;
|
|
text = http_lookup_response(code);
|
|
if (text) {
|
|
desc = http_lookup_description(code);
|
|
sockprintf(c->socket, "<h1 align=center>%s</h1>", text);
|
|
if (desc)
|
|
sockprintf(c->socket, "%s", desc);
|
|
} else {
|
|
sockprintf(c->socket, "<h1 align=center>Error %d</h1>", code);
|
|
}
|
|
}
|
|
}
|
|
if (c->in_request)
|
|
c->in_request = -1; /* signal handle_request() to close later */
|
|
else
|
|
disconn(c->socket);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-file-style: "stroustrup"
|
|
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*
|
|
* vim: expandtab shiftwidth=4:
|
|
*/
|