/* HTTP support for s10sh
* Copyright (C) 2001 Salvatore Sanfilippo <antirez@invece.org>
* This software is under the GPL license */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include <ctype.h>
#include "s10sh.h"
#include "favicon.h"
#define HTTP_DEFAULT_PORT 80
#define REQUEST_BUF_SZ 1024
#define HTTPD_READ_EOF -1
#define HTTPD_READ_ERROR -2
#define HOOK_NUL 0
#define HOOK_ANY 1
#define HOOK_EXACT 2
#define HOOK_EXACT_NOCASE 3
#define HB_INCR 1024 /* html buffer increment step */
#define HTTP_CT_HTML "text/html"
#define HTTP_CT_PLAIN "text/plain"
#define HTTP_CT_JPEG "image/jpeg"
/* global var and types */
static int httpd_s; /* listening socket for the service */
static int httpd_port = HTTP_DEFAULT_PORT; /* port */
struct httpd_hook {
int type;
char *match;
int(*handler)(struct httpd_hook *hook, int s, char *method, char *url);
};
struct html_buffer {
char *buffer;
size_t size;
size_t left;
};
static struct html_buffer HTMLBUF_INITIALIZER = {NULL, 0, 0};
/* prototypes */
static int httpd_handle_request(int s, struct sockaddr_in *addr);
static int httpd_getline(int s, char *dest, int sz);
static int httpd_parse_query(char *request, char **method, char **url);
struct httpd_hook *httpd_search_hook(char *method, char *url);
/* hooks prototypes */
static int hook_handler_favicon(struct httpd_hook *hook, int s, char *method, char *url);
static int hook_handler_showimage(struct httpd_hook *hook, int s, char *method, char *url);
static int hook_handler_dirlist(struct httpd_hook *hook, int s, char *method, char *url);
static int hook_handler_connect(struct httpd_hook *hook, int s, char *method, char *url);
static int hook_handler_main(struct httpd_hook *hook, int s, char *method, char *url);
static int hook_handler_about(struct httpd_hook *hook, int s, char *method, char *url);
static int hook_handler_err404(struct httpd_hook *hook, int s, char *method, char *url);
/* html generation */
static int html_add(struct html_buffer *hb, char *html);
static void html_free(struct html_buffer *hb);
static int html_header(struct html_buffer *hb, char *title);
static int html_finish(struct html_buffer *hb);
static int html_tag(struct html_buffer *hb, char *tag, char *text);
static int html_link(struct html_buffer *hb, char *href, char *text);
static int html_br(struct html_buffer *hb, int count);
static int html_print(struct html_buffer *hb, char *fmt, ...);
static int html_file_entry(struct html_buffer *hb, struct canonfile *f, char *path);
/* Hooks declaration */
static struct httpd_hook httpd_hooks[] = {
{HOOK_EXACT, "/favicon.ico", hook_handler_favicon},
{HOOK_ANY, "/showimage/", hook_handler_showimage},
{HOOK_ANY, "/showthumb/", hook_handler_showimage},
{HOOK_ANY, "/dirlist/", hook_handler_dirlist},
{HOOK_EXACT, "/connect/", hook_handler_connect},
{HOOK_EXACT, "/", hook_handler_main},
{HOOK_ANY, "index.htm", hook_handler_main},
{HOOK_ANY, "default.htm", hook_handler_main},
{HOOK_EXACT, "/about", hook_handler_about},
{HOOK_NUL, NULL, NULL}
};
static struct httpd_hook httpd_hook_404 = {
HOOK_ANY, "error 404", hook_handler_err404
};
/* Change the default port */
void httpd_set_port(int port)
{
httpd_port = port;
}
/* Put the port in listen state */
int httpd_init(void)
{
int on = 1;
struct sockaddr_in sa;
if ((httpd_s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket(2)");
return 1;
}
if (setsockopt(httpd_s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))==-1)
{
perror("warning: setsockopt(SO_REUSEADDR)");
return 1;
}
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = htonl(INADDR_ANY);
sa.sin_port = htons(httpd_port);
if (bind(httpd_s, (struct sockaddr*) &sa, sizeof(sa)) == -1) {
perror("bind(2)");
return 1;
}
if (listen(httpd_s, 0xF) == -1) {
perror("listen(2)");
return 1;
}
return 0;
}
/* The main loop, accept connections and call httpd_handle_requests() */
int httpd_main_loop(void)
{
int clilen; /* client socket len */
struct sockaddr_in ca; /* client address */
int s; /* accept(2) socket */
/* This is just an iterative server, the camera can't
* handle multiple requests anyway */
while(1) {
int nomore;
clilen = sizeof(ca);
s = accept(httpd_s, (struct sockaddr*) &ca, &clilen);
if (s == -1) {
switch(errno) {
case EINTR:
continue;
default:
perror("accept");
continue;
}
}
nomore = httpd_handle_request(s, &ca);
close(s);
if (nomore)
return 0;
}
return 0; /* no compiler warnings */
}
/* decode the % HEX HEX encoding */
static void httpd_decode(char *dest, char *src)
{
char *set = "0123456789ABCDEF";
while (*src) {
if (src[0] == '%' && src[1] && src[2]) {
char a = toupper(src[1]);
char b = toupper(src[2]);
char e;
printf("HERE: %c%c\n",a,b);
if (strchr(set, a) == NULL || strchr(set, b) == NULL)
goto out;
e = ((strchr(set, a) - set) * 16);
e += strchr(set, b) - set;
*dest++ = e;
src += 3;
continue;
}
out:
*dest++ = *src++;
}
*dest = '\0';
}
/* Handle the HTTP request */
static int httpd_handle_request(int s, struct sockaddr_in *addr)
{
char buffer[REQUEST_BUF_SZ];
char request[REQUEST_BUF_SZ];
char decoded_url[REQUEST_BUF_SZ];
char *method, *url;
int len;
int get_query = 1;
struct httpd_hook *hp;
while(1) {
if ((len = httpd_getline(s, buffer, REQUEST_BUF_SZ)) < 0)
return 0; /* protocol error */
if (get_query) {
memcpy(request, buffer, REQUEST_BUF_SZ);
get_query = 0;
}
if (!len)
break;
}
if (get_query) /* protocol error */
return 0;
/* Parse the query */
if (httpd_parse_query(request, &method, &url))
return 0; /* protocol error */
httpd_decode(decoded_url, url);
hp = httpd_search_hook(method, decoded_url);
hp->handler(hp, s, method, decoded_url);
return 0;
}
/* Search for a matching hook */
struct httpd_hook *httpd_search_hook(char *method, char *url)
{
int i;
for (i = 0; httpd_hooks[i].type; i++) {
switch(httpd_hooks[i].type) {
case HOOK_ANY:
if (strstr(url, httpd_hooks[i].match))
return &httpd_hooks[i];
break;
case HOOK_EXACT:
if (strcmp(url, httpd_hooks[i].match) == 0)
return &httpd_hooks[i];
break;
case HOOK_EXACT_NOCASE:
if (strcasecmp(url, httpd_hooks[i].match) == 0)
return &httpd_hooks[i];
break;
default:
fprintf(stderr, "WARNING: Unsupported hook type %d\n",
httpd_hooks[i].type);
break;
}
}
/* Not found, error 404 */
return &httpd_hook_404;
}
/* Set the method and url according to "request", parsing is hard to read... */
static int httpd_parse_query(char *request, char **method, char **url)
{
char *p = request;
while(*p && *p == ' ') p++; /* skip initial spaces */
*method = p; /* set the method */
while(*p && *p != ' ') p++; /* skip the method */
if (*p == '\0') return 1; /* error? */
*p = '\0'; p++; /* nul term the method */
while(*p && *p == ' ') p++; /* skip additional spaces */
*url = p; /* set the url */
while(*p && *p != ' ') p++; /* skip the url */
if (*p == '\0') return 1; /* error? */
*p = '\0'; p++; /* nul term the url */
return 0; /* done */
}
/* Get a single char, make it bufferized */
static int httpd_getchar(int s)
{
unsigned char byte;
switch(read(s, &byte, 1)) {
case 0:
return HTTPD_READ_EOF;
case -1:
return HTTPD_READ_ERROR;
}
return byte;
}
static int httpd_getline(int s, char *dest, int sz)
{
char *p = dest;
int c;
while(sz > 1) {
if ((c = httpd_getchar(s)) >= 0) {
switch(c) {
case '\n':
goto done;
case '\r':
(void) httpd_getchar(s); /* discard \n */
goto done;
default:
*p++ = c;
sz--;
break;
}
} else {
switch(c) {
case HTTPD_READ_EOF:
if (p - dest)
goto done;
else
return c;
default:
return c;
}
}
}
done:
*p = '\0'; /* nul term */
return p - dest;
}
/* -------------------- HTTP header generation ------------------------------ */
/* We can put the header in the HTML stream, then send the whole buffer
* to the client */
static int http_header(struct html_buffer *hb, char *content_type)
{
return html_print(hb,
"HTTP/1.0 200 OK\r\n"
"Date: Tue, 03 Jul 2001 22:06:28 GMT\r\n"
"Server: s10sh in web-mode (Unix)\r\n"
"ETag: \"e788-f6d-37d8def6\"\r\n"
"Accept-Ranges: bytes\r\n"
"Connection: Keep-Alive\r\n"
"Keep-Alive: timeout=15, max=1\r\n"
"Content-Type: %s\r\n"
"X-Pad: avoid browser bug\r\n"
"\r\n", content_type);
}
/* ---------------------- Help for the HTML generation ---------------------- */
/* Dynamic buffers handling */
static int html_add(struct html_buffer *hb, char *html)
{
int len = strlen(html)+1; /* include nul term */
if (len > hb->left) {
size_t new_size = hb->size + len + HB_INCR;
void *tmp = realloc(hb->buffer, new_size);
if (tmp == NULL)
return 1; /* out of memory */
hb->buffer = tmp;
hb->left += len + HB_INCR;
hb->size += len + HB_INCR;
}
memcpy(hb->buffer + (hb->size - hb->left), html, len);
hb->left -= (len-1); /* don't include the nul term */
return 0;
}
/* free an html buffer */
static void html_free(struct html_buffer *hb)
{
free(hb->buffer);
*hb = HTMLBUF_INITIALIZER;
}
static int html_header(struct html_buffer *hb, char *title)
{
return html_add(hb, "<HTML>\n<HEAD>\n<TITLE>") +
html_add(hb, title) +
html_add(hb,
"</TITLE>\n</HEAD>\n\n<HTML>\n"
"<BODY BGCOLOR=\"#FFFFFF\">\n");
}
static int html_finish(struct html_buffer *hb)
{
return html_add(hb, "</BODY>\n</HTML>\n");
}
static int html_tag(struct html_buffer *hb, char *tag, char *text)
{
return html_add(hb, "<") +
html_add(hb, tag) +
html_add(hb, ">") +
html_add(hb, text) +
html_add(hb, "<") +
html_add(hb, "/") +
html_add(hb, tag) +
html_add(hb, ">\n");
}
static int html_link(struct html_buffer *hb, char *href, char *text)
{
return html_add(hb, "<a href=\"") +
html_add(hb, href) +
html_add(hb, "\">") +
html_add(hb, text) +
html_add(hb, "</a>\n");
}
static int html_br(struct html_buffer *hb, int count)
{
int err = 0;
while(count--)
err += html_add(hb, "<BR>\n");
return err;
}
#define HTML_PRINT_BUFSZ 1024
static int html_print(struct html_buffer *hb, char *fmt, ...)
{
char buffer[HTML_PRINT_BUFSZ];
va_list ap;
va_start(ap, fmt);
vsnprintf(buffer, HTML_PRINT_BUFSZ, fmt, ap);
va_end(ap);
buffer[HTML_PRINT_BUFSZ-1] = '\0';
return html_add(hb, buffer);
}
static int html_file_entry(struct html_buffer *hb, struct canonfile *f, char *path)
{
#if 0
if (dirlist[j]->type & 0x10) {/* directory */
snprintf(buffer, 1024,
"<a href=\"/dirlist/%s\\%s\">%s</a><BR>",
path, dirlist[j]->name, dirlist[j]->name);
} else {
snprintf(buffer, 1024,
"<a href=\"/showimage/%s\\%s\">%s</a><BR>",
path, dirlist[j]->name, dirlist[j]->name);
}
html_add(&h, buffer);
#endif
int err = 0;
err += html_print(hb,
"<TABLE BORDER=\"1\" CELLSPACING=\"0\" WIDTH=\"600\" BGCOLOR=\"#FFFFFF\">\n"
"<TR>\n"
"<TD WIDTH=\"30%\" BGCOLOR=\"#%s\">\n"
"<FONT COLOR=\"#000000\">\n"
"<CENTER>\n"
"<a href=\"/%s/%s\\%s\">%s</a>\n"
"</CENTER>\n"
"</FONT>\n"
"</TD>\n"
"<TD WIDTH=\"20%\" BGCOLOR=\"#CCCCEE\">\n"
"<FONT COLOR=\"#000000\">\n"
"<CENTER>\n"
"%dk\n"
"</CENTER>\n"
"</FONT>\n"
"</TD>\n"
"<TD WIDTH=\"50%\" BGCOLOR=\"#FFFFAA\">\n"
"<FONT COLOR=\"#000000\">\n"
"<CENTER>\n"
"%s\n"
"</CENTER>\n"
"</FONT>\n"
"</TD>\n",
f->type & 0x10 ? "CCEECC" : "DDDDDD",
f->type & 0x10 ? "dirlist" : "showimage",
path, f->name, f->name, f->size/1024, ctime(&f->date));
if (!(f->type & 0x10) &&
(strstr(f->name, ".JPG") || strstr(f->name, ".jpg")))
{
err += html_print(hb,
"<TD><a href=\"/showimage/%s\\%s\">\n"
"<IMG SRC=\"/showthumb/%s\\%s\">\n"
"</a></TD>\n",
path, f->name,
path, f->name);
}
err += html_add(hb, "</TR>\n</TABLE>\n");
return err;
}
static int safe_write(int s, char *data, size_t left)
{
int n_written;
while(left) {
n_written = write(s, data, left);
switch(n_written) {
case 0:
case -1:
return 1; /* error or eof from client */
default:
left -= n_written;
data += n_written;
break;
}
}
return 0;
}
/* Our canon digicam can't handle multiple requests at time, so
* this server isn't concurrent. However with a slow internet connection
* the time spent to send the data to the client is much more long
* than the USB access, so we try to get concurrent at least in this
* stage */
static void safe_write_fork(int s, char *data, size_t left)
{
int childpid;
if ((childpid = fork()) == -1) {
perror("fork");
} else {
/* the parent return, the child send the data and exit */
if (childpid != 0)
return;
}
/* note that you reach this even if fork(2) fails */
safe_write(s, data, left);
if (childpid != -1)
exit(1);
return;
}
/* write an html buffer to a file descriptor */
static int send_html(int s, struct html_buffer *hb)
{
size_t len = strlen(hb->buffer);
safe_write_fork(s, hb->buffer, len);
return 0;
}
static int send_data(int s, char *data, size_t len)
{
safe_write_fork(s, data, len);
return 0;
}
/* ----------------------------- s10sh Hooks -------------------------------- */
static int hook_handler_lost_connection(struct httpd_hook *hook, int s, char *method, char *url)
{
struct html_buffer h = HTMLBUF_INITIALIZER;
http_header(&h, HTTP_CT_HTML);
html_header(&h, "Connection lost!\n");
html_tag(&h, "H1", "The connection was lost (timeout?)");
html_link(&h, "/connect/", "click here to reconnect");
html_finish(&h);
send_html(s, &h);
html_free(&h);
return 0;
}
static int hook_handler_connect(struct httpd_hook *hook, int s, char *method, char *url)
{
camera_close();
camera_startup_initialization();
return hook_handler_main(hook, s, method, url);
}
static int hook_handler_main(struct httpd_hook *hook, int s, char *method, char *url)
{
struct html_buffer h = HTMLBUF_INITIALIZER;
char *id = camera_get_id();
char drive[] = "A:";
if (id == NULL)
return hook_handler_lost_connection(hook, s, method, url);
http_header(&h, HTTP_CT_HTML);
html_header(&h, id);
html_tag(&h, "H1", id);
html_add(&h, "Firmware version: ");
html_add(&h, firmware);
html_br(&h, 1);
html_tag(&h, "H1", "Compact Flash detection");
while (drive[0] != 'H') {
int size, free;
char buffer[1024];
if (camera_get_disk_info(drive, &size, &free) == 0) {
snprintf(buffer, 1024,
"<a href=\"/dirlist/%s\">%s</a> (%dK), %dk free<BR>",
drive, drive, size/1024, free/1024);
html_add(&h, buffer);
}
drive[0]++;
}
html_br(&h, 1);
html_finish(&h);
send_html(s, &h);
html_free(&h);
return 0;
}
static int hook_handler_favicon(struct httpd_hook *hook, int s, char *method, char *url)
{
struct html_buffer h = HTMLBUF_INITIALIZER;
http_header(&h, HTTP_CT_PLAIN);
send_html(s, &h);
html_free(&h);
send_data(s, favicon, sizeof(favicon));
return 0;
}
static int hook_handler_showimage(struct httpd_hook *hook, int s, char *method, char *url)
{
struct html_buffer h = HTMLBUF_INITIALIZER;
char *id = camera_get_id();
char *path, *image;
int thumb;
int len;
if (id == NULL)
return hook_handler_lost_connection(hook, s, method, url);
if ((path = strstr(url, hook->match)) == NULL)
return hook_handler_err404(hook, s, method, url);
path += strlen(hook->match);
thumb = (strcmp(hook->match, "/showthumb/") == 0) ? 1 : 0;
if (thumb)
image = camera_get_thumb(path, &len);
else
image = camera_get_file(path, &len, 0);
if (image == NULL)
return hook_handler_err404(hook, s, method, url);
http_header(&h, HTTP_CT_JPEG);
send_html(s, &h);
html_free(&h);
send_data(s, image, len);
free(image);
return 0;
}
static int hook_handler_dirlist(struct httpd_hook *hook, int s, char *method, char *url)
{
struct html_buffer h = HTMLBUF_INITIALIZER;
char *id = camera_get_id();
char *path;
char buffer[1024], upper[1024], *tmp;
int j;
if (id == NULL)
return hook_handler_lost_connection(hook, s, method, url);
if ((path = strstr(url, hook->match)) == NULL)
return hook_handler_err404(hook, s, method, url);
path += strlen(hook->match);
if (camera_get_list(path) == -1)
return hook_handler_err404(hook, s, method, url);
http_header(&h, HTTP_CT_HTML);
html_header(&h, id);
html_tag(&h, "H1", path);
strncpy(upper, path, 1024);
upper[1023] = '\0';
if ((tmp = strrchr(upper, '\\')) != NULL) {
*tmp = '\0';
snprintf(buffer, 1024,
"<a href=\"/dirlist/%s\"><b>UP</b></a><BR>",
upper);
html_add(&h, buffer);
} else {
html_add(&h, "<a href=\"/\"><b>UP</b></a><BR>");
}
html_br(&h, 1);
for (j = 0; j < dirlist_size; j++) {
html_file_entry(&h, dirlist[j], path);
}
html_finish(&h);
send_html(s, &h);
html_free(&h);
return 0;
}
static int hook_handler_about(struct httpd_hook *hook, int s, char *method, char *url)
{
printf("about\n");
return 0;
}
static int hook_handler_err404(struct httpd_hook *hook, int s, char *method, char *url)
{
struct html_buffer h = HTMLBUF_INITIALIZER;
http_header(&h, HTTP_CT_HTML);
html_header(&h, "404 Not Found");
html_tag(&h, "H1", "Not Found");
html_add(&h, "No httpd hook associated with the request: '");
html_add(&h, url);
html_add(&h, "'<P>\n");
html_add(&h,
"This may be the result of an internal bug. "
"If unsure send an email to "
"<a href=\"mailto:antirez@invece.org\">antirez@invece.org"
"</a>\n");
html_finish(&h);
send_html(s, &h);
html_free(&h);
return 0;
}
/* -------------------------------------------------------------------------- */
int httpd_start(int port)
{
httpd_set_port(port);
httpd_init();
httpd_main_loop();
return 0;
}