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>
This commit is contained in:
Denys Vlasenko 2026-01-28 17:45:37 +01:00
parent 03d6d1014a
commit df1ef312a0

View file

@ -1409,6 +1409,13 @@ static unsigned get_line(void)
}
#if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY
static void log_cgi_status(const char *pfx, const char *st, unsigned len)
{
unsigned char *end = (void*)st;
while (*end >= ' ' && *end < 0x7f && len > 0)
end++, len--;
bb_error_msg("cgi %s:'%.*s'", pfx, (int)((char *)end - st), st);
}
/* gcc 4.2.1 fares better with NOINLINE */
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)
{
enum { FROM_CGI = 1, TO_CGI = 2 }; /* indexes in pfd[] */
struct pollfd pfd[3];
int out_cnt; /* we buffer a bit of initial CGI output */
int out_cnt;
int count;
/* 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)
G.POST_len -= hdr_cnt;
//bb_error_msg("G.POST_len:%d", G.POST_len);
/* NB: breaking out of this loop jumps to log_and_exit() */
/* If it's really CGI, we buffer a bit of initial CGI output and handle "Status:" etc */
/* If it's proxying, then no buffering/conversion is needed (out_cnt set to -1)*/
#if ENABLE_FEATURE_HTTPD_PROXY
out_cnt = - (fromCgi_rd == toCgi_wr);
#else
out_cnt = 0;
#endif
/* NB: breaking out of this loop jumps to log_and_exit() */
pfd[FROM_CGI].fd = fromCgi_rd;
pfd[FROM_CGI].events = POLLIN;
pfd[TO_CGI].fd = toCgi_wr;
@ -1552,8 +1566,8 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr)
* CGI may output a few first bytes and then wait
* for POSTDATA without closing stdout.
* With full_read we may wait here forever. */
count = safe_read(fromCgi_rd, iobuf + out_cnt, IOBUF_SIZE - 8);
// "- 8" is important, out_cnt can be up to 7
count = safe_read(fromCgi_rd, iobuf + out_cnt, IOBUF_SIZE - 16);
// "- 16" is important, out_cnt can be up to 9
if (count <= 0) {
/* EOF (or error, and out_cnt=0..7
* 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)
}
out_cnt += count;
count = 0;
if (out_cnt >= 8) {
if (out_cnt >= 10) {
//FIXME: "Status: " is not required to be the first header! It can be anywhere!
//FIXME: many servers also check "Location: ". If it exists but "Status: " does _not_, "302 Found" is assumed instead of "200 OK".
uint64_t str8 = *(uint64_t*)iobuf;
//bb_error_msg("from cgi:'%.*s'", out_cnt, iobuf);
@ -1577,18 +1590,20 @@ static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr)
memmove(iobuf + 1, iobuf, out_cnt);
out_cnt += 1;
memcpy(iobuf, HTTP_200, 9);
if (verbose) {
char *end = iobuf + 9;
int cnt = out_cnt - 9;
while ((unsigned char)*end >= ' ' && (unsigned char)*end < 0x7f && cnt > 0)
end++, cnt--;
bb_error_msg("cgi response:'%.*s'", (int)(end - (iobuf + 9)), iobuf + 9);
}
if (verbose)
log_cgi_status("response", iobuf + 9, out_cnt - 9);
}
else
if (str8 == PACK64_LITERAL_STR("Location") && iobuf[8] == ':' && iobuf[9] == ' ') {
#define HTTP_302 "HTTP/1.1 302 Found\r\n"
if (full_write(STDOUT_FILENO, HTTP_302, sizeof(HTTP_302)-1) != sizeof(HTTP_302)-1)
break;
if (verbose)
log_cgi_status("redirect", iobuf + 10, out_cnt - 10);
}
//NB: Apache has no such autodetection. It always adds its own HTTP/1.x header,
//unless the CGI name starts with "nph-", in which case it passes its output verbatim to network.
else /* Did CGI send "HTTP/1.1"? */
//if (memcmp(iobuf, HTTP_200, 8) != 0)
if (str8 != PACK64_LITERAL_STR(HTTP_200)) {
write_HTTP_200_OK:
/* no, send "HTTP/1.1 200 OK\r\n" ourself */