char *htmlquoted(char *s) { /* * This routine converts a plain string into an html-quoted string */ static strbuffer_t *result = NULL; char *inp, *endp; char c; if (!result) result = newstrbuffer(4096); clearstrbuffer(result); inp = s; do { endp = inp + strcspn(inp, "\"&<> "); c = *endp; if (endp > inp) addtobufferraw(result, inp, endp-inp); switch (c) { case '"': addtobuffer(result, """); break; case '&': addtobuffer(result, "&"); break; case '<': addtobuffer(result, "<"); break; case '>': addtobuffer(result, ">"); break; case ' ': addtobuffer(result, " "); break; default: break; } inp = (c == '\0') ? NULL : endp+1; } while (inp); return STRBUF(result); }
enum conn_cbresult_t tcp_standard_callback(tcpconn_t *connection, enum conn_callback_t id, void *userdata) { int res = CONN_CBRESULT_OK; int n, advancestep; size_t used; time_t start, expire; int keysize; char *certsubject, *issuer, *fulltext; myconn_t *rec = (myconn_t *)userdata; dbgprintf("CB: %s\n", conn_callback_names[id]); switch (id) { case CONN_CB_CONNECT_START: /* Client mode: New outbound connection start */ break; case CONN_CB_CONNECT_FAILED: /* Client mode: New outbound connection failed */ rec->talkresult = TALK_CONN_FAILED; rec->textlog = newstrbuffer(0); addtobuffer(rec->textlog, strerror(connection->errcode)); conn_close_connection(connection, NULL); break; case CONN_CB_CONNECT_COMPLETE: /* Client mode: New outbound connection succeded */ rec->textlog = newstrbuffer(0); rec->talkresult = TALK_OK; /* Will change below if we fail later */ rec->readbufsz = USERBUFSZ; rec->readbuf = rec->readp = malloc(rec->readbufsz); *(rec->readbuf) = '\0'; rec->writebuf = rec->writep = malloc(USERBUFSZ); *(rec->writebuf) = '\0'; break; case CONN_CB_SSLHANDSHAKE_OK: /* Client/server mode: SSL handshake completed OK (peer certificate ready) */ certsubject = conn_peer_certificate(connection, &start, &expire, &keysize, &issuer, &fulltext); if (certsubject) { rec->peercertificate = certsubject; /* certsubject is malloc'ed by conn_peer_certificate */ rec->peercertificateissuer = issuer; /* ditto issuer */ rec->peercertificatestart = start; rec->peercertificateexpiry = expire; rec->peercertificatekeysize = keysize; rec->peercertificatedetails = fulltext; } if (strcasecmp(rec->dialog[rec->step], "CLOSE") == 0) conn_close_connection(connection, NULL); break; case CONN_CB_SSLHANDSHAKE_FAILED: /* Client/server mode: SSL handshake failed (connection will close) */ rec->talkresult = TALK_BADSSLHANDSHAKE; conn_close_connection(connection, NULL); break; case CONN_CB_READCHECK: /* Client/server mode: Check if application wants to read data */ if (!rec->dialog[rec->step]) res = CONN_CBRESULT_FAILED; else if (rec->istelnet > 0) res = CONN_CBRESULT_OK; else res = ((strncasecmp(rec->dialog[rec->step], "EXPECT:", 7) == 0) || (strncasecmp(rec->dialog[rec->step], "READ", 4) == 0)) ? CONN_CBRESULT_OK : CONN_CBRESULT_FAILED; break; case CONN_CB_READ: /* Client/server mode: Ready for application to read data w/ conn_read() */ /* Make sure we have some buffer space */ used = (rec->readp - rec->readbuf); if ((rec->readbufsz - used) < USERBUFSZ) { rec->readbufsz += USERBUFSZ; rec->readbuf = (char *)realloc(rec->readbuf, rec->readbufsz); rec->readp = rec->readbuf + used; } /* Read the data */ n = conn_read(connection, rec->readp, (rec->readbufsz - used - 1)); if (n <= 0) return CONN_CBRESULT_OK; /* n == 0 happens during SSL handshakes, n < 0 means connection will close */ rec->bytesread += n; /* Process data for some protocols */ if (rec->istelnet) n = telnet_datahandler(rec, n); if (rec->talkprotocol == TALK_PROTO_HTTP) n = http_datahandler(rec, n, 0, &advancestep); /* Save the data */ if (n > 0) { *(rec->readp + n) = '\0'; if (rec->talkprotocol == TALK_PROTO_PLAIN) addtobuffer(rec->textlog, rec->readp); rec->readp += n; } /* See how the dialog is progressing */ if (strncasecmp(rec->dialog[rec->step], "EXPECT:", 7) == 0) { int explen, expstart; expstart = 7 + strspn(rec->dialog[rec->step] + 7, " \t"); explen = strlen(rec->dialog[rec->step] + expstart); if ((n < explen) && (strncasecmp(rec->readbuf, rec->dialog[rec->step] + expstart, n) == 0)) { /* * Got the right data so far, but not the complete amount. * Do nothing, we'll just keep reading until we have all of the data */ } else if (strncasecmp(rec->readbuf, rec->dialog[rec->step] + expstart, explen) == 0) { /* Got the expected data, go to next step */ rec->step++; rec->readp = rec->readbuf; *rec->readp = '\0'; } else { /* Got some unexpected data, give up */ rec->talkresult = TALK_BADDATA; conn_close_connection(connection, NULL); } } else if (strcasecmp(rec->dialog[rec->step], "READALL") == 0) { /* No need to save the data twice (we store it in rec->textlog), so reset the readp to start of our readbuffer */ rec->readp = rec->readbuf; *(rec->readp) = '\0'; if (advancestep) rec->step++; } else if (strcasecmp(rec->dialog[rec->step], "READ") == 0) { rec->step++; } /* See if we have reached a point where we switch to TLS mode */ if (rec->dialog[rec->step] && (strcasecmp(rec->dialog[rec->step], "STARTTLS") == 0)) { res = CONN_CBRESULT_STARTTLS; rec->step++; } /* See if we're done */ if (strcasecmp(rec->dialog[rec->step], "CLOSE") == 0) conn_close_connection(connection, NULL); break; case CONN_CB_WRITECHECK: /* Client/server mode: Check if application wants to write data */ if (!rec->dialog[rec->step]) res = CONN_CBRESULT_FAILED; else if (rec->istelnet != 0) res = (rec->istelnet < 0) ? CONN_CBRESULT_OK : CONN_CBRESULT_FAILED; else { if ((*rec->writep == '\0') && (strncasecmp(rec->dialog[rec->step], "SEND:", 5) == 0)) { char *sendstart = rec->dialog[rec->step] + 5; sendstart += strspn(sendstart, " \t"); strcpy(rec->writebuf, sendstart); rec->writep = rec->writebuf; } res = (*rec->writep != '\0') ? CONN_CBRESULT_OK : CONN_CBRESULT_FAILED; } break; case CONN_CB_WRITE: /* Client/server mode: Ready for application to write data w/ conn_write() */ if (rec->istelnet < 0) { n = conn_write(connection, rec->writep, -(rec->istelnet)); if (n <= 0) return CONN_CBRESULT_OK; /* n == 0 happens during SSL handshakes, n < 0 means connection will close */ rec->writep += n; rec->istelnet += n; if (rec->istelnet == 0) rec->istelnet = 1; } else { n = conn_write(connection, rec->writep, strlen(rec->writep)); if (n <= 0) return CONN_CBRESULT_OK; /* n == 0 happens during SSL handshakes, n < 0 means connection will close */ if (n > 0) { rec->byteswritten += n; switch (rec->talkprotocol) { case TALK_PROTO_PLAIN: case TALK_PROTO_HTTP: addtobufferraw(rec->textlog, rec->writep, n); break; default: break; } rec->writep += n; if (*rec->writep == '\0') { rec->step++; /* Next step */ if (last_write_step(rec)) { conn_close_connection(connection, "w"); } } } } /* See if we have reached a point where we switch to TLS mode */ if (rec->dialog[rec->step] && (strcasecmp(rec->dialog[rec->step], "STARTTLS") == 0)) { res = CONN_CBRESULT_STARTTLS; rec->step++; } /* See if we're done */ if (strcasecmp(rec->dialog[rec->step], "CLOSE") == 0) conn_close_connection(connection, NULL); break; case CONN_CB_TIMEOUT: rec->talkresult = TALK_CONN_TIMEOUT; conn_close_connection(connection, NULL); break; case CONN_CB_CLOSED: /* Client/server mode: Connection has been closed */ /* See if we need to report an error from closing the connection unexpectedly */ if ((rec->talkresult == TALK_OK) && rec->dialog[rec->step]) { /* * We should only close if * - we hit a CLOSE command * - we hit the end of the command list (NULL dialog step) * - peer disconnects during a READ step * So if the current step is NOT a CLOSE or a READALL step, then * the close was unexpected - so flag it as an error. */ if ((strcasecmp(rec->dialog[rec->step], "CLOSE") != 0) && (strcasecmp(rec->dialog[rec->step], "READALL") != 0)) rec->talkresult = TALK_INTERRUPTED; } rec->elapsedus = connection->elapsedus; return 0; case CONN_CB_CLEANUP: /* Client/server mode: Connection cleanup */ if (rec->readbuf) xfree(rec->readbuf); if (rec->writebuf) xfree(rec->writebuf); connection->userdata = NULL; test_is_done(rec); return 0; default: break; } return res; }
static int http_datahandler(myconn_t *rec, int iobytes, int startoffset, int *advancestep) { char *endofhdrs; int httpmajorver, httpminorver; char *xferencoding; int len = iobytes; char *bol, *buf; int hdrbytes, bodybytes = 0, bodyoffset, initialhdrbuflen, n; *advancestep = 0; switch (rec->httpdatastate) { case HTTPDATA_HEADERS: initialhdrbuflen = STRBUFLEN(rec->httpheaders); addtobufferraw(rec->httpheaders, rec->readbuf+startoffset, (iobytes - startoffset)); check_for_endofheaders: /* * Now see if we have the end-of-headers delimiter. * This SHOULD be <cr><lf><cr><lf>, but RFC 2616 says * you SHOULD recognize just plain <lf><lf>. * So try the second form, if the first one is not there. */ endofhdrs = strstr(STRBUF(rec->httpheaders), "\r\n\r\n"); if (endofhdrs) { endofhdrs += 4; } else { endofhdrs = strstr(STRBUF(rec->httpheaders), "\n\n"); if (endofhdrs) { endofhdrs += 2; } } if (!endofhdrs) { /* No more to do for now, but pass the databyte-count back to the caller for further processing. */ return iobytes; } else { /* Chop the non-header section of data from the headers */ strbufferchop(rec->httpheaders, strlen(endofhdrs)); } /* We have an end-of-header delimiter, but it could be just a "100 Continue" response */ sscanf(STRBUF(rec->httpheaders), "HTTP/%d.%d %d", &httpmajorver, &httpminorver, &rec->httpstatus); if (rec->httpstatus == 100) { /* * It's a "100" continue-status. * Just drop this set of headers, and re-do the end-of-headers check. */ strbuffer_t *newhdrbuf = newstrbuffer(0); addtobuffer(newhdrbuf, endofhdrs); freestrbuffer(rec->httpheaders); rec->httpheaders = newhdrbuf; goto check_for_endofheaders; } /* Have all the http headers now */ rec->httpdatastate = HTTPDATA_BODY; /* * Find the "Transfer-encoding: " header (if there is one) to see if the transfer uses chunks, * and grab "Content-Length:" to get the length of the body. */ xferencoding = NULL; bol = STRBUF(rec->httpheaders); while (bol && !xferencoding && !rec->httpcontentleft) { if (strncasecmp(bol, "Transfer-encoding:", 18) == 0) { bol += 18; bol += strspn(bol, " "); xferencoding = bol; } else if (strncasecmp(bol, "Content-Length:", 15) == 0) { bol += 15; bol += strspn(bol, " "); rec->httpcontentleft = atoi(bol); } else { bol = strchr(bol, '\n'); if (bol) bol++; } } if (xferencoding && (strncasecmp(xferencoding, "chunked", 7) == 0)) rec->httpchunkstate = HTTP_CHUNK_INIT; else { rec->httpchunkstate = (rec->httpcontentleft > 0) ? HTTP_CHUNK_NOTCHUNKED : HTTP_CHUNK_NOTCHUNKED_NOCLEN; } /* Done with all the http header processing. Call ourselves to handle any remaining data we got after the headers */ /* * To figure out how this works, here is the layout of rec->httpheaders. The first * (initialhdrbuflen) part is what we had before this call to http_datahandler, the * last (iobytes) part has been copied over from the current rec->buf. * endofhdrs points into rec->httpheaders. bodyoffset and bodybytes are relative, * so even though the body data is in rec->buf and NOT in rec->httpheaders, we can * calculate the offset and length of the body data. * * endofhdrs * ! * !-----------------------------!----------------------------! * * <......initialhdrbuflen.......> * <.........iobytes............> * <...............hdrbytes.....................> * <..bodyoffset..> * <..bodybytes..> */ hdrbytes = (endofhdrs - STRBUF(rec->httpheaders)); bodyoffset = hdrbytes - initialhdrbuflen; bodybytes = iobytes - bodyoffset; http_datahandler(rec, bodybytes, bodyoffset, advancestep); break; case HTTPDATA_BODY: buf = rec->readbuf+startoffset; while (len > 0) { bodybytes = 0; switch (rec->httpchunkstate) { case HTTP_CHUNK_NOTCHUNKED: case HTTP_CHUNK_NOTCHUNKED_NOCLEN: bodybytes = len; break; case HTTP_CHUNK_INIT: /* We're about to pick up a chunk length */ rec->httpleftinchunk = 0; rec->httpchunkstate = HTTP_CHUNK_GETLEN; break; case HTTP_CHUNK_GETLEN: /* We are collecting the length of the chunk */ n = hexvalue(*buf); if (n == -1) { rec->httpchunkstate = HTTP_CHUNK_SKIPLENCR; } else { rec->httpleftinchunk = rec->httpleftinchunk*16 + n; buf++; len--; } break; case HTTP_CHUNK_SKIPLENCR: /* We've got the length, now skip to the next LF */ if (*buf == '\n') { buf++; len--; rec->httpchunkstate = ((rec->httpleftinchunk > 0) ? HTTP_CHUNK_DATA : HTTP_CHUNK_NOMORE); } else if ((*buf == '\r') || (*buf == ' ')) { buf++; len--; } else { errprintf("Yikes - strange data following chunk len. Saw a '%c'\n", *buf); buf++; len--; } break; case HTTP_CHUNK_DATA: /* Passing off the data */ bodybytes = (len > rec->httpleftinchunk) ? rec->httpleftinchunk : len; rec->httpleftinchunk -= bodybytes; if (rec->httpleftinchunk == 0) rec->httpchunkstate = HTTP_CHUNK_SKIPENDCR; break; case HTTP_CHUNK_SKIPENDCR: /* Skip CR/LF after a chunk */ if (*buf == '\n') { buf++; len--; rec->httpchunkstate = HTTP_CHUNK_DONE; } else if (*buf == '\r') { buf++; len--; } else { errprintf("Yikes - strange data following chunk data. Saw a '%c'\n", *buf); buf++; len--; } break; case HTTP_CHUNK_DONE: /* One chunk is done, continue with the next */ rec->httpchunkstate = HTTP_CHUNK_GETLEN; break; case HTTP_CHUNK_NOMORE: /* All chunks done. Skip the rest (trailers) */ len = 0; break; } /* bodybytes holds the number of bytes data from buf that should go to userspace */ if (bodybytes > 0) { addtobufferraw(rec->httpbody, buf, bodybytes); buf += bodybytes; len -= bodybytes; if ((rec->httpcontentleft > 0) && (rec->httpcontentleft >= bodybytes)) rec->httpcontentleft -= bodybytes; dbgprintf("HTTP bodybytes %d, %d bytes left\n", bodybytes, rec->httpcontentleft); } } /* Done processing body content. Now see if we have all of it - if we do, then proceed to next step. */ dbgprintf("http chunkstate: %d\n",rec->httpchunkstate); switch (rec->httpchunkstate) { case HTTP_CHUNK_NOTCHUNKED: if (rec->httpcontentleft <= 0) *advancestep = 1; break; case HTTP_CHUNK_NOTCHUNKED_NOCLEN: /* We have no content-length: header, so keep going until we do two NULL-reads */ if ((rec->httplastbodyread == 0) && (bodybytes == 0)) *advancestep = 1; else rec->httplastbodyread = bodybytes; break; case HTTP_CHUNK_NOMORE: *advancestep = 1; break; default: break; } break; } return iobytes; }