Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 23 additions & 12 deletions src/httpgets.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ int http_gets(HTTPC *httpc, UCHAR *buf, unsigned max)
memset(buf, 0, max);
}

int saw_cr = 0;

for(i=0; i < max; ) {
/* get one character from client socket */
c = http_getc(httpc);
Expand All @@ -51,7 +53,7 @@ int http_gets(HTTPC *httpc, UCHAR *buf, unsigned max)
if (httpd->client & HTTPD_CLIENT_INDUMP) {
wtodumpf(httpc->buf, i, "Receive Buffer");
}

http_dbgf(fmt, httpc, httpc->socket, seconds);
}
httpc->state = CSTATE_CLOSE;
Expand All @@ -62,21 +64,30 @@ int http_gets(HTTPC *httpc, UCHAR *buf, unsigned max)
goto quit;
}

/* check for ASCII CR and discard */
if (c==0x0D) continue;
/* check for ASCII CR */
if (c == 0x0D) {
saw_cr = 1;
continue;
}

/* check for ASCII LF */
if (c != 0x0A) {
/* not ASCII LF, translate ASCII to EBCDIC and save character */
buf[i++] = (UCHAR)asc2ebc[c];
continue;
if (c == 0x0A) {
/* LF terminates line (CRLF or bare LF both accepted) */
buf[i++] = '\n'; /* EBCDIC newline */
buf[i] = 0; /* end of string */
rc = i; /* length of string */
break;
}

/* data character after CR without LF = bare CR (RFC 7230 §3.5) */
if (saw_cr) {
errno = EINVAL;
rc = -1;
goto quit;
}

/* ASCII LF */
buf[i++] = '\n'; /* EBCDIC newline */
buf[i] = 0; /* end of string */
rc = i; /* length of string */
break;
/* translate ASCII to EBCDIC and save character */
buf[i++] = (UCHAR)asc2ebc[c];
}

quit:
Expand Down
86 changes: 80 additions & 6 deletions src/httpin.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,24 @@ int http_in(HTTPC *httpc)
UCHAR *buf = httpc->buf;
unsigned len = CBUFSIZE-1;
UCHAR *p;
int host_count = 0;

// wtof("%s: Enter", __func__);

/* read the request string "method uri version" */
rc = http_gets(httpc, buf, len);
if (rc < 0) goto failed;
if (rc < 0) {
if (errno == EINVAL) goto badrequest;
goto failed;
}
if (rc == 0) {
/* request line exceeds buffer — URI too long */
http_resp(httpc, 414);
http_printf(httpc, "Content-Type: text/plain\r\n\r\n");
http_printf(httpc, "414 URI Too Long\r\n");
httpc->state = CSTATE_DONE;
goto quit;
}

/* "GET path/to/resource HTTP/1.0\n" */

Expand All @@ -39,14 +51,27 @@ int http_in(HTTPC *httpc)
if (http_set_env(httpc, "REQUEST_URI", p)) goto failed;

p = strtok(NULL, " ");
if (!p) goto failed;
if (!p) goto badrequest; /* missing HTTP version */
if (http_set_env(httpc, "REQUEST_VERSION", p)) goto failed;

/* validate HTTP version — RFC 7230 §2.6 */
if (http_cmp(p, "HTTP/1.0") != 0 && http_cmp(p, "HTTP/1.1") != 0) {
http_resp(httpc, 505);
http_printf(httpc, "Content-Type: text/plain\r\n\r\n");
http_printf(httpc, "505 HTTP Version Not Supported\r\n");
httpc->state = CSTATE_DONE;
goto quit;
}

