245 lines
6.2 KiB
C
245 lines
6.2 KiB
C
/***************************************************************************
|
|
* Copyright (C) 07/2007 by Olaf Rempel *
|
|
* razzor@kopf-tisch.de *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; version 2 of the License *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program; if not, write to the *
|
|
* Free Software Foundation, Inc., *
|
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
***************************************************************************/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
#include "event.h"
|
|
#include "httpd.h"
|
|
#include "list.h"
|
|
#include "logging.h"
|
|
#include "sockaddr.h"
|
|
#include "tcpsocket.h"
|
|
|
|
static LIST_HEAD(httpd_cb_list);
|
|
|
|
static void destroy_httpd_con(struct httpd_con *con)
|
|
{
|
|
close(event_get_fd(con->event));
|
|
event_remove_fd(con->event);
|
|
|
|
if (con->req_data != NULL)
|
|
free(con->req_data);
|
|
|
|
if (con->req_headers != NULL)
|
|
free(con->req_headers);
|
|
|
|
if (con->req_args != NULL)
|
|
free(con->req_args);
|
|
|
|
free(con);
|
|
}
|
|
|
|
int httpd_send_header(struct httpd_con *con, char *code, char *type)
|
|
{
|
|
char buf[256];
|
|
int len = snprintf(buf, sizeof(buf), "HTTP/1.0 %s\r\nContent-Type: %s\r\nConnection: close\r\n\r\n", code, type);
|
|
write(con->fd, buf, len);
|
|
return 0;
|
|
}
|
|
|
|
int httpd_send_error(struct httpd_con *con, char *code, char *msg)
|
|
{
|
|
httpd_send_header(con, code, "text/plain");
|
|
write(con->fd, msg, strlen(msg));
|
|
return 0;
|
|
}
|
|
|
|
static int parse_headers(struct httpd_con *con)
|
|
{
|
|
char *data = con->req_data;
|
|
while ((data = strstr(data, "\r\n")) && data < con->req_data + con->req_size) {
|
|
con->req_header_cnt++;
|
|
data += 2;
|
|
}
|
|
|
|
con->req_headers = malloc(con->req_header_cnt * sizeof(char *));
|
|
if (con->req_headers == NULL)
|
|
return -1;
|
|
|
|
int i = 0;
|
|
data = con->req_data;
|
|
|
|
do {
|
|
con->req_headers[i++] = data;
|
|
|
|
data = strstr(data, "\r\n");
|
|
if (data == NULL)
|
|
break;
|
|
|
|
*data++ = '\0';
|
|
*data++ = '\0';
|
|
|
|
} while (data < con->req_data + con->req_size);
|
|
return 0;
|
|
}
|
|
|
|
static int parse_args(struct httpd_con *con)
|
|
{
|
|
/* seperate GET (in req->data) */
|
|
char *req = strchr(con->req_data, ' ');
|
|
*req++ = '\0';
|
|
|
|
/* HTTP/1.1 will go to header[0] */
|
|
char *tmp = strchr(req, ' ');
|
|
*tmp++ = '\0';
|
|
con->req_headers[0] = tmp;
|
|
|
|
/* count args */
|
|
tmp = req;
|
|
while (*tmp != '\0' && tmp < con->req_data + con->req_size) {
|
|
if (*tmp == '?' || *tmp == '&')
|
|
con->req_arg_cnt++;
|
|
|
|
tmp++;
|
|
}
|
|
con->req_arg_cnt++;
|
|
|
|
/* alloc args */
|
|
con->req_args = malloc(con->req_arg_cnt * sizeof(char *));
|
|
if (con->req_args == NULL)
|
|
return -1;
|
|
|
|
int i = 0;
|
|
tmp = req;
|
|
|
|
con->req_args[i++] = tmp;
|
|
while (*tmp != '\0' && tmp < con->req_data + con->req_size) {
|
|
if (*tmp == '?' || *tmp == '&') {
|
|
*tmp++ = '\0';
|
|
con->req_args[i++] = tmp;
|
|
}
|
|
tmp++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int httpd_content_handler(int fd, void *privdata)
|
|
{
|
|
struct httpd_con *con = (struct httpd_con *)privdata;
|
|
|
|
if (ioctl(fd, FIONREAD, &con->req_size) == -1) {
|
|
log_print(LOG_WARN, "http_content_handler(): ioctl(FIONREAD)");
|
|
destroy_httpd_con(con);
|
|
return -1;
|
|
}
|
|
|
|
con->req_data = malloc(con->req_size);
|
|
if (con->req_data == NULL) {
|
|
log_print(LOG_WARN, "http_content_handler(): out of memory");
|
|
destroy_httpd_con(con);
|
|
return -1;
|
|
}
|
|
|
|
con->req_size = read(fd, con->req_data, con->req_size);
|
|
if (con->req_size <= 0) {
|
|
log_print(LOG_WARN, "http_content_handler(): read()");
|
|
destroy_httpd_con(con);
|
|
return -1;
|
|
}
|
|
|
|
if (strncmp(con->req_data, "GET ", 4) != 0) {
|
|
httpd_send_error(con, "400 Invalid Request", "Not a GET request\r\n");
|
|
destroy_httpd_con(con);
|
|
return -1;
|
|
}
|
|
|
|
if (parse_headers(con) < 0) {
|
|
httpd_send_error(con, "400 Invalid Request", "Error parsing headers\r\n");
|
|
destroy_httpd_con(con);
|
|
return -1;
|
|
}
|
|
|
|
if (parse_args(con) < 0) {
|
|
httpd_send_error(con, "400 Invalid Request", "Error parsing arguments\r\n");
|
|
destroy_httpd_con(con);
|
|
return -1;
|
|
}
|
|
|
|
struct httpd_callback *entry;
|
|
list_for_each_entry(entry, &httpd_cb_list, list) {
|
|
|
|
if ((entry->wildcard == 1 && strncmp(entry->name, con->req_args[0], strlen(entry->name)) == 0) ||
|
|
(entry->wildcard == 0 && strcmp(entry->name, con->req_args[0]) == 0)) {
|
|
|
|
entry->callback(con, entry->privdata);
|
|
destroy_httpd_con(con);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
httpd_send_error(con, "404 Not Found", "File not found\r\n");
|
|
destroy_httpd_con(con);
|
|
return 0;
|
|
}
|
|
|
|
int httpd_accept_handler(int fd, void *privdata)
|
|
{
|
|
struct httpd_con *con = malloc(sizeof(struct httpd_con));
|
|
if (con == NULL) {
|
|
log_print(LOG_WARN, "httpd_accept_handler(): out of memory");
|
|
return 0;
|
|
}
|
|
|
|
memset(con, 0, sizeof(struct httpd_con));
|
|
|
|
unsigned int i = sizeof(con->addr);
|
|
con->fd = accept(fd, (struct sockaddr *)&con->addr, &i);
|
|
if (con->fd < 0) {
|
|
free(con);
|
|
return 0;
|
|
}
|
|
|
|
con->event = event_add_readfd(NULL, con->fd, httpd_content_handler, con);
|
|
return 0;
|
|
}
|
|
|
|
struct httpd_callback * httpd_add_cb(const char *name,
|
|
int wildcard,
|
|
int (* callback)(struct httpd_con *con, void *privdata),
|
|
void *privdata)
|
|
{
|
|
struct httpd_callback *hcb = malloc(sizeof(struct httpd_callback));
|
|
if (hcb == NULL) {
|
|
log_print(LOG_WARN, "httpd_create_cb(): out of memory");
|
|
return NULL;
|
|
}
|
|
|
|
hcb->name = strdup(name);
|
|
hcb->wildcard = wildcard;
|
|
hcb->callback = callback;
|
|
hcb->privdata = privdata;
|
|
|
|
list_add_tail(&hcb->list, &httpd_cb_list);
|
|
return hcb;
|
|
}
|
|
|
|
int httpd_remove_cb(struct httpd_callback *hcb)
|
|
{
|
|
list_del(&hcb->list);
|
|
free(hcb->name);
|
|
free(hcb);
|
|
return 0;
|
|
}
|