Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

601 lines (476 sloc) 13.393 kb
#ifndef com_sleepless_net_http_httpdaemon_cpp
#define com_sleepless_net_http_httpdaemon_cpp
/* Copyright 1998-2004 Sleepless Software Inc. All Rights Reserved */
/*
This is a simple, basic, single-threaded HTTP server.
It's intended to be used as a base class which you extend
in order to gain the ability to serve data using HTTP.
I often use this to add a second comm port to a custom
server that talks HTTP, so I can interact with the custom
server using a browser.
*/
/*
HTTP Request Headers
---------------------
Netscape 6.0 Linux
GET / HTTP/1.1
Host: localhost:8888
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:0.9.2) Gecko/20010726 Netscape6/6.1
Accept: text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, image/png, image/jpeg, image/gif;q=0.2, text/plain;q=0.8, text/css, asterisk/asterisk;q=0.1
Accept-Language: en-us
Accept-Encoding: gzip,deflate,compress,identity
Accept-Charset: ISO-8859-1, utf-8;q=0.66, *;q=0.66
Keep-Alive: 300
Connection: keep-alive
IE 5.1 Mac
GET / HTTP/1.1
Host: hitchens-pbdsl4.stanford.edu:8888
Accept: asterisk/asterisk
Accept-Language: en
Connection: Keep-Alive
User-Agent: Mozilla/4.0 (compatible; MSIE 5.12; Mac_PowerPC)
UA-OS: MacOS
UA-CPU: PPC
Extension: Security/Remote-Passphrase
Lynx Linux
GET / HTTP/1.0
Host: hitchens-pbdsl4.stanford.edu:8888
Accept: text/html, text/plain, audio/mod, image/asterisk, video/asterisk, video/mpeg, application/pgp, application/pgp, application/pdf, message/partial, message/external-body, application/postscript, x-be2, application/andrew-inset, text/richtext, text/enriched
Accept: x-sun-attachment, audio-file, postscript-file, default, mail-file, sun-deskset-message, application/x-metamail-patch, application/msword, text/sgml, video/mpeg, image/jpeg, image/tiff, image/x-rgb, image/png, image/x-xbitmap, image/x-xbm
Accept: image/gif, application/postscript, asterisk/asterisk;q=0.01
Accept-Encoding: gzip, compress
Accept-Language: en
User-Agent: Lynx/2.8.4dev.16 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/0.9.6
Netscape 4.72 Mac
GET / HTTP/1.0
Connection: Keep-Alive
User-Agent: Mozilla/4.72 (Macintosh; U; PPC)
Host: hitchens-pbdsl4.stanford.edu:8888
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, asterisk/asterisk
Accept-Encoding: gzip
Accept-Language: en
Accept-Charset: iso-8859-1,*,utf-8
HTTP Response Headers
-----------------------
Me!?
HTTP/1.0 200 OK
Server: HTTPDaemon 1.0
Cache-Control: no-cache
Expires: Wed, 23 Aug 2000 20:25:05 GMT
Content-Location: http://204.202.128.139/index.html
Set-Cookie: SWID=D00A460A-7931-11D4-8206-00A0C9F48700; path=/; expires=Wed, 23-Aug-2020 20:25:05 GMT; domain=.go.com;
Date: Wed, 23 Aug 2000 20:25:05 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Wed, 23 Aug 2000 20:21:02 GMT
Content-Length: 27045
www.yahoo.com
HTTP/1.0 200 OK
Date: Fri, 25 Jan 2002 05:48:00 GMT
Connection: close
Content-Type: text/html
www.sleepless.com
HTTP/1.1 200 OK
Date: Fri, 25 Jan 2002 05:45:36 GMT
Server: Apache
Last-Modified: Wed, 23 Jan 2002 08:01:12 GMT
ETag: "194048-7cc-3c4e6dc8"
Accept-Ranges: bytes
Content-Length: 1996
Connection: close
Content-Type: text/html
www.microsoft.com
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
P3P: CP='ALL IND DSP COR ADM CONo CUR CUSo IVAo IVDo PSA PSD TAI TELo OUR SAMo CNT COM INT NAV ONL PHY PRE PUR UNI'
Content-Location: http://cpmsftwbw06/default.htm
Date: Fri, 25 Jan 2002 05:45:06 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Thu, 24 Jan 2002 17:55:26 GMT
ETag: "281fcb4d0a5c11:866"
Content-Length: 26999
File uploading
---------------------
This is what IE 6.0 sends for a file upload POST
POST /testie HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, asterisk/asterisk
Accept-Language: en-us
Content-Type: multipart/form-data; boundary=---------------------------7d2db3a190134
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)
Host: home.sleepless.com:3333
Content-Length: 417
Connection: Keep-Alive
Cache-Control: no-cache'
-----------------------------7d2db3a190134
Content-Disposition: form-data; name="bloo"; filename="D:\Sleepless Software\foo\test.html"
Content-Type: text/html
<html>
<body>
<form
method=post
enctype="multipart/form-data"
action="http://home.sleepless.com:3333/testie"
>
<input name=bloo type="file">
<input type="submit" value="upload">
</form>
-----------------------------7d2db3a190134--
This is what Netscape 6.2 sends for a file upload POST
POST /test HTTP/1.1
Host: localhost:3333
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:0.9.4.1) Gecko/20020314 Netscape6/6.2.2
Accept: text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, image/png, image/jpeg, image/gif;q=0.2, text/plain;q=0.8, text/css, asterisk/asterisk;q=0.1
Accept-Language: en-us
Accept-Encoding: gzip, deflate, compress;q=0.9
Accept-Charset: ISO-8859-1, utf-8;q=0.66, *;q=0.66
Keep-Alive: 300
Connection: keep-alive
Content-type: multipart/form-data; boundary=---------------------------278722862861021530336465782
Content-Length: 458
-----------------------------278722862861021530336465782
Content-Disposition: form-data; name="blah"; filename="ditech.txt"
Content-Type: text/plain
30 yr
fixed rate
$126,600
6.75
$821 / mo
$3095 closing costs ($180 discount for including tax/ins.) + 1.5 ($1899) = $4994
30 yr
fixed
$132,000
6.75
$856 / mo
0 out of pocket
Aaron Rasmussen
Fax
(714) 800 7171
Voice
800 713 4933 x6171
-----------------------------278722862861021530336465782--
*/
#include "log.cpp"
#include "system.cpp"
#include "serversocket.cpp"
#include "urlencode.cpp"
#include "str.cpp"
#include "file.cpp"
struct HTTPDaemon
{
int port;
int backlog;
bool running;
char *title;
Socket *sock;
HTTPDaemon(int p, int bl)
{
TRACE("HTTPDaemon %d, %d\n", p, bl);
port = p;
backlog = bl;
sock = NULL;
running = false;
title = "httpd";
}
virtual ~HTTPDaemon()
{
}
bool loop()
{
DTLOG("%s started\n", title);
ServerSocket ss(port, backlog);
if(!ss.isListening())
{
DTLOG("#### Can't create ServerSocket on port %d\n", port);
return 1;
}
DTLOG("%s listening on port %d\n", title, port);
running = true;
while(running)
{
sock = ss.accept();
if(sock == NULL)
{
DTLOG("#### Bad accept() %d\n");
return 1;
}
transact();
delete sock;
sock = NULL;
}
return 0;
}
// -----------------------------------------------
char line[4096];
char method[10];
char path[4096];
int contentLength;
char contentType[4096];
char boundary[4096];
char query[4096];
#define MAX_QUERY_ARGS 50
int queryArgs;
char *queryTags[MAX_QUERY_ARGS];
char *queryVals[MAX_QUERY_ARGS];
virtual void transact()
{
TRACE("connection from %s\n", sock->remoteIP);
int r;
// Read the first line
r = sock->readLine(line, sizeof(line));
if(r < 1)
return;
TRACE("first line: %s", line);
r = sscanf(line, "%8s %4095[^ ] %*[^\n]\n", method, path);
if(r != 2)
{
DTLOG("ALERT: bogus first line: %s\n", line);
return;
}
TRACE("method = '%s'\n", method);
TRACE("path = '%s'\n", path);
/* Parse args from query str & store in queryTags[] and queryVals[] */
queryArgs = 0;
char *q = strchr(path, '?');
if(q)
{
// We have a query string! Oh joy!
*q = 0; // terminate path str
q++; // skip the '?' char
if(*q != 0)
{
Str::copy(query, q, sizeof(query));
TRACE("query string: '%s'\n", query);
// Break it up on '&' chars.
while(queryArgs < MAX_QUERY_ARGS)
{
queryTags[queryArgs++] = q;
q = strchr(q, '&');
if(!q)
break;
*q = 0;
q++;
}
// Break tag/val pairs (in queryTags now) up on '=' chars.
int i;
char *t;
char *v;
for(i = 0; i < queryArgs; i++)
{
t = queryTags[i];
v = strchr(t, '=');
if(v)
{
*v++ = 0; // terminate tag
queryVals[i] = v; // note val
}
else
{
queryVals[i] = t + strlen(t); // make val an empty str
}
}
// Now urldecode the tags and vals
for(i = 0; i < queryArgs; i++)
{
URLEncode::decode(queryTags[i], queryTags[i]);
URLEncode::decode(queryVals[i], queryVals[i]);
TRACE("param[%d]: '%s'='%s'\n", i, queryTags[i], queryVals[i]);
}
}
}
char *name = getArg("name");
// Decode path string
URLEncode::decode(path, path);
// Refuse any path that tries to venture above "."
if(strstr(path, "../"))
{
DTLOG("ALERT: mischievous path: %s\n", path);
respond404();
return;
}
// Remove leading '/' if present.
while(Str::startsWith(path, "/"))
{
STR_CPY(path, path+1);
}
// Read the headers
contentLength = 0;
contentType[0] = 0;
boundary[0] = 0;
while(true)
{
r = sock->readLine(line, sizeof(line));
if(r < 1)
return;
if(line[0] == '\n')
break; // end of headers
TRACE("header: %s", line);
// watch for and grab content length/type
sscanf(line, "%*[cC]ontent-%*[lL]ength: %d\n", &contentLength);
sscanf(line, "%*[cC]ontent-%*[tT]ype: %[^;\n]\n", contentType);
sscanf(line, "%*[cC]ontent-%*[tT]ype: %*[^;]; boundary=%[^;\n]\n", boundary);
}
TRACE("contentLength = %d\n", contentLength);
TRACE("contentType = %s\n", contentType);
TRACE("boundary = %s\n", boundary);
respond();
TRACE("_______ transaction complete _______\n");
}
/* Fetch the value for a query item given it's tag.
The tag comparison is case-insensitive.
Returns null if the tag isn't found. */
virtual char *getArg(const char *tag)
{
char *val = 0;
for(int i = 0; i < queryArgs; i++)
{
TRACE("tag[%d] = %s\n", i, queryTags[i]);
TRACE("val[%d] = %s\n", i, queryVals[i]);
if(strcasecmp(queryTags[i], tag) == 0)
return queryVals[i];
}
return val;
}
/* Returns a "document not found" response */
virtual void respond404()
{
sock->writeStr("HTTP/1.0 404\nContent-Type: text/html\n\n");
sock->writeStr("<html><body>Document not found.</body>\n");
}
/* This is the MAIN response method and should normally be overridden
by subclasses to do something useful. */
virtual void respond()
{
respondFile();
}
/* Looks for a file at 'path' and returns it as the response if found */
virtual void respondFile()
{
/* if path is empty str, default to index.html */
if(path[0] == 0)
STR_CPY(path, "index.html");
File ff(path);
/* Don't allow the following of hard/soft links ... they could
lead outside the CWD tree */
if(ff.isLink())
{
DTLOG("error: can't read '%s'\n", path);
respond404();
return;
}
/* if path is a dir, add a slash */
if(ff.isDir())
STR_APP(path, "/");
/* if path ends with a slash, default to index.html in that dir */
if(path[strlen(path) - 1] == '/')
STR_APP(path, "index.html");
FILE *fp = fopen(path, "rb");
if(!fp)
{
DTLOG("error: can't read '%s'\n", path);
respond404();
return;
}
if(fseek(fp, 0, SEEK_END) == -1)
{
DTLOG("error: can't fseek for size of '%s'\n", path);
fclose(fp);
respond404();
return;
}
int len = ftell(fp);
if(len == -1)
{
DTLOG("error: can't ftell for size of '%s'\n", path);
fclose(fp);
respond404();
return;
}
if(fseek(fp, 0, SEEK_SET) == -1)
{
fclose(fp);
respond404();
return;
}
// Try to deduce the content-type from the file's extension
const char *mimeType = getMimeType(path);
if(!mimeType)
{
DTLOG("error: no mime-type for '%s'\n", path);
fclose(fp);
respond404();
return;
}
// All looks well. Serve up the file.
sock->writeStr("HTTP/1.0 200 OK\n");
sock->writeStr("Content-Type: ");
sock->writeStr(mimeType);
sock->writeStr("\n");
sock->writeStr("Content-Length: ");
sock->writeInt(len);
sock->writeStr("\n");
sock->writeStr("\n");
char *buf = new char[10000];
if(!buf)
return;
int left = len;
int n;
int r;
int w;
while(left > 0)
{
n = sizeof(buf);
if(left < n)
n = left;
r = fread(buf, 1, n, fp);
if(r == n)
{
w = sock->write(buf, r);
if(w == r)
{
left -= w;
continue;
}
}
DTLOG("error: read/write error while sending '%s'\n", path);
break;
}
delete buf;
fclose(fp);
}
virtual const char *getMimeType(const char *p)
{
if(Str::endsWith(p, ".html"))
return "text/html";
if(Str::endsWith(p, ".txt"))
return "text/plain";
if(Str::endsWith(p, ".jpg"))
return "image/jpeg";
if(Str::endsWith(p, ".bmp"))
return "image/bmp";
if(Str::endsWith(p, ".jpeg"))
return "image/jpeg";
if(Str::endsWith(p, ".gif"))
return "image/gif";
if(Str::endsWith(p, ".js"))
return "application/x-javascript";
return 0;
}
};
#ifdef TEST_HTTPD
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
if(argc < 2)
{
printf("Usage: httpd port\n");
return 1;
}
TRACEON();
HTTPDaemon httpd(atol(argv[1]), 2);
return httpd.loop();
}
#endif
#endif // com_sleepless_net_http_httpdaemon_cpp
Jump to Line
Something went wrong with that request. Please try again.