[PATCH v4] httpd: Pass custom HTTP headers to CGI scripts

Alexander Vickberg wickbergster at gmail.com
Tue Apr 16 06:30:12 UTC 2019


Hi Denys,

What about something like this?

Enable passing of more HTTP headers to CGI applications. Even custom ones.
The variables will be named HTTP_<filtered name> where the filtered name is
the
header name capitalized and all characters not matching [a-z] | [A-Z] |
[0-9]
replaced with '_'. This patch also introduces a size limit of 8kB for the
incoming HTTP headers. If larger a 413 Entity too large will be issued.

Signed-off-by: Alexander Vickberg <wickbergster at gmail.com>

function                                             old     new   delta
http_response                                        160     176     +16
http_response_type                                    20      22      +2
httpd_main                                           874     868      -6
send_headers                                        1004     986     -18
send_file_and_exit                                   802     778     -24
.rodata                                           158036  157938     -98
handle_incoming_and_exit                            2809    2693    -116
send_cgi_and_exit                                   1016     854    -162
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 2/6 up/down: 18/-424)          Total: -406
bytes

diff --git a/networking/httpd.c b/networking/httpd.c
index c486197b8..03b69fb99 100644
--- a/networking/httpd.c
+++ b/networking/httpd.c
@@ -267,6 +267,7 @@
 #define DEBUG 0

 #define IOBUF_SIZE 8192
+#define MAX_HTTP_HEADER_SIZE (8*1024)
 #if PIPE_BUF >= IOBUF_SIZE
 # error "PIPE_BUF >= IOBUF_SIZE"
 #endif
@@ -305,6 +306,13 @@ typedef struct Htaccess_Proxy {
  char *url_to;
 } Htaccess_Proxy;

