file i/o and plain old bad luck
Greetings I am a newbie, but a newbie with experience in other languages. This does not, however, usually detain me from being an idiot.
So, right now as a jumping off point in C I have decided I'd try to make a crappy webserver. Right now it serves files from static GET requests, and I want it to now write its pid in a file so that I might control it (it's a daemon, can't be good to keep closing the shell window to stop it).
Nonetheless, it is supposed to be a quick tutorial in file i/o. Here is my situation:
The server serves files just fine, but it does not do any file writing. Now that I say this, let me say it differently: on Ubuntu, it does not do any writing. My friend compiled it perfectly on MacOS with Xcode, but I am experiencing problems with file i/o on Ubuntu (but it does serve files). To further the insult, I tried running the program on my Slackware box, and the damndest thing -- it doesn't even serve files there.
Here is my code:
Code:
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
/* Arg format: 1=port */
#define BUFSIZE 8196
struct {
char *ext;
char *filetype;
} extensions [] = {
{"gif","image/gif"},
{"jpg","image/jpeg"},
{"jpeg","image/jpeg"},
{"png","image/png"},
{"zip","image/zip"},
{"gz","image/gz"},
{"tar","image/tar"},
{"htm","text/html"},
{"html","text/html"},
{"c","text/text"},
{0,0}
};
void writeFile(char *filename, char *toWrite)
{
FILE * thefile;
thefile = fopen(filename,"wb");
fwrite(toWrite,sizeof toWrite, 1, thefile);
(void)fclose(thefile);
perror("Error was ");
}
int processGet(int fd, char buffer[])
{
/* First we declare our local variable friends */
int file_fd, j, buflen, len; /* eventually we will open a file to serve. Then we will rule the earth */
long ret, i; /* this is a multipurpose tool for
determining if read() calls work */
char *fstr; /* later, when we're checking for legal
extensions, you'll thank me. Oh yea you will. */
/* I'm not quite sure what this does...I think it keeps us from reading extra crap we don't need */
for (i=4; i<BUFSIZE;i++)
{
if (buffer[i] == ' ')
{
buffer[i] = 0;
break;
}
}
/* Nice try, kid */
for (j=0;j<i-1;j++) if (buffer[j] == '.' && buffer[j+1] == '.') printf("no h4x0ring allowed");
/* let's default to index.html */
if (!strncmp(&buffer[0],"GET /\0",6) || !strncmp(&buffer[0],"get /\0",6) ) (void)strcpy(buffer,"GET /index.html");
/* verify that we serve this extension ... this reminds me, we
need a way of at least capturing the query string */
buflen = strlen(buffer);
fstr = (char *)0;
for (i=0;extensions[i].ext != 0;i++)
{
len = strlen(extensions[i].ext);
if (!strncmp(&buffer[buflen-len], extensions[i].ext, len))
{
fstr = extensions[i].filetype;
break;
}
}
if (fstr == 0) printf("not valid, .............."); /* we should be nicer */
/* see if we can't open that file */
if ((file_fd = open(&buffer[5],O_RDONLY)) == -1) printf("file couldn't open");
(void)sprintf(buffer,"HTTP/1.0 200 OK\r\nContent-Type: %s\r\n\r\n",fstr);
(void)write(fd,buffer,strlen(buffer));
while ((ret=read(file_fd,buffer,BUFSIZE))>0)
{
(void) write(fd,buffer,ret);
}
#ifdef LINUX
sleep(1);
#endif
close(fd);
return 1;
}
void *processRequest(void *arg)
{
static char buffer[BUFSIZE+1]; /* this is where we will buffer
the HTTP request */
/* because arg is void in order to work with the thread library I'm using */
int fd = (int*)arg;
long ret;
/* Now we read the HTTP request and see what it is */
ret = read(fd,buffer,BUFSIZE);
if (ret == 0 || ret == -1) printf("error, man. freakin' error."); /* <-- eventually we hope to implement a logging system,
where all instances of printf
will go bye bye */
if (ret > 0 && ret < BUFSIZE) buffer[ret] = 0; /* makes it easier to tell when you're at the end */
else buffer[0] = 0;
char *firsthalf = strtok(buffer,"\r\n");
char *lasthalf = strtok(NULL,"\r\n");
writeFile("log.txt",lasthalf);
if (!strncmp(buffer,"GET",3) || ! strncmp(buffer,"get",3))
{
if (processGet(fd,buffer)) printf("yes");
else printf("no");
}
return NULL;
}
int main (int argc, char ** argv)
{
/* Local variable time! */
int fd, port, connection_fd, socklen; /* stream file handle, port, connection handle, and the ... wtf is socklen? */
struct sockaddr_in sockaddr; /* this is our gateway to the
internet, man */
pthread_t pth;
/*let's become a daemon real quick */
int i;
for (i =0;i<32;i++) (void)close(i);
(void)setpgrp();
fd = socket(PF_INET, SOCK_STREAM,0);
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = INADDR_ANY;
port = atoi(argv[1]); /* this two part cliffhanger takes
the char argument, turns it into an int, and then puts it in
network byte order*/
sockaddr.sin_port = htons(port);
bind (fd, (struct sockaddr*)&sockaddr, sizeof(sockaddr)); /* bind the socket */
writeFile("donner.pid",(char *)getpid());
listen(fd, 1); /* begin listening .......... head */
while (1) /* go forever, should probably find a way to change
that */
{
connection_fd; /* this is the file descriptor for our
session stream */
socklen = sizeof(sockaddr); /* it's just necessary, okay? */
connection_fd = accept(fd, (struct sockaddr *)&sockaddr, &socklen); /* make the connection meaningful */
pthread_create(&pth,NULL,processRequest,connection_fd); /* create our thread to process the request */
pthread_join(pth,NULL); /* then kill it when it's done */
}
pthread_join(pth,NULL); /* it doesn't hurt */
return 0;
}
For compilation, this works:
Code:
gcc donner.c -o donner -lpthread
And to run it:
./donner portnumber &
Please tell me what I am doing wrong. I have put the file i/o stuff in a neat function at the top, writeFile(), to make things easier, but all the code is provided anyway for contextual purposes.
Anyone who can tell me what the issue is gets a quarter in the coin of spirit . Thank you!
Part 2: Things get weirder
So, my server has been repaired and works pretty well, save for one little problem: originally, if a browser requested http://whatever.com/folder with no trailing slash at the end, the page would load fine HTML wise, but all pictures referenced would be searched for in the top level directory. I decided, okay, easy enough, I'll detect when there isn't a trailing slash and then redirect them to the same URL but with the slash, in essence silently forcing them to http://whatever.com/folder/. This is my attempted code (note: prefix is the absolute URL of the request on the server computer, and reqfile is the relative one sent by the browser):
Code:
if (isDir(prefix))
{
len = strlen(prefix);
if (prefix[len-1] != 47) /* is last char "/" ? */
{
fprintf(stdout, "Trailing slash for '%s'. Request: %s\n", reqfile, requrl);
buffer = "";
char *header =
(void)sprintf(buffer,"HTTP/1.1 302 Found\r\nLocation: %s/\r\n", reqfile); /* TROUBLE AREA */
fprintf(stdout, "%s", buffer);
if ( write(fd,buffer,strlen(buffer)) < 0 ) fprintf(stdout, "Error sending header!\n");
#ifdef LINUX
sleep(1);
#endif
close(fd);
return 1;
}
requrl = strcat(prefix,"index.html");
}
While building this, I tested that isDir() does work, that the if logic does correctly observe trailing slashes or not, and that all but the line marked /* TROUBLE AREA */ work.
Well, now, the problem is quite a pickle because, with some debug output, I find this HTTP response is being sent back:
Code:
HTTP/1.1 302 Found\r\nLocation: /1.1 302 Found
Hmm...the HTTP response spits part of itself out where a valid directory should go. In the browser, I am confronted with the notice that I am being sent the malformed new URL of content-type null (point being, the debug output is accurate).
The debug output beforehand proves that reqfile DOES contain the correct information; nowhere in the program does that variable hold an HTTP response. The problem is literally with that one line, and I have no idea why.
Thank you for your consideration.