diff --git a/include/httpcgi.h b/include/httpcgi.h index 305f491..e85948c 100644 --- a/include/httpcgi.h +++ b/include/httpcgi.h @@ -116,7 +116,7 @@ struct httpc { double end; /* 38 end time in seconds */ UCHAR rdw; /* 40 RDW option */ - UCHAR unused; /* 41 available */ + UCHAR chunked; /* 41 chunked transfer encoding */ short resp; /* 42 response code */ CRED *cred; /* 44 client credential */ UFS *ufs; /* 48 UFS handle (opaque) */ @@ -125,7 +125,7 @@ struct httpc { UCHAR ssi; /* 50 Server Side Include enable */ UCHAR ssilevel; /* 51 SSI processing level */ #define SSI_LEVEL_MAX 10 /* ... max SSI processing level */ - UCHAR unused1; /* 52 available */ + UCHAR content_length_set; /* 52 Content-Length was sent */ UCHAR unused2; /* 53 available */ unsigned unused3; /* 54 available */ diff --git a/include/httpd.h b/include/httpd.h index 5b96f82..c3ca5d3 100644 --- a/include/httpd.h +++ b/include/httpd.h @@ -200,7 +200,7 @@ struct httpc { double end; /* 38 end time in seconds */ UCHAR rdw; /* 40 RDW option */ - UCHAR unused; /* 41 available */ + UCHAR chunked; /* 41 chunked transfer encoding */ short resp; /* 42 response code */ CRED *cred; /* 44 client credential */ UFS *ufs; /* 48 UFS handle */ @@ -209,7 +209,7 @@ struct httpc { UCHAR ssi; /* 50 Server Side Include enable */ UCHAR ssilevel; /* 51 SSI processing level */ #define SSI_LEVEL_MAX 10 /* ... max SSI processing levele*/ - UCHAR unused1; /* 52 available */ + UCHAR content_length_set; /* 52 Content-Length was sent */ UCHAR unused2; /* 53 available */ unsigned unused3; /* 54 available */ diff --git a/src/httpdone.c b/src/httpdone.c index b2e9ca5..3f7c4ea 100644 --- a/src/httpdone.c +++ b/src/httpdone.c @@ -21,6 +21,19 @@ httpdone(HTTPC *httpc) ufs_fclose(&httpc->ufp); } + /* send terminating chunk for chunked transfer encoding */ + if (httpc->chunked) { + /* "0\r\n\r\n" in ASCII — disable framing first */ + UCHAR term[5]; + httpc->chunked = 0; + term[0] = 0x30; /* '0' */ + term[1] = 0x0D; /* CR */ + term[2] = 0x0A; /* LF */ + term[3] = 0x0D; /* CR */ + term[4] = 0x0A; /* LF */ + http_send(httpc, term, 5); + } + httpsecs(&httpc->end); quit: diff --git a/src/httpget.c b/src/httpget.c index 25b7328..9aea70e 100644 --- a/src/httpget.c +++ b/src/httpget.c @@ -16,6 +16,7 @@ httpget(HTTPC *httpc) { int rc = 0; UCHAR *path; + UCHAR *open_path = NULL; const HTTPM *mime; FILE *fp; int len; @@ -38,6 +39,7 @@ httpget(HTTPC *httpc) /* try to open path from UFS */ mime = http_mime(path); + open_path = path; fp = http_open(httpc, path, mime); if (fp || httpc->ufp) goto okay; @@ -47,11 +49,13 @@ httpget(HTTPC *httpc) memcpy(buf, path, len); strcpy(&buf[len], "index.html"); mime = http_mime(buf); + open_path = buf; fp = http_open(httpc, buf, mime); if (fp || httpc->ufp) goto okay; strcpy(&buf[len], "default.html"); mime = http_mime(buf); + open_path = buf; fp = http_open(httpc, buf, mime); if (fp || httpc->ufp) goto okay; } @@ -80,8 +84,49 @@ httpget(HTTPC *httpc) #endif rc = http_printf(httpc, "Content-Type: %s\r\n", mime->type); if (rc) goto die; - rc = http_printf(httpc, "\r\n"); - if (rc) goto die; + + /* Content-Length for static UFS files, chunked for SSI */ + if (!httpc->ssi && httpc->ufp) { + UFS *ufs = http_get_ufs(httpc); + if (ufs) { + UFSDLIST st; + UCHAR ufspath[256]; + const char *dr = httpc->httpd->docroot; + if (dr[0]) { + snprintf((char *)ufspath, sizeof(ufspath), "%s%s", + dr, open_path); + } else { + snprintf((char *)ufspath, sizeof(ufspath), "%s", open_path); + } + if (ufs_stat(ufs, (const char *)ufspath, &st) == 0 + && st.filesize > 0) { + rc = http_printf(httpc, "Content-Length: %u\r\n", + st.filesize); + if (rc) goto die; + httpc->content_length_set = 1; + } + } + } + + /* chunked transfer encoding only for HTTP/1.1 clients */ + { + int use_chunked = 0; + if (!httpc->content_length_set) { + UCHAR *ver = http_get_env(httpc, "REQUEST_VERSION"); + if (ver && http_cmp(ver, "HTTP/1.1") == 0) { + rc = http_printf(httpc, "Transfer-Encoding: chunked\r\n"); + if (rc) goto die; + use_chunked = 1; + } + /* HTTP/1.0: no chunked, body delimited by Connection: close */ + } + + rc = http_printf(httpc, "\r\n"); + if (rc) goto die; + + /* enable chunk framing AFTER header-ending CRLF is sent */ + httpc->chunked = use_chunked; + } /* indicate type of document being sent */ if (mime->binary) { diff --git a/src/httpin.c b/src/httpin.c index ba203f3..d6c622c 100644 --- a/src/httpin.c +++ b/src/httpin.c @@ -71,6 +71,21 @@ int http_in(HTTPC *httpc) if (http_set_http_env(httpc, buf, p)) goto failed; } while(rc > 0); + /* HTTP/1.1 requires a Host header */ + { + UCHAR *ver = http_get_env(httpc, "REQUEST_VERSION"); + if (ver && http_cmp(ver, "HTTP/1.1") == 0) { + UCHAR *host = http_get_env(httpc, "HTTP_HOST"); + if (!host || !host[0]) { + http_resp(httpc, 400); + http_printf(httpc, "Content-Type: text/plain\r\n\r\n"); + http_printf(httpc, "400 Bad Request: Missing Host header\r\n"); + httpc->state = CSTATE_DONE; + goto quit; + } + } + } + /* next step will parse and do any additional processing */ rc = 0; httpc->state = CSTATE_PARSE; diff --git a/src/httppars.c b/src/httppars.c index 830ca7c..b1960eb 100644 --- a/src/httppars.c +++ b/src/httppars.c @@ -87,7 +87,7 @@ httppars(HTTPC *httpc) if (http_set_env(httpc, "SERVER_PORT", tmp)) goto failed; } - if (http_set_env(httpc, "SERVER_PROTOCOL", "HTTP/1.0")) goto failed; + if (http_set_env(httpc, "SERVER_PROTOCOL", "HTTP/1.1")) goto failed; sprintf(tmp, "HTTPD/%s", httpc->httpd->version); if (http_set_env(httpc, "SERVER_SOFTWARE", tmp)) goto failed; diff --git a/src/httprese.c b/src/httprese.c index d1b6db0..ac56bfd 100644 --- a/src/httprese.c +++ b/src/httprese.c @@ -31,6 +31,8 @@ httprese(HTTPC *httpc) httpc->sent = 0; httpc->subtype = 0; httpc->substate = 0; + httpc->chunked = 0; + httpc->content_length_set = 0; httpc->start = 0.0; httpc->end = 0.0; memset(httpc->buf, 0, CBUFSIZE); diff --git a/src/httpresp.c b/src/httpresp.c index 7c9cc82..a39fb9d 100644 --- a/src/httpresp.c +++ b/src/httpresp.c @@ -42,7 +42,15 @@ httpresp(HTTPC *httpc, int resp) httpc->resp = resp; - rc = http_printf(httpc, "HTTP/1.0 %s\r\n", p); + /* match response version to client request version */ + { + UCHAR *ver = http_get_env(httpc, "REQUEST_VERSION"); + if (ver && http_cmp(ver, "HTTP/1.1") == 0) { + rc = http_printf(httpc, "HTTP/1.1 %s\r\n", p); + } else { + rc = http_printf(httpc, "HTTP/1.0 %s\r\n", p); + } + } if (rc) goto quit; now = time64(NULL); @@ -68,6 +76,10 @@ httpresp(HTTPC *httpc, int resp) if (rc) goto quit; } + /* HTTP/1.1: always close for now (keep-alive planned) */ + rc = http_printf(httpc, "Connection: close\r\n"); + if (rc) goto quit; + quit: http_exit("httpresp(), rc=%d\n", rc); return rc; diff --git a/src/httpsend.c b/src/httpsend.c index f294ae4..f3ecf22 100644 --- a/src/httpsend.c +++ b/src/httpsend.c @@ -4,62 +4,74 @@ */ #include "httpd.h" -extern int -httpsend(HTTPC *httpc, const UCHAR *buf, int len) +/* send_raw() - send raw bytes to socket without chunk framing */ +static int +send_raw(HTTPC *httpc, const UCHAR *buf, int len) { int rc; int pos = 0; -#if 0 - wtof("httpsend(%08X,%08X,%d)", httpc, buf, len); -#endif - - /* send data to socket */ for(pos=0; pos < len; pos+=rc) { if (pos < 0) { wtof("httpsend() pos underflow %d", pos); break; } -#if 0 - wtof("calling send(%d,%08X,%d,0)", - httpc->socket, &buf[pos], len-pos); -#endif rc = send(httpc->socket, &buf[pos], len-pos, 0); -#if 0 - wtof("send() rc=%d", rc); -#endif if (rc>0) { - /* update bytes sent count */ httpc->sent += rc; -#if 0 - wtof("httpc->sent=%d", httpc->sent); -#endif } else { -#if 0 - wtof("httpsend() send rc=%d, errno=%d, socket=%d", rc, errno, httpc->socket); -#endif /* allow for EWOULDBLOCK */ if (errno == EWOULDBLOCK) break; /* an error occured */ if (httpc->state < CSTATE_DONE) { - /* transtion to done state */ -#if 0 - wtof("httpsend() changing httpc->state=CSTATE_DONE"); -#endif httpc->state = CSTATE_DONE; - goto quit; /* return with negative rc */ + return -1; } break; } } - rc = pos; + return pos; +} + +extern int +httpsend(HTTPC *httpc, const UCHAR *buf, int len) +{ + int rc; + + if (httpc->chunked) { + /* RFC 7230 chunked transfer encoding */ + UCHAR hdr[16]; + int hdrlen; + + if (len <= 0) return 0; + + /* chunk header: hex size + CRLF (convert EBCDIC to ASCII) */ + hdrlen = sprintf((char *)hdr, "%x\r\n", len); + http_etoa(hdr, hdrlen); + rc = send_raw(httpc, hdr, hdrlen); + if (rc < 0) return rc; + + /* chunk data (already in ASCII from caller) */ + rc = send_raw(httpc, buf, len); + if (rc < 0) return rc; + + /* chunk trailer: CRLF in ASCII (0x0D 0x0A) */ + { + UCHAR crlf[2]; + crlf[0] = 0x0D; + crlf[1] = 0x0A; + rc = send_raw(httpc, crlf, 2); + } + if (rc < 0) return rc; + + return len; + } + + /* non-chunked: send raw */ + rc = send_raw(httpc, buf, len); -quit: -#if 0 - wtof("httpsend() rc=%d", rc); -#endif return rc; }