+typedef enum CGI_type {
+ CGI_NONE = 0,
+ CGI_NORMAL,
+ CGI_INDEX,
+ CGI_INTERPRETOR,
+} CGI_type;
+
 enum {
  HTTP_OK = 200,
  HTTP_PARTIAL_CONTENT = 206,
@@ -316,6 +324,7 @@ enum {
  HTTP_REQUEST_TIMEOUT = 408,
  HTTP_NOT_IMPLEMENTED = 501,   /* used for unrecognized requests */
  HTTP_INTERNAL_SERVER_ERROR = 500,
+ HTTP_ENTITY_TOO_LARGE = 413,
  HTTP_CONTINUE = 100,
 #if 0   /* future use */
  HTTP_SWITCHING_PROTOCOLS = 101,
@@ -347,6 +356,7 @@ static const uint16_t http_response_type[] ALIGN2 = {
  HTTP_BAD_REQUEST,
  HTTP_FORBIDDEN,
  HTTP_INTERNAL_SERVER_ERROR,
+ HTTP_ENTITY_TOO_LARGE,
 #if 0   /* not implemented */
  HTTP_CREATED,
  HTTP_ACCEPTED,
@@ -377,6 +387,7 @@ static const struct {
  { "Bad Request", "Unsupported method" },
  { "Forbidden", ""  },
  { "Internal Server Error", "Internal Server Error" },
+ { "Entity Too Large", "Entity Too Large" },
 #if 0   /* not implemented */
  { "Created" },
  { "Accepted" },
@@ -412,11 +423,6 @@ struct globals {

  IF_FEATURE_HTTPD_BASIC_AUTH(const char *g_realm;)
  IF_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;)
- IF_FEATURE_HTTPD_CGI(char *referer;)
- IF_FEATURE_HTTPD_CGI(char *user_agent;)
- IF_FEATURE_HTTPD_CGI(char *host;)
- IF_FEATURE_HTTPD_CGI(char *http_accept;)
- IF_FEATURE_HTTPD_CGI(char *http_accept_language;)

  off_t file_size;        /* -1 - unknown */
 #if ENABLE_FEATURE_HTTPD_RANGES
@@ -1437,23 +1443,17 @@ static void setenv1(const char *name, const char
*value)
  * const char *url              The requested URL (with leading /).
  * const char *orig_uri         The original URI before rewriting (if any)
  * int post_len                 Length of the POST body.
- * const char *cookie           For set HTTP_COOKIE.
- * const char *content_type     For set CONTENT_TYPE.
  */
 static void send_cgi_and_exit(
  const char *url,
  const char *orig_uri,
  const char *request,
- int post_len,
- const char *cookie,
- const char *content_type) NORETURN;
+ int post_len) NORETURN;
 static void send_cgi_and_exit(
  const char *url,
  const char *orig_uri,
  const char *request,
- int post_len,
- const char *cookie,
- const char *content_type)
+ int post_len)
 {
  struct fd_pair fromCgi;  /* CGI -> httpd pipe */
  struct fd_pair toCgi;    /* httpd -> CGI pipe */
@@ -1531,26 +1531,14 @@ static void send_cgi_and_exit(
 #endif
  }
  }
- setenv1("HTTP_USER_AGENT", G.user_agent);
- if (G.http_accept)
- setenv1("HTTP_ACCEPT", G.http_accept);
- if (G.http_accept_language)
- setenv1("HTTP_ACCEPT_LANGUAGE", G.http_accept_language);
  if (post_len)
  putenv(xasprintf("CONTENT_LENGTH=%u", post_len));
- if (cookie)
- setenv1("HTTP_COOKIE", cookie);
- if (content_type)
- setenv1("CONTENT_TYPE", content_type);
 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
  if (remoteuser) {
  setenv1("REMOTE_USER", remoteuser);
  putenv((char*)"AUTH_TYPE=Basic");
  }
 #endif
- if (G.referer)
- setenv1("HTTP_REFERER", G.referer);
- setenv1("HTTP_HOST", G.host); /* set to "" if NULL */
  /* setenv1("SERVER_NAME", safe_gethostname()); - don't do this,
  * just run "env SERVER_NAME=xyz httpd ..." instead */

@@ -1836,10 +1824,11 @@ static void
send_HTTP_FORBIDDEN_and_exit_if_denied_ip(void)
  (unsigned char)(cur->mask)
  );
 #endif
- if ((rmt_ip & cur->mask) == cur->ip)
+ if ((rmt_ip & cur->mask) == cur->ip) {
  if (cur->allow_deny == 'A')
  return;
  send_headers_and_exit(HTTP_FORBIDDEN);
+ }
  }

  if (flg_deny_all) /* depends on whether we saw "D:*" */
@@ -2080,12 +2069,12 @@ static void handle_incoming_and_exit(const
len_and_sockaddr *fromAddr)
  char *urlcopy;
  char *urlp;
  char *tptr;
+ int hdr_len = 0;
 #if ENABLE_FEATURE_HTTPD_CGI
  static const char request_HEAD[] ALIGN1 = "HEAD";
  const char *prequest;
- char *cookie = NULL;
- char *content_type = NULL;
  unsigned long length = 0;
+ enum CGI_type cgi_type = CGI_NONE;
 #elif ENABLE_FEATURE_HTTPD_PROXY
 #define prequest request_GET
  unsigned long length = 0;
@@ -2260,13 +2249,68 @@ static void handle_incoming_and_exit(const
len_and_sockaddr *fromAddr)
  header_buf = header_ptr = xmalloc(IOBUF_SIZE);
 #endif

