diff options
| author | Denys Vlasenko <vda.linux@googlemail.com> | 2026-01-28 17:45:37 +0100 |
|---|---|---|
| committer | Denys Vlasenko <vda.linux@googlemail.com> | 2026-01-28 17:45:37 +0100 |
| commit | df1ef312a081a7aa9e16ef21020ee895aa5ab893 (patch) | |
| tree | f726ba9ed4d03b99596023bf9319d80213634556 | |
| parent | 03d6d1014aad3389a67b3050aa1e5ca049d241c6 (diff) | |
| download | busybox-w32-df1ef312a081a7aa9e16ef21020ee895aa5ab893.tar.gz busybox-w32-df1ef312a081a7aa9e16ef21020ee895aa5ab893.tar.bz2 busybox-w32-df1ef312a081a7aa9e16ef21020ee895aa5ab893.zip | |
httpd: handle bare "Location: URL" redirects from CGIs
Many sites on the Web give those as valid CGI examples -
no "Status:", just "Location:".
function old new delta
cgi_io_loop_and_exit 620 684 +64
log_cgi_status - 49 +49
.rodata 106846 106861 +15
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 2/0 up/down: 128/0) Total: 128 bytes
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
| -rw-r--r-- | networking/httpd.c | 43 |
1 files changed, 29 insertions, 14 deletions
diff --git a/networking/httpd.c b/networking/httpd.c index cdcae01fb..3739fd55f 100644 --- a/networking/httpd.c +++ b/networking/httpd.c | |||
| @@ -1409,6 +1409,13 @@ static unsigned get_line(void) | |||
| 1409 | } | 1409 | } |
| 1410 | 1410 | ||
| 1411 | #if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY | 1411 | #if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY |
| 1412 | static void log_cgi_status(const char *pfx, const char *st, unsigned len) | ||
| 1413 | { | ||
| 1414 | unsigned char *end = (void*)st; | ||
| 1415 | while (*end >= ' ' && *end < 0x7f && len > 0) | ||
| 1416 | end++, len--; | ||
| 1417 | bb_error_msg("cgi %s:'%.*s'", pfx, (int)((char *)end - st), st); | ||
| 1418 | } | ||
| 1412 | 1419 | ||
| 1413 | /* gcc 4.2.1 fares better with NOINLINE */ | 1420 | /* gcc 4.2.1 fares better with NOINLINE */ |
| 1414 | static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr) NORETURN; | 1421 | static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr) NORETURN; |
| @@ -1416,7 +1423,7 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr) | |||
| 1416 | { | 1423 | { |
| 1417 | enum { FROM_CGI = 1, TO_CGI = 2 }; /* indexes in pfd[] */ | 1424 | enum { FROM_CGI = 1, TO_CGI = 2 }; /* indexes in pfd[] */ |
| 1418 | struct pollfd pfd[3]; | 1425 | struct pollfd pfd[3]; |
| 1419 | int out_cnt; /* we buffer a bit of initial CGI output */ | 1426 | int out_cnt; |
| 1420 | int count; | 1427 | int count; |
| 1421 | 1428 | ||
| 1422 | /* iobuf is used for CGI -> network data, | 1429 | /* iobuf is used for CGI -> network data, |
| @@ -1435,8 +1442,15 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr) | |||
| 1435 | G.POST_len -= hdr_cnt; | 1442 | G.POST_len -= hdr_cnt; |
| 1436 | //bb_error_msg("G.POST_len:%d", G.POST_len); | 1443 | //bb_error_msg("G.POST_len:%d", G.POST_len); |
| 1437 | 1444 | ||
| 1438 | /* NB: breaking out of this loop jumps to log_and_exit() */ | 1445 | /* If it's really CGI, we buffer a bit of initial CGI output and handle "Status:" etc */ |
| 1446 | /* If it's proxying, then no buffering/conversion is needed (out_cnt set to -1)*/ | ||
| 1447 | #if ENABLE_FEATURE_HTTPD_PROXY | ||
| 1448 | out_cnt = - (fromCgi_rd == toCgi_wr); | ||
| 1449 | #else | ||
| 1439 | out_cnt = 0; | 1450 | out_cnt = 0; |
| 1451 | #endif | ||
| 1452 | |||
| 1453 | /* NB: breaking out of this loop jumps to log_and_exit() */ | ||
| 1440 | pfd[FROM_CGI].fd = fromCgi_rd; | 1454 | pfd[FROM_CGI].fd = fromCgi_rd; |
| 1441 | pfd[FROM_CGI].events = POLLIN; | 1455 | pfd[FROM_CGI].events = POLLIN; |
| 1442 | pfd[TO_CGI].fd = toCgi_wr; | 1456 | pfd[TO_CGI].fd = toCgi_wr; |
| @@ -1552,8 +1566,8 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr) | |||
| 1552 | * CGI may output a few first bytes and then wait | 1566 | * CGI may output a few first bytes and then wait |
| 1553 | * for POSTDATA without closing stdout. | 1567 | * for POSTDATA without closing stdout. |
| 1554 | * With full_read we may wait here forever. */ | 1568 | * With full_read we may wait here forever. */ |
| 1555 | count = safe_read(fromCgi_rd, iobuf + out_cnt, IOBUF_SIZE - 8); | 1569 | count = safe_read(fromCgi_rd, iobuf + out_cnt, IOBUF_SIZE - 16); |
| 1556 | // "- 8" is important, out_cnt can be up to 7 | 1570 | // "- 16" is important, out_cnt can be up to 9 |
| 1557 | if (count <= 0) { | 1571 | if (count <= 0) { |
| 1558 | /* EOF (or error, and out_cnt=0..7 | 1572 | /* EOF (or error, and out_cnt=0..7 |
| 1559 | * send "HTTP/1.1 200 OK\r\n", then send received 0..7 bytes */ | 1573 | * send "HTTP/1.1 200 OK\r\n", then send received 0..7 bytes */ |
| @@ -1563,9 +1577,8 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr) | |||
| 1563 | } | 1577 | } |
| 1564 | out_cnt += count; | 1578 | out_cnt += count; |
| 1565 | count = 0; | 1579 | count = 0; |
| 1566 | if (out_cnt >= 8) { | 1580 | if (out_cnt >= 10) { |
| 1567 | //FIXME: "Status: " is not required to be the first header! It can be anywhere! | 1581 | //FIXME: "Status: " is not required to be the first header! It can be anywhere! |
| 1568 | //FIXME: many servers also check "Location: ". If it exists but "Status: " does _not_, "302 Found" is assumed instead of "200 OK". | ||
| 1569 | uint64_t str8 = *(uint64_t*)iobuf; | 1582 | uint64_t str8 = *(uint64_t*)iobuf; |
| 1570 | //bb_error_msg("from cgi:'%.*s'", out_cnt, iobuf); | 1583 | //bb_error_msg("from cgi:'%.*s'", out_cnt, iobuf); |
| 1571 | 1584 | ||
| @@ -1577,18 +1590,20 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr) | |||
| 1577 | memmove(iobuf + 1, iobuf, out_cnt); | 1590 | memmove(iobuf + 1, iobuf, out_cnt); |
| 1578 | out_cnt += 1; | 1591 | out_cnt += 1; |
| 1579 | memcpy(iobuf, HTTP_200, 9); | 1592 | memcpy(iobuf, HTTP_200, 9); |
| 1580 | if (verbose) { | 1593 | if (verbose) |
| 1581 | char *end = iobuf + 9; | 1594 | log_cgi_status("response", iobuf + 9, out_cnt - 9); |
| 1582 | int cnt = out_cnt - 9; | 1595 | } |
| 1583 | while ((unsigned char)*end >= ' ' && (unsigned char)*end < 0x7f && cnt > 0) | 1596 | else |
| 1584 | end++, cnt--; | 1597 | if (str8 == PACK64_LITERAL_STR("Location") && iobuf[8] == ':' && iobuf[9] == ' ') { |
| 1585 | bb_error_msg("cgi response:'%.*s'", (int)(end - (iobuf + 9)), iobuf + 9); | 1598 | #define HTTP_302 "HTTP/1.1 302 Found\r\n" |
| 1586 | } | 1599 | if (full_write(STDOUT_FILENO, HTTP_302, sizeof(HTTP_302)-1) != sizeof(HTTP_302)-1) |
| 1600 | break; | ||
| 1601 | if (verbose) | ||
| 1602 | log_cgi_status("redirect", iobuf + 10, out_cnt - 10); | ||
| 1587 | } | 1603 | } |
| 1588 | //NB: Apache has no such autodetection. It always adds its own HTTP/1.x header, | 1604 | //NB: Apache has no such autodetection. It always adds its own HTTP/1.x header, |
| 1589 | //unless the CGI name starts with "nph-", in which case it passes its output verbatim to network. | 1605 | //unless the CGI name starts with "nph-", in which case it passes its output verbatim to network. |
| 1590 | else /* Did CGI send "HTTP/1.1"? */ | 1606 | else /* Did CGI send "HTTP/1.1"? */ |
| 1591 | //if (memcmp(iobuf, HTTP_200, 8) != 0) | ||
| 1592 | if (str8 != PACK64_LITERAL_STR(HTTP_200)) { | 1607 | if (str8 != PACK64_LITERAL_STR(HTTP_200)) { |
| 1593 | write_HTTP_200_OK: | 1608 | write_HTTP_200_OK: |
| 1594 | /* no, send "HTTP/1.1 200 OK\r\n" ourself */ | 1609 | /* no, send "HTTP/1.1 200 OK\r\n" ourself */ |
