2012-02-13 20:54:27 -07:00
|
|
|
/*
|
|
|
|
* simple httpd to be started from tcpserver
|
|
|
|
*/
|
2011-08-16 14:36:11 -06:00
|
|
|
#define _FILE_OFFSET_BITS 64
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <time.h>
|
2012-02-13 20:54:27 -07:00
|
|
|
#include <stdio.h>
|
2011-08-16 14:36:11 -06:00
|
|
|
#include <string.h>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <grp.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <sys/poll.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <netinet/tcp.h>
|
|
|
|
#include <dirent.h>
|
|
|
|
#include <sys/mman.h>
|
|
|
|
#include <limits.h>
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
/*
|
|
|
|
* Some things I use for debugging
|
|
|
|
*/
|
2012-02-13 22:49:10 -07:00
|
|
|
#define XXNODUMP
|
|
|
|
|
|
|
|
#ifndef NODUMP
|
2012-02-13 20:54:27 -07:00
|
|
|
#define DUMPf(fmt, args...) fprintf(stderr, "%s:%s:%d " fmt "\n", __FILE__, __FUNCTION__, __LINE__, ##args)
|
2011-10-12 17:21:32 -06:00
|
|
|
#else
|
2012-02-13 20:54:27 -07:00
|
|
|
#define DUMPf(fmt, args...)
|
2011-10-12 17:21:32 -06:00
|
|
|
#endif
|
|
|
|
#define DUMP() DUMPf("")
|
|
|
|
#define DUMP_d(v) DUMPf("%s = %d", #v, v)
|
|
|
|
#define DUMP_x(v) DUMPf("%s = 0x%x", #v, v)
|
|
|
|
#define DUMP_s(v) DUMPf("%s = %s", #v, v)
|
|
|
|
#define DUMP_c(v) DUMPf("%s = %c", #v, v)
|
|
|
|
#define DUMP_p(v) DUMPf("%s = %p", #v, v)
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
/*
|
|
|
|
* the following is the time in seconds that fnord should wait for a valid
|
|
|
|
* HTTP request
|
|
|
|
*/
|
2011-08-16 14:36:11 -06:00
|
|
|
#define READTIMEOUT 20
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
/*
|
|
|
|
* the following is the time in seconds that fnord should wait before
|
|
|
|
* aborting a request when trying to write the answer
|
|
|
|
*/
|
2011-08-16 14:36:11 -06:00
|
|
|
#define WRITETIMEOUT 20
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
#define CGI_TIMEOUT (5*60) /* 5 minutes time-out for CGI to complete */
|
2011-08-16 14:36:11 -06:00
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
/*
|
|
|
|
* defining USE_SENDFILE enables zero-copy TCP on Linux for static files.
|
|
|
|
* I measured over 320 meg per second with apache bench over localhost
|
|
|
|
* with sendfile and keep-alive. However, sendfile does not work with
|
|
|
|
* large files and may be considered cheating ;-) Also, sendfile is a
|
|
|
|
* blocking operation. Thus, no timeout handling.
|
|
|
|
*/
|
2011-08-16 14:36:11 -06:00
|
|
|
#define USE_SENDFILE
|
|
|
|
|
|
|
|
#ifndef __linux__
|
|
|
|
#undef USE_SENDFILE
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef USE_SENDFILE
|
|
|
|
#include <sys/sendfile.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef O_NDELAY
|
|
|
|
#define O_NDELAY O_NONBLOCK
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define USE_MMAP
|
|
|
|
#ifndef _POSIX_MAPPED_FILES
|
|
|
|
#undef USE_MMAP
|
|
|
|
#endif
|
|
|
|
|
|
|
|
enum { UNKNOWN, GET, HEAD, POST } method;
|
|
|
|
|
|
|
|
#ifdef TCP_CORK
|
2012-02-13 20:54:27 -07:00
|
|
|
static int corked;
|
2011-08-16 14:36:11 -06:00
|
|
|
#endif
|
2012-02-13 20:54:27 -07:00
|
|
|
static long retcode = 404; /* used for logging code */
|
|
|
|
char *host = "?"; /* Host: header */
|
|
|
|
char *port; /* also Host: header, :80 part */
|
|
|
|
char *args; /* URL behind ? (for CGIs) */
|
2012-02-13 22:49:10 -07:00
|
|
|
char *url; /* string between GET and HTTP/1.0, *
|
2012-02-13 20:54:27 -07:00
|
|
|
* demangled */
|
|
|
|
char *ua = "?"; /* user-agent */
|
|
|
|
char *refer; /* Referrer: header */
|
|
|
|
char *accept_enc; /* Accept-Encoding */
|
|
|
|
int httpversion; /* 0 == 1.0, 1 == 1.1 */
|
|
|
|
int keepalive = 0; /* should we keep the connection alive? */
|
|
|
|
int rootdir; /* fd of root directory, so we can fchdir
|
2012-02-13 22:49:10 -07:00
|
|
|
* * back for keep-alive */
|
2012-02-13 20:54:27 -07:00
|
|
|
char *cookie; /* Referrer: header */
|
|
|
|
char *uri; /* copy of url before demangling */
|
|
|
|
char *content_type;
|
|
|
|
char *content_len;
|
|
|
|
char *auth_type;
|
|
|
|
char *post_miss;
|
|
|
|
unsigned long post_mlen;
|
|
|
|
unsigned long post_len = 0;
|
2011-08-16 14:36:11 -06:00
|
|
|
|
|
|
|
#if _FILE_OFFSET_BITS == 64
|
2012-02-13 20:54:27 -07:00
|
|
|
static unsigned long long rangestart,
|
|
|
|
rangeend; /* for ranged queries */
|
2012-02-13 22:49:10 -07:00
|
|
|
#define strtorange strtoull
|
2011-08-16 14:36:11 -06:00
|
|
|
#else
|
2012-02-13 20:54:27 -07:00
|
|
|
static unsigned long rangestart,
|
|
|
|
rangeend; /* for ranged queries */
|
2012-02-13 22:49:10 -07:00
|
|
|
#define strtorange strtoul
|
2011-08-16 14:36:11 -06:00
|
|
|
#endif
|
|
|
|
|
|
|
|
static const char days[] = "SunMonTueWedThuFriSat";
|
|
|
|
static const char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
|
|
|
|
|
|
|
|
#define MAXHEADERLEN 8192
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
char *remote_ip;
|
|
|
|
char *remote_port;
|
|
|
|
char *remote_ident;
|
2011-08-16 14:36:11 -06:00
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
static void
|
|
|
|
sanitize(char *ua)
|
2012-02-13 22:49:10 -07:00
|
|
|
{ /* replace strings with underscores for *
|
2012-02-13 20:54:27 -07:00
|
|
|
* logging */
|
|
|
|
int j;
|
2012-02-15 16:19:02 -07:00
|
|
|
if (!ua)
|
|
|
|
return;
|
2012-02-13 20:54:27 -07:00
|
|
|
for (j = 0; ua[j]; ++j)
|
|
|
|
if (isspace(ua[j]))
|
|
|
|
ua[j] = '_';
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
static void
|
|
|
|
dolog(off_t len)
|
|
|
|
{ /* write a log line to stderr */
|
|
|
|
sanitize(host);
|
|
|
|
sanitize(ua);
|
2012-02-13 22:49:10 -07:00
|
|
|
sanitize(refer);
|
2012-02-13 20:54:27 -07:00
|
|
|
|
2012-02-14 17:23:32 -07:00
|
|
|
fprintf(stderr, "%s %ld %lu %s %s %s %s\n",
|
2012-02-13 20:54:27 -07:00
|
|
|
remote_ip ? remote_ip : "0.0.0.0",
|
2012-02-15 16:19:02 -07:00
|
|
|
retcode, (unsigned long) len, host, ua, refer, url);
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
/*
|
|
|
|
* output an error message and exit
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
badrequest(long code, const char *httpcomment, const char *message)
|
|
|
|
{
|
|
|
|
retcode = code;
|
|
|
|
dolog(0);
|
2012-02-14 17:23:32 -07:00
|
|
|
printf("HTTP/1.0 %ld %s\r\nConnection: close\r\n", code, httpcomment);
|
2012-02-13 22:49:10 -07:00
|
|
|
if (message && message[0]) {
|
2012-02-14 17:23:32 -07:00
|
|
|
printf("Content-Length: %lu\r\nContent-Type: text/html\r\n\r\n",
|
|
|
|
(unsigned long) strlen(message));
|
2012-02-13 22:49:10 -07:00
|
|
|
fputs(message, stdout);
|
2012-02-13 20:54:27 -07:00
|
|
|
} else {
|
2012-02-14 17:23:32 -07:00
|
|
|
fputs("\r\n", stdout);
|
2012-02-13 20:54:27 -07:00
|
|
|
}
|
2012-02-13 22:49:10 -07:00
|
|
|
fflush(stdout);
|
2012-02-13 20:54:27 -07:00
|
|
|
exit(0);
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
#define CGIENVLEN 21
|
|
|
|
|
|
|
|
static const char *cgivars[CGIENVLEN] = {
|
2012-02-13 20:54:27 -07:00
|
|
|
"GATEWAY_INTERFACE=",
|
|
|
|
"SERVER_PROTOCOL=",
|
|
|
|
"SERVER_SOFTWARE=",
|
|
|
|
"SERVER_NAME=",
|
|
|
|
"SERVER_PORT=",
|
|
|
|
"REQUEST_METHOD=",
|
|
|
|
"REQUEST_URI=",
|
|
|
|
"SCRIPT_NAME=",
|
|
|
|
"REMOTE_ADDR=",
|
|
|
|
"REMOTE_PORT=",
|
|
|
|
"REMOTE_IDENT=",
|
|
|
|
"HTTP_USER_AGENT=",
|
|
|
|
"HTTP_COOKIE=",
|
|
|
|
"HTTP_REFERER=",
|
|
|
|
"HTTP_ACCEPT_ENCODING=",
|
|
|
|
"AUTH_TYPE=",
|
|
|
|
"CONTENT_TYPE=",
|
|
|
|
"CONTENT_LENGTH=",
|
|
|
|
"QUERY_STRING=",
|
|
|
|
"PATH_INFO=",
|
|
|
|
"PATH_TRANSLATED="
|
2011-08-16 14:36:11 -06:00
|
|
|
};
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
static int
|
|
|
|
iscgivar(const char *s)
|
|
|
|
{
|
2012-02-15 16:19:02 -07:00
|
|
|
int sl = strlen(s);
|
2012-02-13 20:54:27 -07:00
|
|
|
register unsigned int i = 0;
|
2012-02-14 17:23:32 -07:00
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
for (; i < CGIENVLEN; i++)
|
2012-02-15 16:19:02 -07:00
|
|
|
if (!strncmp(s, cgivars[i], sl))
|
2012-02-13 20:54:27 -07:00
|
|
|
return 1;
|
|
|
|
return 0;
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
static unsigned int
|
|
|
|
elen(register const char *const *e)
|
|
|
|
{
|
|
|
|
register unsigned int i = 0;
|
|
|
|
while (e[i])
|
|
|
|
i++;
|
|
|
|
return i;
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
|
|
|
|
2012-02-15 16:19:02 -07:00
|
|
|
char *
|
2012-02-14 17:23:32 -07:00
|
|
|
env_append(const char *key, const char *val)
|
|
|
|
{
|
2012-02-15 16:19:02 -07:00
|
|
|
static char buf[MAXHEADERLEN * 2 + PATH_MAX + 200];
|
|
|
|
static char *p = buf;
|
|
|
|
char *ret = p;
|
2012-02-14 17:23:32 -07:00
|
|
|
|
2012-02-15 16:19:02 -07:00
|
|
|
if (!key) {
|
2012-02-14 17:23:32 -07:00
|
|
|
p = buf;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
p = stpcpy(p, key);
|
|
|
|
*(p++) = '=';
|
|
|
|
if (val) {
|
|
|
|
p = stpcpy(p, val) + 1;
|
|
|
|
} else {
|
|
|
|
*(p++) = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
static void
|
|
|
|
do_cgi(const char *pathinfo, const char *const *envp)
|
|
|
|
{
|
|
|
|
const char *method_name[] = { "?", "GET", "HEAD", "POST" };
|
|
|
|
register unsigned int en = elen(envp);
|
|
|
|
char **cgi_arg;
|
|
|
|
register int i;
|
|
|
|
char **cgi_env =
|
|
|
|
(char **) alloca((CGIENVLEN + en + 1) * sizeof(char *));
|
|
|
|
|
|
|
|
cgi_env[0] = "GATEWAY_INTERFACE=CGI/1.1";
|
|
|
|
cgi_env[1] = "SERVER_PROTOCOL=HTTP/1.0";
|
|
|
|
cgi_env[2] = "SERVER_SOFTWARE=" FNORD;
|
|
|
|
|
2012-02-14 17:23:32 -07:00
|
|
|
i = 3;
|
|
|
|
env_append(NULL, NULL);
|
|
|
|
cgi_env[i++] = env_append("SERVER_NAME", host);
|
|
|
|
cgi_env[i++] = env_append("SERVER_PORT", port);
|
|
|
|
cgi_env[i++] = env_append("REQUEST_METHOD", method_name[method]);
|
|
|
|
cgi_env[i++] = env_append("REQUEST_URI", uri);
|
|
|
|
cgi_env[i++] = env_append("SCRIPT_NAME", url);
|
2012-02-15 16:19:02 -07:00
|
|
|
if (remote_ip)
|
|
|
|
cgi_env[i++] = env_append("REMOTE_ADDR", remote_ip);
|
|
|
|
if (remote_port)
|
|
|
|
cgi_env[i++] = env_append("REMOTE_PORT", remote_port);
|
|
|
|
if (remote_ident)
|
|
|
|
cgi_env[i++] = env_append("REMOTE_IDENT", remote_ident);
|
|
|
|
if (ua)
|
|
|
|
cgi_env[i++] = env_append("HTTP_USER_AGENT", ua);
|
|
|
|
if (cookie)
|
|
|
|
cgi_env[i++] = env_append("HTTP_COOKIE", cookie);
|
|
|
|
if (refer)
|
|
|
|
cgi_env[i++] = env_append("HTTP_REFERER", refer);
|
|
|
|
if (accept_enc)
|
|
|
|
cgi_env[i++] = env_append("HTTP_ACCEPT_ENCODING", accept_enc);
|
|
|
|
if (auth_type)
|
|
|
|
cgi_env[i++] = env_append("AUTH_TYPE", auth_type);
|
|
|
|
if (content_type)
|
|
|
|
cgi_env[i++] = env_append("CONTENT_TYPE", content_type);
|
|
|
|
if (content_type)
|
|
|
|
cgi_env[i++] = env_append("CONTENT_LENGTH", content_len);
|
|
|
|
if (args)
|
|
|
|
cgi_env[i++] = env_append("QUERY_STRING", args);
|
2012-02-13 20:54:27 -07:00
|
|
|
if (pathinfo) {
|
2012-02-15 16:19:02 -07:00
|
|
|
char *rp = realpath(pathinfo, NULL);
|
2012-02-14 17:23:32 -07:00
|
|
|
|
|
|
|
cgi_env[i++] = env_append("PATH_INFO", pathinfo);
|
2012-02-15 16:19:02 -07:00
|
|
|
cgi_env[i++] = env_append("PATH_TRANSLATED", rp ? rp : pathinfo);
|
|
|
|
if (rp)
|
|
|
|
free(rp);
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
2012-02-13 20:54:27 -07:00
|
|
|
|
|
|
|
{
|
|
|
|
unsigned int j = 0;
|
|
|
|
for (; j < en; j++)
|
|
|
|
if (!iscgivar(envp[j]))
|
|
|
|
cgi_env[++i] = (char *) envp[j];
|
|
|
|
}
|
|
|
|
cgi_env[++i] = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* argv
|
|
|
|
*/
|
2012-02-13 22:49:10 -07:00
|
|
|
if (args && (strchr(args, '=') == 0)) {
|
2012-02-13 20:54:27 -07:00
|
|
|
int n = 3;
|
|
|
|
for (i = 0; args[i]; ++i)
|
|
|
|
if (args[i] == '+')
|
|
|
|
++n;
|
|
|
|
cgi_arg = alloca(n * sizeof(char *));
|
|
|
|
cgi_arg[n = 1] = args;
|
|
|
|
for (i = 0; args[i]; ++i) {
|
|
|
|
if (args[i] == '+') {
|
|
|
|
args[i] = 0;
|
|
|
|
++i;
|
|
|
|
cgi_arg[++n] = args + i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cgi_arg[++n] = 0;
|
|
|
|
} else {
|
|
|
|
cgi_arg = alloca(2 * sizeof(char *));
|
|
|
|
cgi_arg[1] = 0;
|
|
|
|
}
|
|
|
|
|
2012-02-14 17:23:32 -07:00
|
|
|
{
|
2012-02-15 16:19:02 -07:00
|
|
|
char tmp[PATH_MAX];
|
2012-02-13 20:54:27 -07:00
|
|
|
|
2012-02-14 17:23:32 -07:00
|
|
|
i = strrchr(url, '/') - url;
|
|
|
|
strncpy(tmp, url + 1, i);
|
|
|
|
tmp[i] = 0;
|
|
|
|
chdir(tmp);
|
|
|
|
}
|
2012-02-13 20:54:27 -07:00
|
|
|
|
2012-02-15 16:19:02 -07:00
|
|
|
{
|
|
|
|
char tmp[PATH_MAX];
|
2012-02-14 17:23:32 -07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* program name
|
|
|
|
*/
|
|
|
|
cgi_arg[0] = tmp;
|
|
|
|
tmp[0] = '.';
|
|
|
|
strcpy(tmp + 1, url + i);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* start cgi
|
|
|
|
*/
|
|
|
|
execve(cgi_arg[0], cgi_arg, cgi_env);
|
2012-02-15 16:19:02 -07:00
|
|
|
raise(SIGQUIT); /* gateway unavailable. */
|
2012-02-14 17:23:32 -07:00
|
|
|
}
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
static void
|
|
|
|
cgi_child(int sig)
|
|
|
|
{
|
|
|
|
int n,
|
|
|
|
pid = waitpid(0, &n, WNOHANG);
|
|
|
|
if (pid > 0) {
|
|
|
|
if (WIFSIGNALED(n)) {
|
|
|
|
if (WTERMSIG(n) == SIGALRM)
|
|
|
|
badrequest(504, "Gateway Time-out",
|
|
|
|
"Gateway has hit the Time-out.");
|
|
|
|
else
|
|
|
|
badrequest(502, "Bad Gateway",
|
|
|
|
"Gateway broken or unavailable.");
|
|
|
|
}
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
2012-02-13 20:54:27 -07:00
|
|
|
signal(SIGCHLD, cgi_child);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
cgi_send_correct_http(const char *s, unsigned int sl)
|
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
char ch = 0;
|
|
|
|
for (i = 0; i < sl; ++i) {
|
|
|
|
if ((s[i] == '\r') && (s[i + 1] == '\n')) {
|
|
|
|
++i;
|
|
|
|
goto out_nl;
|
|
|
|
} else {
|
2012-02-13 22:49:10 -07:00
|
|
|
if (s[i] != '\n') {
|
|
|
|
putchar(s[i]);
|
|
|
|
} else {
|
|
|
|
out_nl:
|
|
|
|
printf("\r\n");
|
2012-02-13 20:54:27 -07:00
|
|
|
if (ch == '\n') {
|
|
|
|
++i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ch = s[i];
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
2012-02-13 22:49:10 -07:00
|
|
|
printf("%.*s", sl - i, s + i);
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
static void
|
|
|
|
start_cgi(int nph, const char *pathinfo, const char *const *envp)
|
|
|
|
{
|
|
|
|
size_t size = 0;
|
|
|
|
int n;
|
|
|
|
int pid;
|
|
|
|
char ibuf[8192],
|
|
|
|
obuf[8192];
|
|
|
|
int fd[2],
|
|
|
|
df[2];
|
|
|
|
|
|
|
|
if (pipe(fd) || pipe(df)) {
|
|
|
|
badrequest(500, "Internal Server Error",
|
|
|
|
"Server Resource problem.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((pid = fork())) {
|
|
|
|
if (pid > 0) {
|
|
|
|
struct pollfd pfd[2];
|
|
|
|
int nr = 1;
|
|
|
|
int startup = 1;
|
|
|
|
|
|
|
|
signal(SIGCHLD, cgi_child);
|
|
|
|
signal(SIGPIPE, SIG_IGN); /* NO! no signal! */
|
|
|
|
|
|
|
|
close(df[0]);
|
|
|
|
close(fd[1]);
|
|
|
|
|
|
|
|
pfd[0].fd = fd[0];
|
|
|
|
pfd[0].events = POLLIN;
|
|
|
|
pfd[0].revents = 0;
|
|
|
|
|
|
|
|
pfd[1].fd = df[1];
|
|
|
|
pfd[1].events = POLLOUT;
|
|
|
|
pfd[1].revents = 0;
|
|
|
|
|
|
|
|
if (post_len)
|
|
|
|
++nr; /* have post data */
|
|
|
|
else
|
|
|
|
close(df[1]); /* no post data */
|
|
|
|
|
|
|
|
while (poll(pfd, nr, -1) != -1) {
|
|
|
|
/*
|
|
|
|
* read from cgi
|
|
|
|
*/
|
|
|
|
if (pfd[0].revents & POLLIN) {
|
|
|
|
if (!(n = read(fd[0], ibuf, sizeof(ibuf))))
|
|
|
|
break;
|
|
|
|
if (n < 0)
|
|
|
|
goto cgi_500;
|
|
|
|
/*
|
|
|
|
* startup
|
|
|
|
*/
|
|
|
|
if (startup) {
|
|
|
|
startup = 0;
|
|
|
|
if (nph) { /* NPH-CGI */
|
2012-02-13 22:49:10 -07:00
|
|
|
printf("%.*s", n, ibuf);
|
|
|
|
/*
|
|
|
|
* skip HTTP/x.x
|
|
|
|
*/
|
|
|
|
retcode = strtoul(ibuf + 9, NULL, 10);
|
2012-02-13 20:54:27 -07:00
|
|
|
} else { /* CGI */
|
2012-02-13 22:49:10 -07:00
|
|
|
if (memcmp(ibuf, "Location: ", 10) == 0) {
|
2012-02-13 20:54:27 -07:00
|
|
|
retcode = 302;
|
2012-02-13 22:49:10 -07:00
|
|
|
printf
|
|
|
|
("HTTP/1.0 302 CGI-Redirect\r\nConnection: close\r\n");
|
2012-02-13 20:54:27 -07:00
|
|
|
signal(SIGCHLD, SIG_IGN);
|
|
|
|
cgi_send_correct_http(ibuf, n);
|
2012-02-13 22:49:10 -07:00
|
|
|
fflush(stdout);
|
2012-02-13 20:54:27 -07:00
|
|
|
dolog(0);
|
|
|
|
exit(0);
|
|
|
|
} else {
|
|
|
|
retcode = 200;
|
2012-02-13 22:49:10 -07:00
|
|
|
printf("HTTP/1.0 200 OK\r\nServer: "
|
|
|
|
FNORD
|
|
|
|
"\r\nPragma: no-cache\r\nConnection: close\r\n");
|
2012-02-13 20:54:27 -07:00
|
|
|
signal(SIGCHLD, SIG_IGN);
|
|
|
|
cgi_send_correct_http(ibuf, n);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* non startup
|
|
|
|
*/
|
|
|
|
else {
|
2012-02-13 22:49:10 -07:00
|
|
|
printf("%.*s", n, ibuf);
|
2012-02-13 20:54:27 -07:00
|
|
|
}
|
|
|
|
size += n;
|
|
|
|
if (pfd[0].revents & POLLHUP)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* write to cgi the post data
|
|
|
|
*/
|
|
|
|
else if (nr > 1 && pfd[1].revents & POLLOUT) {
|
|
|
|
if (post_miss) {
|
|
|
|
write(df[1], post_miss, post_mlen);
|
|
|
|
post_miss = 0;
|
|
|
|
} else if (post_mlen < post_len) {
|
|
|
|
n = read(0, obuf, sizeof(obuf));
|
|
|
|
if (n < 1)
|
|
|
|
goto cgi_500;
|
|
|
|
post_mlen += n;
|
|
|
|
write(df[1], obuf, n);
|
|
|
|
} else {
|
|
|
|
--nr;
|
|
|
|
close(df[1]);
|
|
|
|
}
|
|
|
|
} else if (pfd[0].revents & POLLHUP)
|
|
|
|
break;
|
|
|
|
else {
|
|
|
|
cgi_500:if (startup)
|
|
|
|
badrequest(500, "Internal Server Error",
|
|
|
|
"Looks like the CGI crashed.");
|
|
|
|
else {
|
2012-02-13 22:49:10 -07:00
|
|
|
printf("\n\nLooks like the CGI crashed.\n\n");
|
2012-02-13 20:54:27 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-02-13 22:49:10 -07:00
|
|
|
fflush(stdout);
|
2012-02-13 20:54:27 -07:00
|
|
|
dolog(size);
|
2011-08-16 14:36:11 -06:00
|
|
|
#ifdef TCP_CORK
|
2012-02-13 20:54:27 -07:00
|
|
|
{
|
|
|
|
int zero = 0;
|
|
|
|
setsockopt(1, IPPROTO_TCP, TCP_CORK, &zero, sizeof(zero));
|
|
|
|
}
|
2011-08-16 14:36:11 -06:00
|
|
|
#endif
|
2012-02-13 20:54:27 -07:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
close(df[1]);
|
|
|
|
close(fd[0]);
|
2011-08-16 14:36:11 -06:00
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
dup2(df[0], 0);
|
|
|
|
dup2(fd[1], 1);
|
2011-08-16 14:36:11 -06:00
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
close(df[0]);
|
|
|
|
close(fd[1]);
|
2011-08-16 14:36:11 -06:00
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
alarm(CGI_TIMEOUT);
|
|
|
|
do_cgi(pathinfo, envp);
|
|
|
|
}
|
|
|
|
exit(0);
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
static int
|
|
|
|
fromhex(int c)
|
|
|
|
{
|
|
|
|
if (c >= '0' && c <= '9')
|
|
|
|
return c - '0';
|
|
|
|
else {
|
|
|
|
c |= ' ';
|
|
|
|
if (c >= 'a' && c <= 'f')
|
|
|
|
return c - 'a' + 10;
|
|
|
|
}
|
|
|
|
return -1;
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
/*
|
|
|
|
* header(buf,buflen,"User-Agent")="Mozilla"
|
|
|
|
*/
|
|
|
|
static char *
|
|
|
|
header(char *buf, int buflen, const char *hname)
|
|
|
|
{
|
2012-02-13 22:49:10 -07:00
|
|
|
int slen = strlen(hname);
|
2012-02-13 20:54:27 -07:00
|
|
|
int i;
|
|
|
|
char *c;
|
|
|
|
|
|
|
|
for (i = 0; i < buflen - slen - 2; ++i) {
|
|
|
|
if (!strncasecmp(buf + i, hname, slen)) {
|
|
|
|
if (i && (buf[i - 1] && buf[i - 1] != '\n'))
|
|
|
|
continue;
|
|
|
|
if (buf[i + slen] != ':' || buf[i + slen + 1] != ' ')
|
|
|
|
continue;
|
|
|
|
c = buf + i + slen + 2;
|
|
|
|
i += slen + 2;
|
|
|
|
for (; i < buflen; ++i) {
|
|
|
|
if (buf[i] == 0 || buf[i] == '\n' || buf[i] == '\r') {
|
|
|
|
buf[i] = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while (*c == ' ' || *c == '\t')
|
|
|
|
++c;
|
|
|
|
return c;
|
|
|
|
}
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
2012-02-13 20:54:27 -07:00
|
|
|
return 0;
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
static char *encoding = 0;
|
|
|
|
static char *mimetype = "application/octet-stream";
|
|
|
|
|
|
|
|
static struct mimeentry {
|
|
|
|
const char *name,
|
|
|
|
*type;
|
|
|
|
} mimetab[] = {
|
|
|
|
{
|
|
|
|
"html", "text/html; charset=UTF-8"}, {
|
|
|
|
"htm", "text/html; charset=UTF-8"}, {
|
|
|
|
"txt", "text/plain; charset=UTF-8"}, {
|
|
|
|
"css", "text/css"}, {
|
|
|
|
"ps", "application/postscript"}, {
|
|
|
|
"pdf", "application/pdf"}, {
|
|
|
|
"js", "application/javascript"}, {
|
|
|
|
"gif", "image/gif"}, {
|
|
|
|
"png", "image/png"}, {
|
|
|
|
"jpeg", "image/jpeg"}, {
|
|
|
|
"jpg", "image/jpeg"}, {
|
|
|
|
"svg", "image/svg+xml"}, {
|
|
|
|
"mpeg", "video/mpeg"}, {
|
|
|
|
"mpg", "video/mpeg"}, {
|
|
|
|
"avi", "video/x-msvideo"}, {
|
|
|
|
"mov", "video/quicktime"}, {
|
|
|
|
"qt", "video/quicktime"}, {
|
|
|
|
"mp3", "audio/mpeg"}, {
|
|
|
|
"ogg", "audio/ogg"}, {
|
|
|
|
"wav", "audio/x-wav"}, {
|
|
|
|
"epub", "application/epub+zip"}, {
|
|
|
|
"dvi", "application/x-dvi"}, {
|
|
|
|
"pac", "application/x-ns-proxy-autoconfig"}, {
|
|
|
|
"sig", "application/pgp-signature"}, {
|
|
|
|
"swf", "application/x-shockwave-flash"}, {
|
|
|
|
"torrent", "application/x-bittorrent"}, {
|
|
|
|
"tar", "application/x-tar"}, {
|
|
|
|
"zip", "application/zip"}, {
|
|
|
|
"dtd", "text/xml"}, {
|
|
|
|
"xml", "text/xml"}, {
|
|
|
|
"xbm", "image/x-xbitmap"}, {
|
|
|
|
"xpm", "image/x-xpixmap"}, {
|
|
|
|
"xwd", "image/x-xwindowdump"}, {
|
|
|
|
"ico", "image/x-icon"}, {
|
|
|
|
0}};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* try to find out MIME type and content encoding. This is called twice,
|
|
|
|
* once for the actual URL and once for URL.gz. If the actual URL already
|
|
|
|
* ende with .gz, return application/octet-stream to make sure the client
|
|
|
|
* can download the file even if he does not support gzip encoding
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
getmimetype(char *url, int explicit)
|
|
|
|
{
|
|
|
|
char save;
|
|
|
|
int ext;
|
2012-02-13 22:49:10 -07:00
|
|
|
ext = strlen(url);
|
2012-02-13 20:54:27 -07:00
|
|
|
while (ext > 0 && url[ext] != '.' && url[ext] != '/')
|
|
|
|
--ext;
|
|
|
|
if (url[ext] == '.') {
|
|
|
|
++ext;
|
2012-02-13 22:49:10 -07:00
|
|
|
if (!strcmp(url + ext, "bz2"))
|
2012-02-13 20:54:27 -07:00
|
|
|
goto octetstream;
|
2012-02-13 22:49:10 -07:00
|
|
|
if (!strcmp(url + ext, "gz")) {
|
2012-02-13 20:54:27 -07:00
|
|
|
if (!encoding) {
|
|
|
|
if (explicit)
|
|
|
|
goto octetstream;
|
|
|
|
encoding = "gzip";
|
|
|
|
save = url[ext - 1];
|
|
|
|
url[ext - 1] = 0;
|
|
|
|
getmimetype(url, explicit);
|
|
|
|
url[ext - 1] = save;
|
|
|
|
} else
|
|
|
|
octetstream:
|
|
|
|
mimetype = "application/octet-stream";
|
|
|
|
} else {
|
|
|
|
int i;
|
|
|
|
for (i = 0; mimetab[i].name; ++i)
|
2012-02-13 22:49:10 -07:00
|
|
|
if (!strcmp(mimetab[i].name, url + ext)) {
|
2012-02-13 20:54:27 -07:00
|
|
|
mimetype = (char *) mimetab[i].type;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
static int
|
|
|
|
matchcommalist(const char *needle, const char *haystack)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* needle: "text/html", haystack: the accept header, "text/html,
|
|
|
|
* text/plain\r\n"
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
* return nonzero if match was found
|
|
|
|
*/
|
2012-02-13 22:49:10 -07:00
|
|
|
int len = strlen(needle);
|
|
|
|
if (strncmp(needle, haystack, len))
|
2012-02-13 20:54:27 -07:00
|
|
|
return 0;
|
|
|
|
switch (haystack[len]) {
|
|
|
|
case ';':
|
|
|
|
case ',':
|
|
|
|
case '\r':
|
|
|
|
case '\n':
|
|
|
|
case 0:
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
static int
|
|
|
|
findincommalist(const char *needle, const char *haystack)
|
|
|
|
{
|
|
|
|
const char *accept;
|
|
|
|
for (accept = haystack; accept;) {
|
|
|
|
/*
|
|
|
|
* format: foo/bar,
|
|
|
|
*/
|
|
|
|
const char *tmp = accept;
|
|
|
|
int final;
|
|
|
|
while (*tmp) {
|
|
|
|
if (*tmp == ';')
|
|
|
|
break;
|
|
|
|
else if (*tmp == ',')
|
|
|
|
break;
|
|
|
|
++tmp;
|
|
|
|
}
|
|
|
|
final = (*tmp == 0 || *tmp == ';');
|
|
|
|
if (matchcommalist("*/*", accept))
|
|
|
|
break;
|
|
|
|
if (matchcommalist(haystack, accept))
|
|
|
|
break;
|
|
|
|
accept = tmp + 1;
|
|
|
|
if (final)
|
|
|
|
return 0;
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
2012-02-13 20:54:27 -07:00
|
|
|
return 1;
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
|
|
|
|
2011-08-16 15:16:03 -06:00
|
|
|
/*
|
|
|
|
* timerfc function Copyright 1996, Michiel Boland.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or
|
|
|
|
* without modification, are permitted provided that the following
|
|
|
|
* conditions are met:
|
|
|
|
*
|
|
|
|
* 1. Redistributions of source code must retain the above
|
|
|
|
* copyright notice, this list of conditions and the following
|
|
|
|
* disclaimer.
|
|
|
|
*
|
|
|
|
* 2. Redistributions in binary form must reproduce the above
|
|
|
|
* copyright notice, this list of conditions and the following
|
|
|
|
* disclaimer in the documentation and/or other materials
|
|
|
|
* provided with the distribution.
|
|
|
|
*
|
|
|
|
* 3. The name of the author may not be used to endorse or promote
|
|
|
|
* products derived from this software without specific prior
|
|
|
|
* written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
|
|
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
|
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
|
|
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
|
|
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
|
|
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
|
|
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
|
|
* THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
2012-02-13 22:49:10 -07:00
|
|
|
static time_t
|
2012-02-13 20:54:27 -07:00
|
|
|
timerfc(const char *s)
|
2011-08-16 15:16:03 -06:00
|
|
|
{
|
2012-02-13 20:54:27 -07:00
|
|
|
static const int daytab[2][12] = {
|
|
|
|
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
|
|
|
|
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}
|
|
|
|
};
|
|
|
|
unsigned sec,
|
|
|
|
min,
|
|
|
|
hour,
|
|
|
|
day,
|
|
|
|
mon,
|
|
|
|
year;
|
|
|
|
char month[3];
|
|
|
|
int c;
|
|
|
|
unsigned n;
|
|
|
|
char flag;
|
|
|
|
char state;
|
|
|
|
char isctime;
|
|
|
|
enum { D_START, D_END, D_MON, D_DAY, D_YEAR, D_HOUR, D_MIN, D_SEC };
|
|
|
|
|
|
|
|
sec = 60;
|
|
|
|
min = 60;
|
|
|
|
hour = 24;
|
|
|
|
day = 32;
|
|
|
|
year = 1969;
|
|
|
|
isctime = 0;
|
|
|
|
month[0] = 0;
|
|
|
|
state = D_START;
|
|
|
|
n = 0;
|
|
|
|
flag = 1;
|
|
|
|
do {
|
|
|
|
c = *s++;
|
|
|
|
switch (state) {
|
|
|
|
case D_START:
|
|
|
|
if (c == ' ') {
|
|
|
|
state = D_MON;
|
|
|
|
isctime = 1;
|
|
|
|
} else if (c == ',')
|
|
|
|
state = D_DAY;
|
|
|
|
break;
|
|
|
|
case D_MON:
|
|
|
|
if (isalpha(c)) {
|
|
|
|
if (n < 3)
|
|
|
|
month[n++] = c;
|
|
|
|
} else {
|
|
|
|
if (n < 3)
|
|
|
|
return -1;
|
|
|
|
n = 0;
|
|
|
|
state = isctime ? D_DAY : D_YEAR;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case D_DAY:
|
|
|
|
if (c == ' ' && flag);
|
|
|
|
else if (isdigit(c)) {
|
|
|
|
flag = 0;
|
|
|
|
n = 10 * n + (c - '0');
|
|
|
|
} else {
|
|
|
|
day = n;
|
|
|
|
n = 0;
|
|
|
|
state = isctime ? D_HOUR : D_MON;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case D_YEAR:
|
|
|
|
if (isdigit(c))
|
|
|
|
n = 10 * n + (c - '0');
|
|
|
|
else {
|
|
|
|
year = n;
|
|
|
|
n = 0;
|
|
|
|
state = isctime ? D_END : D_HOUR;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case D_HOUR:
|
|
|
|
if (isdigit(c))
|
|
|
|
n = 10 * n + (c - '0');
|
|
|
|
else {
|
|
|
|
hour = n;
|
|
|
|
n = 0;
|
|
|
|
state = D_MIN;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case D_MIN:
|
|
|
|
if (isdigit(c))
|
|
|
|
n = 10 * n + (c - '0');
|
|
|
|
else {
|
|
|
|
min = n;
|
|
|
|
n = 0;
|
|
|
|
state = D_SEC;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case D_SEC:
|
|
|
|
if (isdigit(c))
|
|
|
|
n = 10 * n + (c - '0');
|
|
|
|
else {
|
|
|
|
sec = n;
|
|
|
|
n = 0;
|
|
|
|
state = isctime ? D_YEAR : D_END;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} while (state != D_END && c);
|
|
|
|
switch (month[0]) {
|
|
|
|
case 'A':
|
|
|
|
mon = (month[1] == 'p') ? 4 : 8;
|
|
|
|
break;
|
|
|
|
case 'D':
|
|
|
|
mon = 12;
|
|
|
|
break;
|
|
|
|
case 'F':
|
|
|
|
mon = 2;
|
|
|
|
break;
|
|
|
|
case 'J':
|
|
|
|
mon = (month[1] == 'a') ? 1 : ((month[2] == 'l') ? 7 : 6);
|
|
|
|
break;
|
|
|
|
case 'M':
|
|
|
|
mon = (month[2] == 'r') ? 3 : 5;
|
|
|
|
break;
|
|
|
|
case 'N':
|
|
|
|
mon = 11;
|
|
|
|
break;
|
|
|
|
case 'O':
|
|
|
|
mon = 10;
|
|
|
|
break;
|
|
|
|
case 'S':
|
|
|
|
mon = 9;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (year <= 100)
|
|
|
|
year += (year < 70) ? 2000 : 1900;
|
|
|
|
--mon;
|
|
|
|
--day;
|
|
|
|
if (sec >= 60 || min >= 60 || hour >= 60 || day >= 31)
|
|
|
|
return -1;
|
|
|
|
if (year < 1970)
|
|
|
|
return 0;
|
|
|
|
return sec + 60L * (min + 60L * (hour + 24L * (day +
|
|
|
|
daytab[year % 4 == 0
|
|
|
|
&& (year % 100
|
|
|
|
|| year %
|
|
|
|
400 ==
|
|
|
|
0)][mon] +
|
|
|
|
365L * (year - 1970L) +
|
|
|
|
((year -
|
|
|
|
1969L) >> 2))));
|
2011-08-16 14:36:11 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct stat st;
|
|
|
|
|
2012-02-13 20:54:27 -07:00
|
|
|
/*
|
|
|
|
* try to return a file
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
doit(char *buf, int buflen, char *url, int explicit)
|
|
|
|
{
|
|
|
|
int fd = -1;
|
|
|
|
char *accept;
|
|
|
|
while (url[0] == '/')
|
|
|
|
++url;
|
|
|
|
getmimetype(url, explicit);
|
2011-08-16 15:47:35 -06:00
|
|
|
{
|
2012-02-13 20:54:27 -07:00
|
|
|
char *b = buf;
|
|
|
|
int l = buflen;
|
|
|
|
for (;;) {
|
|
|
|
char *h = header(b, l, "Accept");
|
|
|
|
if (!h)
|
|
|
|
goto ok;
|
|
|
|
if (findincommalist(mimetype, h))
|
|
|
|
goto ok;
|
|
|
|
l -= (h - b) + 1;
|
|
|
|
b = h + 1;
|
|
|
|
}
|
|
|
|
retcode = 406;
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
ok:
|
|
|
|
if (encoding) { /* see if client accepts the encoding */
|
|
|
|
char *tmp = header(buf, buflen, "Accept-Encoding");
|
|
|
|
if (!tmp || !strstr(tmp, "gzip")) {
|
|
|
|
retcode = 406;
|
|
|
|
goto bad;
|
|
|
|
}
|
2011-08-16 15:47:35 -06:00
|
|
|
}
|
2012-02-13 20:54:27 -07:00
|
|
|
if ((fd = open(url, O_RDONLY)) >= 0) {
|
|
|
|
if (fstat(fd,< |