+ tptr = urlcopy + 1;      /* skip first '/' */
+
+#if ENABLE_FEATURE_HTTPD_CGI
+ if (is_prefixed_with(tptr, "cgi-bin/")) {
+ if (tptr[8] == '\0') {
+ /* protect listing "cgi-bin/" */
+ send_headers_and_exit(HTTP_FORBIDDEN);
+ }
+ cgi_type = CGI_NORMAL;
+ }
+#endif
+
+ if (urlp[-1] == '/') {
+ /* When index_page string is appended to <dir>/ URL, it overwrites
+ * the query string. If we fall back to call /cgi-bin/index.cgi,
+ * query string would be lost and not available to the CGI.
+ * Work around it by making a deep copy.
+ */
+ if (ENABLE_FEATURE_HTTPD_CGI)
+ g_query = xstrdup(g_query); /* ok for NULL too */
+ strcpy(urlp, index_page);
+ }
+ if (stat(tptr, &sb) == 0) {
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+ char *suffix = strrchr(tptr, '.');
+ if (suffix) {
+ Htaccess *cur;
+ for (cur = script_i; cur; cur = cur->next) {
+ if (strcmp(cur->before_colon + 1, suffix) == 0) {
+ cgi_type = CGI_INTERPRETOR;
+ break;
+ }
+ }
+ }
+#endif
+ if (!found_moved_temporarily) {
+ file_size = sb.st_size;
+ last_mod = sb.st_mtime;
+ }
+ }
+#if ENABLE_FEATURE_HTTPD_CGI
+ else if (urlp[-1] == '/') {
+ /* It's a dir URL and there is no index.html
+ * Try cgi-bin/index.cgi */
+ if (access("/cgi-bin/index.cgi"+1, X_OK) == 0) {
+ cgi_type = CGI_INDEX;
+ }
+ }
+#endif
+ urlp[0] = '\0';
+
  if (http_major_version >= '0') {
  /* Request was with "... HTTP/nXXX", and n >= 0 */

  /* Read until blank line */
  while (1) {
- if (!get_line())
+ int iobuf_len = get_line();
+ if (!iobuf_len)
  break; /* EOF or error or empty line */
+ hdr_len += iobuf_len;
+ if (hdr_len > MAX_HTTP_HEADER_SIZE)
+ send_headers_and_exit(HTTP_ENTITY_TOO_LARGE);
  if (DEBUG)
  bb_error_msg("header: '%s'", iobuf);

@@ -2306,30 +2350,7 @@ static void handle_incoming_and_exit(const
len_and_sockaddr *fromAddr)
  if (errno || length > INT_MAX)
  send_headers_and_exit(HTTP_BAD_REQUEST);
  }
- }
-#endif
-#if ENABLE_FEATURE_HTTPD_CGI
- else if (STRNCASECMP(iobuf, "Cookie:") == 0) {
- if (!cookie) /* in case they send millions of these, do not OOM */
- cookie = xstrdup(skip_whitespace(iobuf + sizeof("Cookie:")-1));
- } else if (STRNCASECMP(iobuf, "Content-Type:") == 0) {
- if (!content_type)
- content_type = xstrdup(skip_whitespace(iobuf +
sizeof("Content-Type:")-1));
- } else if (STRNCASECMP(iobuf, "Referer:") == 0) {
- if (!G.referer)
- G.referer = xstrdup(skip_whitespace(iobuf + sizeof("Referer:")-1));
- } else if (STRNCASECMP(iobuf, "User-Agent:") == 0) {
- if (!G.user_agent)
- G.user_agent = xstrdup(skip_whitespace(iobuf + sizeof("User-Agent:")-1));
- } else if (STRNCASECMP(iobuf, "Host:") == 0) {
- if (!G.host)
- G.host = xstrdup(skip_whitespace(iobuf + sizeof("Host:")-1));
- } else if (STRNCASECMP(iobuf, "Accept:") == 0) {
- if (!G.http_accept)
- G.http_accept = xstrdup(skip_whitespace(iobuf + sizeof("Accept:")-1));
- } else if (STRNCASECMP(iobuf, "Accept-Language:") == 0) {
- if (!G.http_accept_language)
- G.http_accept_language = xstrdup(skip_whitespace(iobuf +
sizeof("Accept-Language:")-1));
+ continue;
  }
 #endif
 #if ENABLE_FEATURE_HTTPD_BASIC_AUTH
@@ -2345,6 +2366,7 @@ static void handle_incoming_and_exit(const
len_and_sockaddr *fromAddr)
  /* decodeBase64() skips whitespace itself */
  decodeBase64(tptr);
  authorized = check_user_passwd(urlcopy, tptr);
+ continue;
  }
 #endif
 #if ENABLE_FEATURE_HTTPD_RANGES
@@ -2362,6 +2384,7 @@ static void handle_incoming_and_exit(const
len_and_sockaddr *fromAddr)
  range_start = -1;
  }
  }