/* create "HTTP_..." environment variables from HTTP headers */
do {
/* get HTTP header string */
rc = http_gets(httpc, buf, len);
if (rc < 0) goto failed;
if (rc < 0) {
if (errno == EINVAL) goto badrequest;
goto failed;
}
if (rc == 0) goto badrequest; /* header line too long */

/* check for end of HTTP headers */
if (buf[0]=='\n') break;
Expand All @@ -55,22 +80,64 @@ int http_in(HTTPC *httpc)
p = strrchr(buf, '\n');
if (p) *p = 0;

// wtof("%s: \"%s\"", __func__, buf);

/* find keyword delimiter */
p = strchr(buf, ':');
if (!p) continue;

/* replace delimiter with 0 byte */
*p++ = 0;


/* validate header name — RFC 7230 §3.2.6 token chars only
allowlist approach avoids EBCDIC code page mismatches */
{
UCHAR *s;
for (s = buf; *s; s++) {
if (isalnum(*s)) continue;
if (*s == '!' || *s == '#' || *s == '$' || *s == '%' ||
*s == '&' || *s == '\'' || *s == '*' || *s == '+' ||
*s == '-' || *s == '.' || *s == '^' || *s == '_' ||
*s == '`' || *s == '|' || *s == '~')
continue;
goto badrequest;
}
}

/* skip any spaces */
while(*p==' ') p++;

/* validate header value — no control chars (RFC 7230 §3.2.6) */
{
UCHAR *s;
for (s = p; *s; s++) {
if (*s < ' ' && *s != '\t')
goto badrequest;
}
}

/* track Host header for duplicate detection */
if (http_cmp(buf, "Host") == 0)
host_count++;

/* create "HTTP_..." environment variable */
if (http_set_http_env(httpc, buf, p)) goto failed;
} while(rc > 0);

/* reject multiple Host headers — RFC 7230 §5.4 */
if (host_count > 1) goto badrequest;

/* validate Content-Length — RFC 7230 §3.3.2 (digits only) */
{
UCHAR *cl = http_get_env(httpc, "HTTP_CONTENT-LENGTH");
if (cl) {
UCHAR *s;
if (!*cl) goto badrequest;
for (s = cl; *s; s++) {
if (*s < '0' || *s > '9')
goto badrequest;
}
}
}

/* HTTP/1.1 requires a Host header */
{
UCHAR *ver = http_get_env(httpc, "REQUEST_VERSION");
Expand Down Expand Up @@ -106,6 +173,13 @@ int http_in(HTTPC *httpc)
httpc->state = CSTATE_PARSE;
goto quit;

badrequest:
http_resp(httpc, 400);
http_printf(httpc, "Content-Type: text/plain\r\n\r\n");
http_printf(httpc, "400 Bad Request\r\n");
httpc->state = CSTATE_DONE;
goto quit;

failed:
if (httpc->request_count > 0) {
/* keep-alive: idle timeout or client disconnect — just close */
Expand Down
15 changes: 13 additions & 2 deletions src/httppars.c
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,13 @@ httppars(HTTPC *httpc)
goto postdata;
}

/* not implemented */
rc = http_resp_not_implemented(httpc);
/* method not allowed — RFC 7231 §6.5.5 */
httpc->keepalive = 0; /* unread body would poison next request */
http_resp(httpc, 405);
http_printf(httpc, "Cache-Control: no-store\r\n");
http_printf(httpc, "Content-Type: text/plain\r\n");
http_printf(httpc, "\r\n");
http_printf(httpc, "405 Method Not Allowed\n");
httpc->state = CSTATE_DONE;
goto quit;

Expand Down Expand Up @@ -198,6 +203,12 @@ httppars(HTTPC *httpc)
if (http_set_env(httpc, "POST_STRING", buf)) goto failed;

nodata:
/* if the request had a body we didn't read, disable keep-alive
to prevent body data from poisoning the next request */
if (http_get_env(httpc, "HTTP_CONTENT-LENGTH") ||
http_get_env(httpc, "HTTP_TRANSFER-ENCODING"))
httpc->keepalive = 0;

/* no more expected data from client */
memset(httpc->buf, 0, CBUFSIZE);
httpc->len = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/httpprm.c
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ set_defaults(HTTPD *httpd)
httpd->cgilua_path = NULL;
httpd->cgilua_cpath = NULL;
httpd->unused_80 = NULL;
httpd->docroot[0] = '\0';
strcpy(httpd->docroot, "/www");
httpd->codepage[0] = '\0';
httpd->dbg_enabled = 0;
httpd->cfg_keepalive_timeout = 5;
Expand Down
Loading