From 133c7f17b11564868dbe43995392c97b20505849 Mon Sep 17 00:00:00 2001 From: Stephen Heumann Date: Wed, 1 Aug 2018 00:05:19 -0500 Subject: [PATCH] Add code to parse status line and headers of an HTTP response to our request. --- http.c | 244 +++++++++++++++++++++++++++++++++++++++++++++++++++- http.h | 11 +++ main.c | 38 ++------ session.h | 13 +++ seturl.c | 2 +- urlparser.c | 5 +- 6 files changed, 278 insertions(+), 35 deletions(-) diff --git a/http.c b/http.c index 165a4c7..353347c 100644 --- a/http.c +++ b/http.c @@ -3,9 +3,34 @@ #include #include +#include #include +#include +#include +#include +#include +#include #include "session.h" #include "http.h" +#include "tcpconnection.h" +#include "strcasecmp.h" + +#define buffTypePointer 0x0000 /* For TCPIPReadTCP() */ +#define buffTypeHandle 0x0001 +#define buffTypeNewHandle 0x0002 + +#define HTTP_RESPONSE_TIMEOUT 15 /* seconds to wait for response headers */ + +#define MAX_REDIRECTS 5 + +enum ResponseHeader { + UNKNOWN_HEADER = 0, + CONTENT_RANGE, + CONTENT_LENGTH, + TRANSFER_ENCODING, + CONTENT_ENCODING, + LOCATION, +}; Boolean BuildHTTPRequest(Session *sess, char *resourceStr) { int sizeNeeded = 0; @@ -49,5 +74,222 @@ Boolean BuildHTTPRequest(Session *sess, char *resourceStr) { } void UpdateRequestRange(Session *sess, unsigned long start, unsigned long end) { - snprintf(sess->httpRequestRange, 10+1+10+5, "%lu-%lu\r\n\r\n", start, end); + int count = + snprintf(sess->httpRequestRange, 10+1+10+5, "%lu-%lu\r\n\r\n", start, end); + + sess->httpRequestLen = sess->httpRequestRange - sess->httpRequest + count; +} + +#pragma debug -1 + +enum RequestResult DoHTTPRequest(Session *sess) { +top:; + rlrBuff rlrBuff = {0}; + Word tcpError; + Boolean wantRedirect = FALSE; + enum RequestResult result; + + /* Send out request */ + result = NETWORK_ERROR; + tcpError = TCPIPWriteTCP(sess->ipid, sess->httpRequest, + sess->httpRequestLen, TRUE, FALSE); + if (tcpError || toolerror()) + goto errorReturn; + + /* Get response status line & headers */ + LongWord startTime = GetTick(); + do { + TCPIPPoll(); + tcpError = TCPIPReadLineTCP(sess->ipid, + (void*)((LongWord)"\p\r\n\r\n" | 0x80000000), + buffTypeNewHandle, (Ref)NULL, + 0xFFFFFF, &rlrBuff); + if (tcpError || toolerror()) + goto errorReturn; + } while (rlrBuff.rlrBuffCount == 0 + && GetTick() - startTime < HTTP_RESPONSE_TIMEOUT * 60); + + result = NO_RESPONSE; + if (!rlrBuff.rlrIsDataFlag) + goto errorReturn; + + result = INVALID_RESPONSE; + /* Response must be at least long enough for a status line & final CRLF */ + if (rlrBuff.rlrBuffCount < 8+1+3+1+2+2) + goto errorReturn; + + HLock(rlrBuff.rlrBuffHandle); + + char *response = *rlrBuff.rlrBuffHandle; + char *responseEnd = response + rlrBuff.rlrBuffCount; + /* Make response a C-string. Specifically, it will end "CR LF NUL NUL". */ + response[rlrBuff.rlrBuffCount - 2] = '\0'; + response[rlrBuff.rlrBuffCount - 1] = '\0'; + + /* Parse status line of HTTP response */ + char *endPtr; + + if (strncmp(response, "HTTP/1.", 7) != 0) + goto errorReturn; + response += 7; + if (!(*response >= '1' && *response <= '9')) + goto errorReturn; + response += 1; + if (*response != ' ') + goto errorReturn; + response += 1; + + errno = 0; + sess->responseCode = strtoul(response, &endPtr, 10); + if (errno || sess->responseCode > 999 || endPtr != response + 3) + goto errorReturn; + response += 3; + + if (*response != ' ') + goto errorReturn; + response++; + + while (*response != '\r' && response < responseEnd) + response++; + response++; + + if (*response != '\n' && response < responseEnd) + goto errorReturn; + response++; + + + switch(sess->responseCode) { + /* "Partial content" response, as expected */ + case 206: + break; + + /* Redirect responses */ + case 300: case 301: case 302: case 307: case 308: + if (sess->redirectCount < MAX_REDIRECTS) { + sess->redirectCount++; + wantRedirect = TRUE; + break; + } else { + result = EXCESSIVE_REDIRECTS; + goto errorReturn; + } + + default: + result = UNSUPPORTED_RESPONSE; + goto errorReturn; + } + + /* Parse response headers */ + sess->rangeStart = 0; + sess->rangeEnd = 0; + sess->totalLength = 0; + sess->contentLength = 0; + + result = UNSUPPORTED_HEADER_VALUE; + + while (response < responseEnd - 2) { + enum ResponseHeader header = UNKNOWN_HEADER; + + if (strncasecmp(response, "Content-Range:", 14) == 0) { + response += 14; + header = CONTENT_RANGE; + } else if (strncasecmp(response, "Content-Length:", 15) == 0) { + response += 15; + header = CONTENT_LENGTH; + } else if (strncasecmp(response, "Transfer-Encoding:", 18) == 0) { + response += 18; + header = TRANSFER_ENCODING; + } else if (strncasecmp(response, "Content-Encoding:", 17) == 0) { + response += 17; + header = CONTENT_ENCODING; + } else if (strncasecmp(response, "Location:", 9) == 0) { + response += 9; + header = LOCATION; + } + + while ((*response == ' ' || *response == '\t') && response < responseEnd) + response++; + + switch (header) { + case CONTENT_RANGE: + if (strncasecmp(response, "bytes ", 6) != 0) + goto errorReturn; + response += 6; + + errno = 0; + sess->rangeStart = strtoul(response, &response, 10); + if (errno) + goto errorReturn; + if (*response != '-') + goto errorReturn; + response++; + + sess->rangeEnd = strtoul(response, &response, 10); + if (errno || sess->rangeEnd == 0) + goto errorReturn; + if (*response != '/') + goto errorReturn; + response++; + + /* require numeric total length, not indeterminate '*' */ + sess->totalLength = strtoul(response, &response, 10); + if (errno || sess->totalLength == 0) + goto errorReturn; + break; + + case CONTENT_LENGTH: + errno = 0; + sess->contentLength = strtoul(response, &endPtr, 10); + if (errno || sess->contentLength == 0) + goto errorReturn; + response = endPtr; + break; + + case CONTENT_ENCODING: + case TRANSFER_ENCODING: + if (strcasecmp(response, "identity") == 0) { + response += 8; + } else { + goto errorReturn; + } + break; + + case LOCATION: /*TODO*/ ; + + /* Unknown headers: ignored */ + case UNKNOWN_HEADER: + default: + while (*response != '\r' && response < responseEnd) + response++; + break; + } + + while ((*response == ' ' || *response == '\t') && response < responseEnd) + response++; + + if (*response != '\r') + goto errorReturn; + response++; + if (*response != '\n') + goto errorReturn; + response++; + } + + /* Wanted redirect, but to Location header found */ + if (wantRedirect) { + result = UNSUPPORTED_RESPONSE; + goto errorReturn; + } + + DisposeHandle(rlrBuff.rlrBuffHandle); + return REQUEST_SUCCESSFUL; + +errorReturn: + if (rlrBuff.rlrBuffHandle != NULL) { + DisposeHandle(rlrBuff.rlrBuffHandle); + } + + /* Error condition on this TCP connection means it can't be reused. */ + EndTCPConnection(sess); + return result; } diff --git a/http.h b/http.h index 5a6af25..042b8ba 100644 --- a/http.h +++ b/http.h @@ -4,7 +4,18 @@ #include #include "session.h" +enum RequestResult { + REQUEST_SUCCESSFUL = 0, + NETWORK_ERROR, + NO_RESPONSE, + INVALID_RESPONSE, + EXCESSIVE_REDIRECTS, + UNSUPPORTED_RESPONSE, + UNSUPPORTED_HEADER_VALUE, +}; + Boolean BuildHTTPRequest(Session *sess, char *resourceStr); void UpdateRequestRange(Session *sess, unsigned long start, unsigned long end); +enum RequestResult DoHTTPRequest(Session *sess); #endif \ No newline at end of file diff --git a/main.c b/main.c index 00b63d7..a81ed71 100644 --- a/main.c +++ b/main.c @@ -11,18 +11,12 @@ #include "seturl.h" #include "hostname.h" -#define buffTypePointer 0x0000 /* For TCPIPReadTCP() */ -#define buffTypeHandle 0x0001 -#define buffTypeNewHandle 0x0002 - /* http://archive.org/download/a2gs_System_1.0_1986_Apple_FW/System_1.0_1986_Apple_FW.2mg redirects to: http://ia800505.us.archive.org/16/items/a2gs_System_1.0_1986_Apple_FW/System_1.0_1986_Apple_FW.2mg */ -static char delimitStr[] = "\p\r\n\r\n"; - char *defaultURL = "http://ia800505.us.archive.org/16/items/a2gs_System_1.0_1986_Apple_FW/System_1.0_1986_Apple_FW.2mg"; @@ -72,32 +66,16 @@ int main(int argc, char **argv) { goto exit; } - TCPIPWriteTCP(sess.ipid, sess.httpRequest, strlen(sess.httpRequest), TRUE, FALSE); + enum RequestResult requestResult = DoHTTPRequest(&sess); + printf("RequestResult %i\n", requestResult); - rlrBuff rlrBuff = {0}; - Word tcpError; - do { - TCPIPPoll(); - tcpError = TCPIPReadLineTCP(sess.ipid, - (void*)((LongWord)delimitStr | 0x80000000), - buffTypeNewHandle, (Ref)NULL, - 0xFFFFFF, &rlrBuff); - if (tcpError || toolerror()) { - printf("tcpError = %u, toolerror = %u\n", tcpError, toolerror()); - break; - } - } while (rlrBuff.rlrBuffCount == 0); - - printf("Response:\n"); - printf("=========\n"); - for (int i = 0; i < rlrBuff.rlrBuffCount; i++) { - char ch = ((char*)*(rlrBuff.rlrBuffHandle))[i]; - if (ch != '\r') - putchar(ch); + if (requestResult == REQUEST_SUCCESSFUL) { + printf("rangeStart = %lu\n", sess.rangeStart); + printf("rangeEnd = %lu\n", sess.rangeEnd); + printf("totalLength = %lu\n", sess.totalLength); + printf("contentLength = %lu\n", sess.contentLength); } - printf("=========\n"); - printf("Response size = %lu\n", rlrBuff.rlrBuffCount); - + EndTCPConnection(&sess); exit: diff --git a/session.h b/session.h index c219352..527da7e 100644 --- a/session.h +++ b/session.h @@ -20,6 +20,11 @@ typedef struct Session { char *httpRequest; /* Pointer to the range part within httpRequest */ char *httpRequestRange; + /* Length of HTTP request */ + LongWord httpRequestLen; + + /* HTTP response code */ + unsigned long responseCode; /* IP address and TCP port of host */ LongWord ipAddr; @@ -29,6 +34,14 @@ typedef struct Session { char hostName[257]; /* Time (GetTick) of last DNS lookup */ LongWord dnsTime; + + /* Number of redirects followed */ + Word redirectCount; + + /* Values reported by server in Content-Range header */ + LongWord rangeStart, rangeEnd, totalLength; + /* Value reported by server in Content-Length header */ + LongWord contentLength; } Session; #endif diff --git a/seturl.c b/seturl.c index 62aff0c..ba8b237 100644 --- a/seturl.c +++ b/seturl.c @@ -21,7 +21,7 @@ SetURL(Session *sess, char *url, Boolean permissive, Boolean partialOK) { } for(unsigned int i = 0; url[i] != '\0'; i++) { - if (url[i] <= ' ' && url[i] >= 0) { + if ((unsigned char)url[i] <= ' ') { return INVALID_CHARACTER_IN_URL; } } diff --git a/urlparser.c b/urlparser.c index 7482682..82d87ef 100644 --- a/urlparser.c +++ b/urlparser.c @@ -25,8 +25,8 @@ URLParts ParseURL(char *url) { /* Detect relative URL (absolute-path reference only) */ if (url[0] == '/') { if (url[1] != '/') { - url++; - goto pathPart; + urlParts.path = url + 1; + return urlParts; } else { urlParts.errorFound = 1; return urlParts; @@ -90,7 +90,6 @@ URLParts ParseURL(char *url) { urlParts.port = sep + 1; } -pathPart: return urlParts; }