+ continue;
  }
 #endif
 #if ENABLE_FEATURE_HTTPD_GZIP
@@ -2379,6 +2402,36 @@ static void handle_incoming_and_exit(const
len_and_sockaddr *fromAddr)
  content_gzip = 1;
  //}
  }
+ continue;
+ }
+#endif
+#if ENABLE_FEATURE_HTTPD_CGI
+ if (cgi_type == CGI_NONE)
+     continue;
+ if (STRNCASECMP(iobuf, "Content-Type:") == 0) {
+ putenv(xasprintf("CONTENT_TYPE=%s", skip_whitespace(iobuf +
sizeof("Content-Type:")-1)));
+ } else {
+ char *after_colon = strchr(iobuf, ':');
+ char *ch = iobuf;
+ char *val, *varval;
+
+ if (!after_colon)
+ continue;
+
+ *after_colon++ = '\0';
+
+ while (*ch) {
+ if (isalpha(*ch))
+ *ch &= ~0x20;
+ else if (!isdigit(*ch))
+ *ch = '_';
+ ch++;
+ }
+
+ val = skip_whitespace(after_colon);
+ varval = xmalloc(strlen(iobuf) + strlen(val) + 7);
+ sprintf(varval, "HTTP_%s=%s", iobuf, val);
+ putenv(varval);
  }
 #endif
  } /* while extra header reading */
@@ -2437,51 +2490,18 @@ static void handle_incoming_and_exit(const
len_and_sockaddr *fromAddr)
  tptr = urlcopy + 1;      /* skip first '/' */

 #if ENABLE_FEATURE_HTTPD_CGI
- if (is_prefixed_with(tptr, "cgi-bin/")) {
- if (tptr[8] == '\0') {
- /* protect listing "cgi-bin/" */
- send_headers_and_exit(HTTP_FORBIDDEN);
- }
- send_cgi_and_exit(urlcopy, urlcopy, prequest, length, cookie,
content_type);
+ if (cgi_type == CGI_NORMAL || cgi_type == CGI_INTERPRETOR) {
+     send_cgi_and_exit(urlcopy, urlcopy, prequest, length);
+ } else if (cgi_type == CGI_INDEX) {
+     send_cgi_and_exit("/cgi-bin/index.cgi", urlcopy, prequest, length);
  }
 #endif

  if (urlp[-1] == '/') {
- /* When index_page string is appended to <dir>/ URL, it overwrites
- * the query string. If we fall back to call /cgi-bin/index.cgi,
- * query string would be lost and not available to the CGI.
- * Work around it by making a deep copy.
- */
- if (ENABLE_FEATURE_HTTPD_CGI)
- g_query = xstrdup(g_query); /* ok for NULL too */
  strcpy(urlp, index_page);
  }
- if (stat(tptr, &sb) == 0) {
-#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
- char *suffix = strrchr(tptr, '.');
- if (suffix) {
- Htaccess *cur;
- for (cur = script_i; cur; cur = cur->next) {
- if (strcmp(cur->before_colon + 1, suffix) == 0) {
- send_cgi_and_exit(urlcopy, urlcopy, prequest, length, cookie,
content_type);
- }
- }
- }
-#endif
- file_size = sb.st_size;
- last_mod = sb.st_mtime;
- }
-#if ENABLE_FEATURE_HTTPD_CGI
- else if (urlp[-1] == '/') {
- /* It's a dir URL and there is no index.html
- * Try cgi-bin/index.cgi */
- if (access("/cgi-bin/index.cgi"+1, X_OK) == 0) {
- urlp[0] = '\0'; /* remove index_page */
- send_cgi_and_exit("/cgi-bin/index.cgi", urlcopy, prequest, length,
cookie, content_type);
- }
- }
- /* else fall through to send_file, it errors out if open fails: */

+#if ENABLE_FEATURE_HTTPD_CGI
  if (prequest != request_GET && prequest != request_HEAD) {
  /* POST for files does not make sense */
  send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.busybox.net/pipermail/busybox/attachments/20190416/eb58bf57/attachment-0001.html>


More information about the busybox mailing list