/**
* @file minihttpd.c
* @author Mathieu Bunel
* @brief Minimal HTTP 0.9 deamon.
* @version 0.3
* @date 2008
*
* Minimal http server.
* @todo Protect against bad formatted request like GET //bin/sh or GET /../foo.
* @todo Make robust.
* @todo directory handling (if /foo/ is request, send /foo/index.html)
*
* 0.3
* Create a thread on each new connection
*
*
* 0.2
* Able to provide files.
* Answers index.html on 'GET /' request.
* Answers error page if request page is unknown.
*
* 0.1
* Accepts one connection, disconnect after answer.
* GET request without arg, just answer an hello world page.
*
*
*/
/**
* 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, either version 2 of the License, or
* any later version.
*
* 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, see .
*
*/
#include
#include
#include
#include
#include
#ifdef WIN32
#include
#include
typedef void thread_return;
#else
#include
#include
#include
#include
#include
#include
#include
typedef void *thread_return;
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define closesocket(s) close(s)
typedef int SOCKET;
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;
typedef struct in_addr IN_ADDR;
#endif
#ifndef socklen_t
#define socklen_t int
#endif
#ifdef _DEBUG
#define TRACE(a) printf(a)
#define TRACEd(a) printf("%d",a)
#else
#define TRACE(a) /**/
#define TRACEd(a) /**/
#endif
#define PORT 7766
#define DEFAULT_PORT 8080
#define VERSION 0.3
#define SND_BUF_SIZE 128
#define QUERY_BUF_SIZE 1024
#define ERROR_404 "HTTP/0.9 200 OK\r\n\
Content-Type: text/html\r\n\r\n\
\
minihttpd\
\
minihttpd 0.3
\
Error file not found.
\
\
\r\n\r\n"
void send_file(FILE * pFile, SOCKET sock)
{
TRACE("send_file\n");
char buffer[SND_BUF_SIZE];
size_t cSize, cRead, cSend;
if (NULL == pFile) {
return;
}
/* get file size */
fseek(pFile, 0, SEEK_SET);
fseek(pFile, 0, SEEK_END);
cSize = ftell(pFile);
#ifdef _DEBUG
printf("size: %uko\n", cSize / 1024);
#endif
/* go back to file start */
fseek(pFile, 0, SEEK_SET);
while (0 != cSize) {
/* read portion of file */
cRead =
fread(buffer, SND_BUF_SIZE < cSize ? SND_BUF_SIZE : cSize, 1,
pFile);
if (cRead < 1) {
perror("fread()");
break;
}
/* send file */
cSend =
send(sock, buffer, SND_BUF_SIZE < cSize ? SND_BUF_SIZE : cSize,
0);
if (cSend < 1) {
perror("send()");
break;
}
if (cSize > SND_BUF_SIZE) {
cSize -= SND_BUF_SIZE;
} else {
cSize = 0;
break;
}
}
}
void parse_request(char *pRequest, size_t size, SOCKET sock)
{
TRACE("parse_request(");
TRACE(pRequest);
TRACE(", ");
TRACEd(size);
TRACE(")\n");
char reqPage[SND_BUF_SIZE] = { 0 };
size_t i = 0;
FILE *pFile;
if ((NULL == pRequest) || (size < 1)) {
return;
}
if (0 == strncmp("GET /", pRequest, 5)) {
/* Can't use strtok, pRequest might not have '\0' delimiter */
for (i = 5; i < size; ++i) {
if (' ' == pRequest[i]) {
break;
}
}
}
if (i > 5) {
strncpy(reqPage, pRequest + 5, i - 5);
} else {
strcpy(reqPage, "index.html");
printf("Default page requested... sending index.html\n");
}
pFile = fopen(reqPage, "rb");
if (NULL != pFile) {
#ifdef _DEBUG
printf("Page \"%s\" requested\n", reqPage);
#endif
send_file(pFile, sock);
fclose(pFile);
} else {
send(sock, ERROR_404, strlen(ERROR_404), 0);
printf("Page \"%s\" not found\n", reqPage);
}
}
/**
*/
thread_return wait_query(void *pcsock)
{
/* Waiting query */
int cReceived;
char buffer[QUERY_BUF_SIZE];
if (pcsock) {
SOCKET csock = *(SOCKET *) pcsock;
cReceived = recv(csock, buffer, QUERY_BUF_SIZE - 1, 0);
if (cReceived < 0) {
perror("recv()");
} else {
parse_request(buffer, cReceived, csock);
}
/* closing connection to client */
closesocket(csock);
}
#ifndef WIN32
return NULL;
#endif
}
/**
* init function.
* today, on ly used to init Win32 sockets
*/
void init(void)
{
#ifdef WIN32
/* init socket windows */
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
#endif
}
/**
* init function.
* today, on ly used to desinit Win32 sockets
*/
void desinit(void)
{
#ifdef WIN32
/* desinit socket windows */
WSACleanup();
printf("system desinited\n");
#endif
}
/**
* program entry point
* usage: minihttpd [tcp_port]
*/
int main(int argc, char *argv[])
{
/* init */
init();
printf("init [OK]\n");
/* desinit preparation */
atexit(desinit);
/* listening socket creation */
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == sock) {
perror("socket()");
exit(errno);
}
printf("socket creation [OK]\n");
SOCKADDR_IN sin /*= { 0 }*/ ;
sin.sin_addr.s_addr = htonl(INADDR_ANY);
sin.sin_family = AF_INET;
if (1 == argc) {
sin.sin_port = htons(DEFAULT_PORT);
} else {
sin.sin_port = htons(strtol(argv[1], NULL, 10));
}
if (SOCKET_ERROR == bind(sock, (SOCKADDR *) & sin, sizeof(sin))) {
perror("bind()");
exit(errno);
}
printf("configuration [OK]\n");
while (1) {
/* Listening */
if (SOCKET_ERROR == listen(sock, 1)) {
perror("listen()");
exit(errno);
}
/* Acknowledgment */
SOCKADDR_IN csin;
SOCKET csock;
socklen_t sinsize = sizeof csin;
csock = accept(sock, (SOCKADDR *) & csin, &sinsize);
if (INVALID_SOCKET == csock) {
perror("accept()");
exit(errno);
}
#ifdef WIAIT_QUERY_IN_A_THREAD
#ifdef WIN32
_beginthread(wait_query, 0, &csock);
#else
pthread_t thread;
pthread_create(&thread, NULL, wait_query, &csock);
#endif
#else
wait_query(&csock);
#endif
}
/* closing listen socket */
closesocket(sock);
printf("socket closed\n");
return EXIT_SUCCESS;
}