+ i = head->noheaders++;
+ head->headers = srealloc(head->headers, sizeof(*head->headers) * head->noheaders);
+ head->headers[i] = smalloc(sizeof(*head->headers[i]) * 2);
+ head->headers[i][0] = sstrdup(name);
+ head->headers[i][1] = sstrdup(val);
+}
+
+void headrmheader(struct hthead *head, const char *name)
+{
+ int i;
+
+ for(i = 0; i < head->noheaders; i++) {
+ if(!strcasecmp(head->headers[i][0], name)) {
+ free(head->headers[i][0]);
+ free(head->headers[i][1]);
+ free(head->headers[i]);
+ memmove(head->headers + i, head->headers + i + 1, sizeof(head->headers) * (--head->noheaders - i));
+ return;
+ }
+ }
+}
+
+int writeresp(FILE *out, struct hthead *resp)
+{
+ int i;
+
+ if(fprintf(out, "%s %i %s\r\n", resp->ver, resp->code, resp->msg) < 0)
+ return(-1);
+ for(i = 0; i < resp->noheaders; i++) {
+ if(fprintf(out, "%s: %s\r\n", resp->headers[i][0], resp->headers[i][1]) < 0)
+ return(-1);
+ }
+ return(0);
+}
+
+int writerespb(struct bufio *out, struct hthead *resp)
+{
+ int i;
+
+ if(bioprintf(out, "%s %i %s\r\n", resp->ver, resp->code, resp->msg) < 0)
+ return(-1);
+ for(i = 0; i < resp->noheaders; i++) {
+ if(bioprintf(out, "%s: %s\r\n", resp->headers[i][0], resp->headers[i][1]) < 0)
+ return(-1);
+ }
+ return(0);
+}
+
+int sendreq2(int sock, struct hthead *req, int fd, int flags)
+{
+ int ret, i;
+ struct charbuf buf;
+
+ bufinit(buf);
+ bufcatstr2(buf, req->method);
+ bufcatstr2(buf, req->url);
+ bufcatstr2(buf, req->ver);
+ bufcatstr2(buf, req->rest);
+ for(i = 0; i < req->noheaders; i++) {
+ bufcatstr2(buf, req->headers[i][0]);
+ bufcatstr2(buf, req->headers[i][1]);
+ }
+ bufcatstr2(buf, "");
+ ret = sendfd2(sock, fd, buf.b, buf.d, flags);
+ buffree(buf);
+ if(ret < 0)
+ return(-1);
+ else
+ return(0);
+}
+
+int sendreq(int sock, struct hthead *req, int fd)
+{
+ return(sendreq2(sock, req, fd, MSG_NOSIGNAL));
+}
+
+int recvreq(int sock, struct hthead **reqp)
+{
+ int fd;
+ struct charbuf buf;
+ char *p;
+ size_t l;
+ char *name, *val;
+ struct hthead *req;
+
+ if((fd = recvfd(sock, &buf.b, &buf.d)) < 0) {
+ return(-1);
+ }
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ buf.s = buf.d;
+ p = buf.b;
+ l = buf.d;
+
+ *reqp = omalloc(req);
+ if((req->method = sstrdup(decstr(&p, &l))) == NULL)
+ goto fail;
+ if((req->url = sstrdup(decstr(&p, &l))) == NULL)
+ goto fail;
+ if((req->ver = sstrdup(decstr(&p, &l))) == NULL)
+ goto fail;
+ if((req->rest = sstrdup(decstr(&p, &l))) == NULL)
+ goto fail;
+
+ while(1) {
+ if(!*(name = decstr(&p, &l)))
+ break;
+ val = decstr(&p, &l);
+ headappheader(req, name, val);
+ }
+
+ buffree(buf);
+ return(fd);
+
+fail:
+ close(fd);
+ freehthead(req);
+ errno = EPROTO;
+ return(-1);
+}
+
+char *unquoteurl(char *in)
+{
+ struct charbuf buf;
+ char *p;
+ int c;
+
+ bufinit(buf);
+ p = in;
+ while(*p) {
+ if(*p == '%') {
+ if(!p[1] || !p[2])
+ goto fail;
+ c = 0;
+ if((p[1] >= '0') && (p[1] <= '9')) c |= (p[1] - '0') << 4;
+ else if((p[1] >= 'a') && (p[1] <= 'f')) c |= (p[1] - 'a' + 10) << 4;
+ else if((p[1] >= 'A') && (p[1] <= 'F')) c |= (p[1] - 'A' + 10) << 4;
+ else goto fail;
+ if((p[2] >= '0') && (p[2] <= '9')) c |= (p[2] - '0');
+ else if((p[2] >= 'a') && (p[2] <= 'f')) c |= (p[2] - 'a' + 10);
+ else if((p[2] >= 'A') && (p[2] <= 'F')) c |= (p[2] - 'A' + 10);
+ else goto fail;
+ bufadd(buf, c);
+ p += 3;
+ } else {
+ bufadd(buf, *(p++));
+ }
+ }
+ bufadd(buf, 0);
+ return(buf.b);
+fail:
+ buffree(buf);
+ return(NULL);