chimera2/proto/http.c

1329 lines
28 KiB
C

/*
* http.c
*
* Copyright (C) 1993-1997, John Kilburg <john@cs.unlv.edu>
*
* 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
* (at your option) 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "port_before.h"
#include <stdio.h>
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include "port_after.h"
#include "Chimera.h"
#include "ChimeraStream.h"
#include "ChimeraAuth.h"
#include "ChimeraSource.h"
#include "mime.h"
/*
* OK, this is a little bit weird. The context that is passed is
* actually the address of the address of the context. This is
* because once a context is returned by HTTPInit it can't be
* changed (at least the way this circus works). Its convienent
* to be able to destroy the context when a new connection is made
* because of authorization stuff or a redirect occurs.
*/
/*
* Size of chunks to allocate while reading.
*/
#define CHUNKSIZE 16384
#define PRINT_RATE 10
#define MSGLEN 1024
#define HM_OPEN 0
#define HM_RU 1
#define HM_RK 2
#define HM_DONE 3
#define HM_SEND 4
#define HM_WAITING 5
static struct http_message
{
char *name;
char *def;
} http_messages[] =
{
{ "http.open", "Connecting to " },
{ "http.read_unknown", " bytes read so far." },
{ "http.read_known", " bytes remaining." },
{ "http.done", "HTTP read finished." },
{ "http.send", "Sending request..." },
{ "http.waiting", "Waiting for a response from " },
{ NULL, NULL },
};
typedef struct
{
char *username;
char *password;
char *hostname;
char *realm;
char *type;
int port;
} HTTPPassword;
typedef struct
{
MemPool mp;
char *msg[sizeof(http_messages) / sizeof(http_messages[0])];
size_t mlen;
GList passwords; /* username/password/realm cache */
} HTTPClass;
typedef struct
{
MemPool mp, rmp;
ChimeraSource ws;
ChimeraResources cres;
/* addressing stuff */
ChimeraRequest *wr; /* request addresses */
char *url; /* URL to use for request */
URLParts *up; /* URLParts to use for request */
URLParts *pup; /* proxy server */
/* other stuff */
HTTPClass *hc;
char msgbuf[MSGLEN];
int nline;
int rcount;
/* Information from HTTP status line */
int status;
int major;
int minor;
/* authorization stuff */
ChimeraAuth wa;
HTTPPassword *hp;
char *auth_type; /* used for the Auth callback */
char *realm; /* used for the Auth callback */
ChimeraStream ios;
/* Information about the data received. */
byte *b;
size_t blen;
size_t bsize;
size_t bmax;
size_t doff; /* offset to data beyond header */
MIMEHeader mh;
} HTTPInfo;
static int uuencode _ArgProto((unsigned char *, unsigned int, char *));
static byte *HTTPBuildRequest _ArgProto((HTTPInfo *, ssize_t *));
static int HTTPCreateInfo _ArgProto((HTTPInfo **,
ChimeraSource, ChimeraRequest *,
HTTPClass *,
URLParts *, URLParts *,
HTTPPassword *));
static void HTTPCancel _ArgProto((void *));
static void *HTTPInit _ArgProto((ChimeraSource, ChimeraRequest *, void *));
static void HTTPDestroy _ArgProto((void *));
static void HTTPDestroyInfo _ArgProto((HTTPInfo *));
static void HTTPGetData _ArgProto((void *, byte **, size_t *, MIMEHeader *));
static byte *HTTPRequest_Auth _ArgProto((HTTPInfo *, ssize_t *));
static byte *HTTPRequest_UserAgent _ArgProto((HTTPInfo *, ssize_t *));
static byte *HTTPRequest_AcceptLang _ArgProto((HTTPInfo *, ssize_t *));
static byte *HTTPRequest_Accept _ArgProto((HTTPInfo *, ssize_t *));
static byte *HTTPRequest_Pragma _ArgProto((HTTPInfo *, ssize_t *));
static byte *HTTPRequest_Method _ArgProto((HTTPInfo *, ssize_t *));
static byte *HTTPRequest_Host _ArgProto((HTTPInfo *, ssize_t *));
static byte *HTTPRequest_Data1 _ArgProto((HTTPInfo *, ssize_t *));
static byte *HTTPRequest_Data2 _ArgProto((HTTPInfo *, ssize_t *));
static void HTTPFailure _ArgProto((HTTPInfo *));
static void HTTPRead _ArgProto((HTTPInfo **, ChimeraStreamCallback));
void HTTPReadHeader _ArgProto((ChimeraStream, ssize_t, void *));
static void HTTPReadData _ArgProto((ChimeraStream, ssize_t, void *));
static void HTTPReadUnknown _ArgProto((ChimeraStream, ssize_t, void *));
static void HTTPRequestDone _ArgProto((ChimeraStream, ssize_t, void *));
static void HTTPClassDestroy _ArgProto((void *));
void InitModule_HTTP _ArgProto((ChimeraResources));
static char *HTTPGetFilename _ArgProto((HTTPInfo *));
static int HTTPCheckHeader _ArgProto((HTTPInfo **));
HTTPPassword *HTTPFindPassword _ArgProto((HTTPInfo *, char *,
char *, int));
HTTPPassword *HTTPAddPassword _ArgProto((HTTPInfo *, char *, char *,
char *, char *, char *, int));
static void HTTPAuthCallback _ArgProto((void *, char *, char *));
/*
* crap for uuencode
*/
static char six2pr[64] =
{
'A','B','C','D','E','F','G','H','I','J','K','L','M',
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'a','b','c','d','e','f','g','h','i','j','k','l','m',
'n','o','p','q','r','s','t','u','v','w','x','y','z',
'0','1','2','3','4','5','6','7','8','9','+','/'
};
/*
* uuencode
*
* I snarfed this code from some version of libwww.
*
* ACKNOWLEDGEMENT:
* This code is taken from rpem distribution, and was originally
* written by Mark Riordan.
*
* AUTHORS:
* MR Mark Riordan riordanmr@clvax1.cl.msu.edu
* AL Ari Luotonen luotonen@dxcern.cern.ch
*
*/
static int
uuencode(bufin, nbytes, bufcoded)
unsigned char *bufin;
unsigned int nbytes;
char *bufcoded;
{
/* ENC is the basic 1 character encoding function to make a char printing */
#define ENC(c) six2pr[c]
register char *outptr = bufcoded;
unsigned int i;
for (i=0; i<nbytes; i += 3)
{
*(outptr++) = ENC(*bufin >> 2); /* c1 */
*(outptr++) = ENC(((*bufin << 4) & 060) | ((bufin[1] >> 4) & 017)); /*c2*/
*(outptr++) = ENC(((bufin[1] << 2) & 074) | ((bufin[2] >> 6) & 03));/*c3*/
*(outptr++) = ENC(bufin[2] & 077); /* c4 */
bufin += 3;
}
/* If nbytes was not a multiple of 3, then we have encoded too
* many characters. Adjust appropriately.
*/
if (i == nbytes + 1)
{
/* There were only 2 bytes in that last group */
outptr[-1] = '=';
}
else if (i == nbytes + 2)
{
/* There was only 1 byte in that last group */
outptr[-1] = '=';
outptr[-2] = '=';
}
*outptr = '\0';
return(outptr - bufcoded);
}
/*
* HTTPAuthCallback
*/
static void
HTTPAuthCallback(closure, username, password)
void *closure;
char *username;
char *password;
{
HTTPInfo **hip = (HTTPInfo **)closure;
HTTPInfo *hi = *hip;
HTTPPassword *hp;
if (username == NULL || password == NULL)
{
HTTPCancel(hip);
return;
}
hp = HTTPAddPassword(hi, username, password, hi->realm, hi->auth_type,
hi->up->hostname, hi->up->port);
if (HTTPCreateInfo(hip, hi->ws, hi->wr, hi->hc,
hi->up, hi->pup, hp) == -1)
{
HTTPFailure(hi);
}
HTTPDestroyInfo(hi);
return;
}
/*
* HTTPFindPassword
*/
HTTPPassword *
HTTPFindPassword(hi, realm, hostname, port)
HTTPInfo *hi;
char *realm;
char *hostname;
int port;
{
HTTPPassword *hp;
port = port == 0 ? 80:port;
for (hp = (HTTPPassword *)GListGetHead(hi->hc->passwords); hp != NULL;
hp = (HTTPPassword *)GListGetNext(hi->hc->passwords))
{
if (strlen(realm) == strlen(hp->realm) &&
strcmp(realm, hp->realm) == 0 &&
strlen(hostname) == strlen(hp->hostname) &&
strcasecmp(hostname, hp->hostname) == 0 &&
port == hp->port)
{
return(hp);
}
}
return(NULL);
}
/*
* HTTPAddPassword
*/
HTTPPassword *
HTTPAddPassword(hi, username, password, realm, type, hostname, port)
HTTPInfo *hi;
char *username;
char *password;
char *realm;
char *type;
char *hostname;
int port;
{
HTTPPassword *hp;
MemPool mp;
if (username == NULL) return(NULL);
if (hostname == NULL) return(NULL);
mp = hi->hc->mp;
hp = (HTTPPassword *)MPCGet(mp, sizeof(HTTPPassword));
hp->username = MPStrDup(mp, username);
if (password != NULL) hp->password = MPStrDup(mp, password);
else hp->password = MPStrDup(mp, "");
/*
* Probably shouldn't have defaults here. Should just error out.
*/
if (realm != NULL) hp->realm = MPStrDup(mp, realm);
else hp->realm = MPStrDup(mp, "");
if (type != NULL) hp->type = MPStrDup(mp, type);
else hp->type = MPStrDup(mp, "basic");
hp->hostname = MPStrDup(mp, hostname);
hp->port = port == 0 ? 80:port;
GListAddHead(hi->hc->passwords, hp);
return(hp);
}
/*
* HTTPRequest_Auth
*/
static byte *
HTTPRequest_Auth(hi, len)
HTTPInfo *hi;
ssize_t *len;
{
char *t, *line = NULL;
const char *authfield = "Authorization: ";
char *username;
char *password;
if (hi->hp == NULL) return(NULL);
if (hi->hp->type == NULL || strcasecmp(hi->hp->type, "basic") != 0)
{
return(NULL);
}
username = hi->hp->username;
password = hi->hp->password;
line = (char *)MPGet(hi->rmp,
strlen(authfield) +
2 * strlen(username) + 2 * strlen(":") +
2 * strlen(password != NULL ? password:"") +
2 * strlen(hi->hp->type) +
strlen("\r\n") + 1);
strcpy(line, username);
if (password != NULL)
{
strcat(line, ":");
strcat(line, password);
}
t = (char *)MPGet(hi->rmp, strlen(line) * 2);
uuencode(line, strlen(line), t);
strcpy(line, authfield);
strcat(line, hi->hp->type);
strcat(line, " ");
strcat(line, t);
strcat(line, "\r\n");
if (line != NULL) *len = strlen(line);
return((byte *)line);
}
/*
* HTTPRequest_UserAgent
*/
static byte *
HTTPRequest_UserAgent(hi, len)
HTTPInfo *hi;
ssize_t *len;
{
char *ua;
char *line = NULL;
const char *uaformat = "User-agent: %s\r\n";
size_t linelen;
ua = ResourceGetString(hi->cres, "http.userAgent");
if (ua == NULL) return(NULL);
linelen = strlen(uaformat) + strlen(ua) + 1;
line = (char *)MPGet(hi->rmp, linelen);
snprintf (line, linelen, uaformat, ua);
if (line != NULL) *len = strlen(line);
return((byte *)line);
}
/*
* HTTPRequest_AcceptLang
*/
static byte *
HTTPRequest_AcceptLang(hi, len)
HTTPInfo *hi;
ssize_t *len;
{
char *line;
char *acc;
const char *accformat = "Accept-Language: %s\r\n";
size_t linelen;
if ((acc = ResourceGetString(hi->cres, "http.acceptLanguage")) != NULL)
{
linelen = strlen(accformat) + strlen(acc) + 1;
line = (char *)MPGet(hi->rmp, linelen);
snprintf (line, linelen, accformat, acc);
*len = strlen(line);
return((byte *)line);
}
return(NULL);
}
/*
* HTTPRequest_Accept
*/
static byte *
HTTPRequest_Accept(hi, len)
HTTPInfo *hi;
ssize_t *len;
{
GList list;
char *nr;
size_t rlen;
size_t delimlen;
char *line = NULL;
const char *accfield = "Accept: ";
const char *delim = ",";
if (hi->wr->contents == NULL) line = "Accept: */*\r\n";
else
{
list = hi->wr->contents;
delimlen = strlen(delim);
rlen = 0;
for (nr = (char *)GListGetHead(list); nr != NULL;
nr = (char *)GListGetNext(list))
{
rlen += strlen(nr) + delimlen;
}
line = (char *)MPGet(hi->rmp, strlen(accfield) +
rlen + strlen("\r\n") + 1);
strcpy(line, accfield);
for (nr = (char *)GListGetHead(list); nr != NULL; )
{
strcat(line, nr);
if ((nr = GListGetNext(list)) != NULL) strcat(line, delim);
}
strcat(line, "\r\n");
}
if (line != NULL) *len = strlen(line);
return((byte *)line);
}
/*
* HTTPRequest_Pragma
*/
static byte *
HTTPRequest_Pragma(hi, len)
HTTPInfo *hi;
ssize_t *len;
{
const char *pragma = "Pragma: no-cache\r\n";
if (hi->wr->reload)
{
*len = strlen(pragma);
return((byte *)MPStrDup(hi->rmp, pragma));
}
return(NULL);
}
/*
* HTTPRequest_Method
*/
static byte *
HTTPRequest_Method(hi, len)
HTTPInfo *hi;
ssize_t *len;
{
char *filename;
char *line = NULL;
ChimeraRequest *wr;
const char *getformat1 = "GET %s HTTP/1.0\r\n";
const char *getformat2 = "GET %s%s%s HTTP/1.0\r\n";
const char *postformat = "POST %s HTTP/1.0\r\n";
size_t linelen;
filename = HTTPGetFilename(hi);
wr = hi->wr;
if (wr->input_method == NULL || strcasecmp(wr->input_method, "GET") == 0)
{
if (wr->input_data != NULL && wr->input_len > 0)
{
linelen = strlen(getformat2) + wr->input_len + strlen(filename) +
strlen("?") + 1;
line = (char *)MPGet(hi->rmp, linelen);
snprintf (line, linelen, getformat2, filename, "?", wr->input_data);
}
else
{
linelen = strlen(getformat1) + strlen(filename) + 1;
line = (char *)MPGet(hi->rmp, linelen);
snprintf (line, linelen, getformat1, filename);
}
}
else if (strcasecmp(wr->input_method, "POST") == 0)
{
linelen = strlen(postformat) + strlen(filename) + 1;
line = (char *)MPGet(hi->rmp, linelen);
snprintf (line, linelen, postformat, filename);
}
else
{
*len = -1;
return(NULL);
}
if (line != NULL) *len = strlen(line);
return((byte *)line);
}
/*
* HTTPRequest_Host
*/
static byte *
HTTPRequest_Host(hi, len)
HTTPInfo *hi;
ssize_t *len;
{
char *host;
int port;
char *line;
size_t linelen;
host = hi->up->hostname;
port = hi->up->port;
linelen = strlen(host) + strlen("Host:") + 50;
line = (char *)MPCGet(hi->rmp, linelen);
if (port == 0) snprintf (line, linelen, "Host: %s\r\n", host);
else snprintf (line, linelen, "Host: %s:%d\r\n", host, port);
if (line != NULL) *len = strlen(line);
return((byte *)line);
}
/*
* HTTPRequest_Data1
*/
static byte *
HTTPRequest_Data1(hi, len)
HTTPInfo *hi;
ssize_t *len;
{
char *line = NULL;
const char *format = "Content-type: %s\r\nContent-length: %d\r\n";
ChimeraRequest *wr = hi->wr;
size_t linelen;
if (wr->input_method == NULL ||
strcasecmp(wr->input_method, "POST") != 0)
{
return(NULL);
}
if (wr->input_data != NULL && wr->input_len > 0 &&
wr->input_type != NULL)
{
linelen = strlen(format) + strlen(wr->input_type) + 101;
line = (char *)MPGet(hi->rmp, linelen);
snprintf (line, linelen, format, wr->input_type, wr->input_len);
}
if (line != NULL) *len = strlen(line);
return((byte *)line);
}
/*
* HTTPRequest_Data2
*/
static byte *
HTTPRequest_Data2(hi, len)
HTTPInfo *hi;
ssize_t *len;
{
ChimeraRequest *wr = hi->wr;
if (wr->input_method == NULL ||
strcasecmp(wr->input_method, "POST") != 0)
{
return(NULL);
}
if (wr->input_data != NULL && wr->input_len > 0 &&
wr->input_type != NULL)
{
*len = wr->input_len;
return(wr->input_data);
}
return(NULL);
}
/*
* HTTPBuildRequest
*
* This became less efficient but easier to hack, I think.
*/
static byte *
HTTPBuildRequest(hi, len)
HTTPInfo *hi;
ssize_t *len;
{
byte *line = NULL;
do
{
*len = 0;
if (hi->nline == 0) line = HTTPRequest_Method(hi, len);
else if (hi->nline == 1) line = HTTPRequest_Host(hi, len);
else if (hi->nline == 2) line = HTTPRequest_UserAgent(hi, len);
else if (hi->nline == 3) line = HTTPRequest_Accept(hi, len);
else if (hi->nline == 4) line = HTTPRequest_AcceptLang(hi, len);
else if (hi->nline == 5) line = HTTPRequest_Pragma(hi, len);
else if (hi->nline == 6) line = HTTPRequest_Auth(hi, len);
/* this must be last */
else if (hi->nline == 7) line = HTTPRequest_Data1(hi, len);
else if (hi->nline == 8)
{
*len = strlen("\r\n");
line = (byte *)MPStrDup(hi->rmp, "\r\n");
}
else if (hi->nline == 9) line = HTTPRequest_Data2(hi, len);
else break;
hi->nline++;
} while (line == NULL && *len != -1);
return(line);
}
/*
* HTTPFailure
*/
static void
HTTPFailure(hi)
HTTPInfo *hi;
{
HTTPCancel(&hi);
SourceStop(hi->ws, "Read error during HTTP transfer");
return;
}
/*
* HTTPGetFilename
*/
static char *
HTTPGetFilename(hi)
HTTPInfo *hi;
{
char *filename;
/*
* If there is a proxy URL supplied then get the entire URL and not just
* just the filename part. If there is no proxy then just use the
* filename part.
*/
if (hi->pup != NULL) filename = URLMakeString(hi->mp, hi->up, false);
else filename = hi->up->filename;
if (filename == NULL) filename = "/";
return(filename);
}
/*
* HTTPRead
*/
static void
HTTPRead(hip, func)
HTTPInfo **hip;
ChimeraStreamCallback func;
{
size_t rsize;
HTTPInfo *hi = *hip;
if (hi->bmax > 0)
{
rsize = hi->bmax - hi->blen;
if (rsize > CHUNKSIZE) rsize = CHUNKSIZE;
}
else rsize = CHUNKSIZE;
if (hi->bmax == 0 || ((hi->bsize - hi->blen) < rsize))
{
if (hi->b == NULL) hi->b = (byte *)alloc_mem(rsize);
else hi->b = (byte *)realloc_mem(hi->b, hi->bsize + rsize);
hi->bsize += rsize;
}
StreamRead(hi->ios, hi->b + hi->blen, rsize, func, hip);
return;
}
/*
* HTTPReadData
*/
static void
HTTPReadData(ios, len, closure)
ChimeraStream ios;
ssize_t len;
void *closure;
{
HTTPInfo **hip = (HTTPInfo **)closure;
HTTPInfo *hi = *hip;
long rnum;
char *rmsg;
if (len < 0)
{
HTTPFailure(hi);
return;
}
if (len > 0) hi->blen += len;
if (len == 0 || ((hi->blen - hi->doff) == hi->bmax && hi->bmax > 0))
{
SourceSendMessage(hi->ws, hi->hc->msg[HM_DONE]);
SourceEnd(hi->ws);
}
else
{
/*
* Don't want to return data until we know the final size...realloc()
* causes trouble.
*/
if (hi->bmax > 0)
{
if (hi->blen - hi->doff > 0) SourceAdd(hi->ws);
rmsg = hi->hc->msg[HM_RK];
rnum = (long)(hi->bmax - hi->blen - hi->doff);
}
else
{
rmsg = hi->hc->msg[HM_RU];
rnum = (long)(hi->blen - hi->doff);
}
if (hi->rcount++ % PRINT_RATE == 0)
{
snprintf (hi->msgbuf, sizeof(hi->msgbuf), "%ld %s", rnum, rmsg);
SourceSendMessage(hi->ws, hi->msgbuf);
}
HTTPRead(hip, HTTPReadData);
}
return;
}
/*
* HTTPCheckHeader
*
* Scrounges around in the header fields to see if there is any
* interesting information.
*/
static int
HTTPCheckHeader(hip)
HTTPInfo **hip;
{
char *value;
size_t clen;
char *option;
char *list;
URLParts *up, *pup;
char *url;
char *username;
char *cp;
bool cache;
HTTPInfo *hi = *hip;
if (hi->status >= 300) cache = false;
else cache = true;
/*
* Take action on MIME fields.
*/
if ((MIMEGetField(hi->mh, "location", &value) == 0 && value != NULL) &&
hi->status >= 300 && hi->status < 400)
{
up = URLParse(hi->mp, value);
pup = NULL;
/* Just blindly follow the URL unless it should be used as a proxy */
if (hi->status != 305) up = URLResolve(hi->mp, up, hi->up);
else
{
pup = up;
up = hi->up;
}
if (HTTPCreateInfo(hip, hi->ws, hi->wr, hi->hc,
up, pup, hi->hp) == -1)
{
HTTPFailure(hi);
}
HTTPDestroyInfo(hi);
return(-1);
}
if (MIMEGetField(hi->mh, "content-length", &value) == 0 && value != NULL)
{
clen = (size_t)atoi(value) + hi->doff;
if (clen > 0 && clen > hi->blen && clen > hi->bsize)
{
hi->b = (byte *)realloc_mem(hi->b, clen);
hi->bsize = clen;
hi->bmax = clen;
}
else hi->bmax = 0;
}
if (MIMEGetField(hi->mh, "pragma", &value) == 0 && value != NULL)
{
list = value;
while ((option = mystrtok(list, ' ', &list)) != NULL)
{
if (strcasecmp(option, "no-cache") == 0) cache = false;
}
}
if (MIMEGetField(hi->mh, "www-authenticate", &value) == 0 &&
value != NULL && hi->hp == NULL)
{
cache = false;
/*
* Look for the authorization type and realm
*/
list = value;
while ((option = mystrtok(list, ' ', &list)) != NULL)
{
if (strcasecmp(option, "basic") == 0)
{
hi->auth_type = MPStrDup(hi->mp, option);
}
else if (strncasecmp(option, "realm=", 6) == 0)
{
for (cp = option; *cp != '\0'; cp++)
{
if (*cp == '=')
{
hi->realm = MPStrDup(hi->mp, cp + 1);
break;
}
}
}
}
/*
* If there is an authorization type and realm then try to
* see if the password is already known. If not then ask the user
* for a username and password.
*/
if (hi->auth_type != NULL && hi->realm != NULL)
{
if ((hi->hp = HTTPFindPassword(hi, hi->realm,
hi->up->hostname,
hi->up->port)) != NULL)
{
if (HTTPCreateInfo(hip, hi->ws, hi->wr, hi->hc,
hi->up, hi->pup, hi->hp) == -1)
{
HTTPFailure(hi);
}
HTTPDestroyInfo(hi);
return(-1);
}
else
{
if (hi->up->username != NULL)
{
if (hi->up->password != NULL)
{
HTTPAuthCallback(hip, hi->up->username,
hi->up->password);
username = NULL;
}
else username = hi->up->username;
}
else username = "";
if (username != NULL) {
hi->wa = AuthCreate(hi->cres, "Enter password", username,
HTTPAuthCallback, hip);
}
/*
* If the authorization context is created then return -1 to
* indicate that is the end of the transaction. If the context
* is not created then pass through so the auth message will
* appear.
*/
if (hi->wa != NULL) return(-1);
}
}
}
if (MIMEGetField(hi->mh, "content-type", &value) != 0 || value == NULL)
{
if ((value = ChimeraExt2Content(hi->cres, HTTPGetFilename(hi))) == NULL)
{
value = "text/html";
}
MIMEAddField(hi->mh, "content-type", value);
}
if (hi->url != NULL) url = hi->url;
else url = "unknown:/";
MIMEAddField(hi->mh, "x-url", url);
/* Do not call this before dealing with "Location:" */
SourceInit(hi->ws, cache && hi->hp == NULL);
return(0);
}
/*
* HTTPReadHeader
*/
void
HTTPReadHeader(ios, len, closure)
ChimeraStream ios;
ssize_t len;
void *closure;
{
HTTPInfo **hip = (HTTPInfo **)closure;
HTTPInfo *hi = *hip;
char *cp;
ssize_t i;
size_t moff = 0;
if (len <= 0)
{
HTTPFailure(hi);
return;
}
if (MIMEFindData(hi->mh, hi->b, hi->blen + len, &(hi->doff)) == 0)
{
/*
* Look for the end of the first line.
*/
for (i = 0, cp = (char *)hi->b; i < hi->doff; i++, cp++)
{
if (*cp == '\n')
{
moff = i + 1;
break;
}
}
myassert(i < hi->doff, "MIMEFindData must be broken");
if (sscanf((char *)hi->b, "HTTP/%d.%d %d",
&hi->major, &hi->minor, &hi->status) != 3)
{
HTTPFailure(hi);
return;
}
MIMEParseBuffer(hi->mh, hi->b + moff, hi->doff - moff);
if (HTTPCheckHeader(hip) == -1) return;
HTTPReadData(ios, len, closure);
}
else
{
snprintf (hi->msgbuf, sizeof(hi->msgbuf),
"%ld %s", (long)hi->blen, hi->hc->msg[HM_RU]);
SourceSendMessage(hi->ws, hi->msgbuf);
hi->blen += len;
HTTPRead(hip, HTTPReadHeader);
}
return;
}
/*
* HTTPReadUnknown
*/
static void
HTTPReadUnknown(ios, len, closure)
ChimeraStream ios;
ssize_t len;
void *closure;
{
HTTPInfo **hip = (HTTPInfo **)closure;
HTTPInfo *hi = *hip;
char *content;
size_t nblen;
if (len < 0 || (len == 0 && hi->blen == 0))
{
HTTPFailure(hi);
return;
}
nblen = hi->blen + len;
if (nblen < 5 && len > 0)
{
hi->blen = nblen;
/* Not enough information to know the HTTP version */
HTTPRead(hip, HTTPReadUnknown);
}
else if (nblen >= 5 && strncmp("HTTP/", (char *)hi->b, 5) == 0)
{
HTTPReadHeader(ios, len, hip);
}
else
{
/* Must be HTTP/0.9 */
if ((content = ChimeraExt2Content(hi->cres, HTTPGetFilename(hi))) == NULL)
{
content = "text/html";
}
MIMEAddField(hi->mh, "content-type", content);
MIMEAddField(hi->mh, "x-url", hi->url);
SourceInit(hi->ws, true);
HTTPReadData(ios, len, hip);
}
snprintf (hi->msgbuf, sizeof(hi->msgbuf),
"%ld %s", (long)hi->blen, hi->hc->msg[HM_RU]);
SourceSendMessage(hi->ws, hi->msgbuf);
return;
}
/*
* HTTPRequestDone
*/
static void
HTTPRequestDone(ios, rval, closure)
ChimeraStream ios;
ssize_t rval;
void *closure;
{
byte *rdata;
ssize_t rlen;
HTTPInfo **hip = (HTTPInfo **)closure;
HTTPInfo *hi = *hip;
if (rval == -1)
{
HTTPFailure(hi);
return;
}
if ((rdata = HTTPBuildRequest(hi, &rlen)) == NULL)
{
if (rlen == -1) HTTPFailure(hi);
else HTTPRead(hip, HTTPReadUnknown);
}
else
{
StreamWrite(hi->ios, rdata, rlen, HTTPRequestDone, hip);
}
return;
}
/*
* HTTPDestroy
*/
static void
HTTPDestroy(closure)
void *closure;
{
HTTPInfo **hip = (HTTPInfo **)closure;
HTTPDestroyInfo(*hip);
free_mem(hip);
return;
}
/*
* HTTPDestroyInfo
*/
static void
HTTPDestroyInfo(hi)
HTTPInfo *hi;
{
HTTPCancel(&hi);
if (hi->rmp != NULL) MPDestroy(hi->rmp);
if (hi->b != NULL) free_mem(hi->b);
if (hi->mh != NULL) MIMEDestroyHeader(hi->mh);
MPDestroy(hi->mp);
return;
}
/*
* HTTPCreateInfo
*/
static int
HTTPCreateInfo(hip, ws, wr, hc, up, pup, hp)
HTTPInfo **hip;
ChimeraSource ws;
ChimeraRequest *wr;
HTTPClass *hc;
URLParts *up, *pup;
HTTPPassword *hp;
{
char *hostname;
int port;
HTTPInfo *hi;
MemPool mp;
mp = MPCreate();
*hip = hi = (HTTPInfo *)MPCGet(mp, sizeof(HTTPInfo));
hi->mp = mp;
hi->hp = hp;
hi->rmp = MPCreate();
hi->ws = ws;
hi->wr = wr;
hi->cres = SourceToResources(ws);
hi->hc = hc;
hi->mh = MIMECreateHeader();
hi->up = URLDup(mp, up);
hi->url = URLMakeString(hi->mp, hi->up, true);
if (pup != NULL) hi->pup = URLDup(mp, pup);
else hi->pup = NULL;
if (hi->pup != NULL)
{
hostname = pup->hostname;
port = pup->port;
}
else
{
hostname = up->hostname;
port = up->port;
}
snprintf (hi->msgbuf, sizeof(hi->msgbuf),
"%s %s", hc->msg[HM_OPEN], hostname);
SourceSendMessage(hi->ws, hi->msgbuf);
hi->ios = StreamCreateINet(hi->cres, hostname, port == 0 ? 80:port);
if (hi->ios == NULL)
{
HTTPDestroyInfo(hi);
return(-1);
}
HTTPRequestDone(hi->ios, 0, (void *)hip);
return(0);
}
/*
* HTTPInit
*/
static void *
HTTPInit(ws, wr, class_closure)
ChimeraSource ws;
ChimeraRequest *wr;
void *class_closure;
{
HTTPClass *hc = (HTTPClass *)class_closure;
HTTPInfo **hip;
hip = (HTTPInfo **)alloc_mem(sizeof(HTTPInfo **));
if (HTTPCreateInfo(hip, ws, wr, hc, wr->up, wr->pup, NULL) == -1)
{
free_mem(hip);
return(NULL);
}
return(hip);
}
/*
* HTTPCancel
*
* Used to obliterate a connection. Use this when an error occurs which
* could cause a connection to get messed up or at least a pain to
* figure out.
*/
void
HTTPCancel(closure)
void *closure;
{
HTTPInfo *hi = *((HTTPInfo **)closure);
if (hi->wa != NULL)
{
AuthDestroy(hi->wa);
hi->wa = NULL;
}
if (hi->ios != NULL)
{
StreamDestroy(hi->ios);
hi->ios = NULL;
}
return;
}
/*
* HTTPClassDestroy
*/
static void
HTTPClassDestroy(closure)
void *closure;
{
HTTPClass *hc = (HTTPClass *)closure;
MPDestroy(hc->mp);
return;
}
/*
* HTTPGetData
*/
static void
HTTPGetData(closure, data, len, mh)
void *closure;
byte **data;
size_t *len;
MIMEHeader *mh;
{
HTTPInfo *hi = *((HTTPInfo **)closure);
*data = hi->b + hi->doff;
*len = hi->blen - hi->doff;
*mh = hi->mh;
return;
}
/*
* InitModule_HTTP
*/
void
InitModule_HTTP(cres)
ChimeraResources cres;
{
ChimeraSourceHooks ph;
HTTPClass *hc;
MemPool mp;
size_t tlen;
int i;
mp = MPCreate();
hc = (HTTPClass *)MPCGet(mp, sizeof(HTTPClass));
hc->mp = mp;
hc->passwords = GListCreateX(mp);
/* get the status messages and allocate a message work buffer */
for (i = 0; http_messages[i].name != NULL; i++)
{
if ((hc->msg[i] = ResourceGetString(cres, http_messages[0].name)) == NULL)
{
hc->msg[i] = http_messages[i].def;
}
if ((tlen = strlen(hc->msg[i])) > hc->mlen) hc->mlen = tlen;
}
memset(&ph, 0, sizeof(ph));
ph.class_closure = hc;
ph.name = "http";
ph.init = HTTPInit;
ph.destroy = HTTPDestroy;
ph.stop = HTTPCancel;
ph.getdata = HTTPGetData;
ph.class_destroy = HTTPClassDestroy;
SourceAddHooks(cres, &ph);
return;
}