blob: 7860cd91008e63eeafdd6f69b19db59e66876ee9 [file] [log] [blame]
#include <u.h>
#include <libc.h>
#include <httpd.h>
static char hstates[] = "nrewE";
static char hxfers[] = " x";
static int _hflush(Hio*, int, int);
int
hinit(Hio *h, int fd, int mode)
{
if(fd == -1 || mode != Hread && mode != Hwrite)
return -1;
h->hh = nil;
h->fd = fd;
h->seek = 0;
h->state = mode;
h->start = h->buf + 16; /* leave space for chunk length */
h->stop = h->pos = h->start;
if(mode == Hread){
h->bodylen = ~0UL;
*h->pos = '\0';
}else
h->stop = h->start + Hsize;
return 0;
}
int
hiserror(Hio *h)
{
return h->state == Herr;
}
int
hgetc(Hio *h)
{
uchar *p;
p = h->pos;
if(p < h->stop){
h->pos = p + 1;
return *p;
}
p -= UTFmax;
if(p < h->start)
p = h->start;
if(!hreadbuf(h, p) || h->pos == h->stop)
return -1;
return *h->pos++;
}
int
hungetc(Hio *h)
{
if(h->state == Hend)
h->state = Hread;
else if(h->state == Hread)
h->pos--;
if(h->pos < h->start || h->state != Hread){
h->state = Herr;
h->pos = h->stop;
return -1;
}
return 0;
}
/*
* fill the buffer, saving contents from vsave onwards.
* nothing is saved if vsave is nil.
* returns the beginning of the buffer.
*
* understands message body sizes and chunked transfer encoding
*/
void *
hreadbuf(Hio *h, void *vsave)
{
Hio *hh;
uchar *save;
int c, in, cpy, dpos;
save = vsave;
if(save && (save < h->start || save > h->stop)
|| h->state != Hread && h->state != Hend){
h->state = Herr;
h->pos = h->stop;
return nil;
}
dpos = 0;
if(save && h->pos > save)
dpos = h->pos - save;
cpy = 0;
if(save){
cpy = h->stop - save;
memmove(h->start, save, cpy);
}
h->seek += h->stop - h->start - cpy;
h->pos = h->start + dpos;
in = Hsize - cpy;
if(h->state == Hend)
in = 0;
else if(in > h->bodylen)
in = h->bodylen;
/*
* for chunked encoding, fill buffer,
* then read in new chunk length and wipe out that line
*/
hh = h->hh;
if(hh != nil){
if(!in && h->xferenc && h->state != Hend){
if(h->xferenc == 2){
c = hgetc(hh);
if(c == '\r')
c = hgetc(hh);
if(c != '\n'){
h->pos = h->stop;
h->state = Herr;
return nil;
}
}
h->xferenc = 2;
in = 0;
while((c = hgetc(hh)) != '\n'){
if(c >= '0' && c <= '9')
c -= '0';
else if(c >= 'a' && c <= 'f')
c -= 'a' - 10;
else if(c >= 'A' && c <= 'F')
c -= 'A' - 10;
else
break;
in = in * 16 + c;
}
while(c != '\n'){
if(c < 0){
h->pos = h->stop;
h->state = Herr;
return nil;
}
c = hgetc(hh);
}
h->bodylen = in;
in = Hsize - cpy;
if(in > h->bodylen)
in = h->bodylen;
}
if(in){
while(hh->pos + in > hh->stop){
if(hreadbuf(hh, hh->pos) == nil){
h->pos = h->stop;
h->state = Herr;
return nil;
}
}
memmove(h->start + cpy, hh->pos, in);
hh->pos += in;
}
}else if(in){
if((in = read(h->fd, h->start + cpy, in)) < 0){
h->state = Herr;
h->pos = h->stop;
return nil;
}
}
if(in == 0)
h->state = Hend;
h->bodylen -= in;
h->stop = h->start + cpy + in;
*h->stop = '\0';
if(h->pos == h->stop)
return nil;
return h->start;
}
int
hbuflen(Hio *h, void *p)
{
return h->stop - (uchar*)p;
}
/*
* prepare to receive a message body
* len is the content length (~0 => unspecified)
* te is the transfer encoding
* returns < 0 if setup failed
*/
Hio*
hbodypush(Hio *hh, ulong len, HFields *te)
{
Hio *h;
int xe;
if(hh->state != Hread)
return nil;
xe = 0;
if(te != nil){
if(te->params != nil || te->next != nil)
return nil;
if(cistrcmp(te->s, "chunked") == 0){
xe = 1;
len = 0;
}else if(cistrcmp(te->s, "identity") == 0){
;
}else
return nil;
}
h = malloc(sizeof *h);
if(h == nil)
return nil;
h->hh = hh;
h->fd = -1;
h->seek = 0;
h->state = Hread;
h->xferenc = xe;
h->start = h->buf + 16; /* leave space for chunk length */
h->stop = h->pos = h->start;
*h->pos = '\0';
h->bodylen = len;
return h;
}
/*
* dump the state of the io buffer into a string
*/
char *
hunload(Hio *h)
{
uchar *p, *t, *stop, *buf;
int ne, n, c;
stop = h->stop;
ne = 0;
for(p = h->pos; p < stop; p++){
c = *p;
if(c == 0x80)
ne++;
}
p = h->pos;
n = (stop - p) + ne + 3;
buf = mallocz(n, 1);
if(buf == nil)
return nil;
buf[0] = hstates[h->state];
buf[1] = hxfers[h->xferenc];
t = &buf[2];
for(; p < stop; p++){
c = *p;
if(c == 0 || c == 0x80){
*t++ = 0x80;
if(c == 0x80)
*t++ = 0x80;
}else
*t++ = c;
}
*t++ = '\0';
if(t != buf + n)
return nil;
return (char*)buf;
}
/*
* read the io buffer state from a string
*/
int
hload(Hio *h, char *buf)
{
uchar *p, *t, *stop;
char *s;
int c;
s = strchr(hstates, buf[0]);
if(s == nil)
return -1;
h->state = s - hstates;
s = strchr(hxfers, buf[1]);
if(s == nil)
return -1;
h->xferenc = s - hxfers;
t = h->start;
stop = t + Hsize;
for(p = (uchar*)&buf[2]; c = *p; p++){
if(c == 0x80){
if(p[1] != 0x80)
c = 0;
else
p++;
}
*t++ = c;
if(t >= stop)
return -1;
}
*t = '\0';
h->pos = h->start;
h->stop = t;
h->seek = 0;
return 0;
}
void
hclose(Hio *h)
{
if(h->fd >= 0){
if(h->state == Hwrite)
hxferenc(h, 0);
close(h->fd);
}
h->stop = h->pos = nil;
h->fd = -1;
}
/*
* flush the buffer and possibly change encoding modes
*/
int
hxferenc(Hio *h, int on)
{
if(h->xferenc && !on && h->pos != h->start)
hflush(h);
if(_hflush(h, 1, 0) < 0)
return -1;
h->xferenc = !!on;
return 0;
}
int
hputc(Hio *h, int c)
{
uchar *p;
p = h->pos;
if(p < h->stop){
h->pos = p + 1;
return *p = c;
}
if(hflush(h) < 0)
return -1;
return *h->pos++ = c;
}
static int
fmthflush(Fmt *f)
{
Hio *h;
h = f->farg;
h->pos = f->to;
if(hflush(h) < 0)
return 0;
f->stop = h->stop;
f->to = h->pos;
f->start = h->pos;
return 1;
}
int
hvprint(Hio *h, char *fmt, va_list args)
{
int n;
Fmt f;
f.runes = 0;
f.stop = h->stop;
f.to = h->pos;
f.start = h->pos;
f.flush = fmthflush;
f.farg = h;
f.nfmt = 0;
fmtlocaleinit(&f, nil, nil, nil);
n = fmtvprint(&f, fmt, args);
h->pos = f.to;
return n;
}
int
hprint(Hio *h, char *fmt, ...)
{
int n;
va_list arg;
va_start(arg, fmt);
n = hvprint(h, fmt, arg);
va_end(arg);
return n;
}
static int
_hflush(Hio *h, int force, int dolength)
{
uchar *s;
int w;
if(h->state != Hwrite){
h->state = Herr;
h->stop = h->pos;
return -1;
}
s = h->start;
w = h->pos - s;
if(w == 0 && !force)
return 0;
if(h->xferenc){
*--s = '\n';
*--s = '\r';
do{
*--s = "0123456789abcdef"[w & 0xf];
w >>= 4;
}while(w);
h->pos[0] = '\r';
h->pos[1] = '\n';
w = &h->pos[2] - s;
}
if(dolength)
fprint(h->fd, "Content-Length: %d\r\n\r\n", w);
if(write(h->fd, s, w) != w){
h->state = Herr;
h->stop = h->pos;
return -1;
}
h->seek += w;
h->pos = h->start;
return 0;
}
int
hflush(Hio *h)
{
return _hflush(h, 0, 0);
}
int
hlflush(Hio* h)
{
return _hflush(h, 0, 1);
}
int
hwrite(Hio *h, void *vbuf, int len)
{
uchar *buf;
int n, m;
buf = vbuf;
n = len;
if(n < 0 || h->state != Hwrite){
h->state = Herr;
h->stop = h->pos;
return -1;
}
if(h->pos + n >= h->stop){
if(h->start != h->pos)
if(hflush(h) < 0)
return -1;
while(h->pos + n >= h->stop){
m = h->stop - h->pos;
if(h->xferenc){
memmove(h->pos, buf, m);
h->pos += m;
if(hflush(h) < 0)
return -1;
}else{
if(write(h->fd, buf, m) != m){
h->state = Herr;
h->stop = h->pos;
return -1;
}
h->seek += m;
}
n -= m;
buf += m;
}
}
memmove(h->pos, buf, n);
h->pos += n;
return len;
}