Support redbean Fetch() headers (#405)

This commit is contained in:
Paul Kulchenko 2022-05-11 22:06:42 -07:00 committed by GitHub
parent cfc3a953ae
commit 70c97f598b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 59 additions and 11 deletions

View file

@ -682,23 +682,26 @@ FUNCTIONS
EscapeUser(str) → str
Escapes URL username. See kescapeauthority.c.
Fetch(url:str[,body:str|{method=value:str,body=value:str,...}])
Fetch(url:str[,body:str|{method=value:str,body=value:str,headers=table,...}])
→ status:int,{header:str=value:str,...},body:str
Sends an HTTP/HTTPS request to the specified URL. If only the URL is
provided, then a GET request is sent. If both URL and body parameters
are specified, then a POST request is sent. If any other method needs
to be specified (for example, PUT or DELETE), then passing a table as
the second value allows setting method and body values as well other
options:
the second value allows setting request method, body, and headers, as
well as some other options:
- method (default = "GET"): sets the method to be used for the
request. The specified method is converted to uppercase.
- body (default = ""): sets the body value to be sent.
- headers: sets headers for the request using the key/value pairs
from this table. Only string keys are used and all the values are
converted to strings.
- followredirect (default = true): forces temporary and permanent
redirects to be followed. This behavior can be disabled by
passing `false`.
- maxredirects (default = 5): sets the number of allowed redirects
to minimize looping due to misconfigured servers. When the number
is exceeded, the result of the last redirect is returned.
is exceeded, the last response is returned.
When the redirect is being followed, the same method and body values
are being sent in all cases except when 303 status is returned. In
that case the method is set to GET and the body is removed before the

View file

@ -3685,7 +3685,7 @@ static int LuaFetch(lua_State *L) {
bool usessl;
uint32_t ip;
struct Url url;
int t, ret, sock, methodidx;
int t, ret, sock, methodidx, hdridx;
char *host, *port;
struct TlsBio *bio;
struct addrinfo *addr;
@ -3694,6 +3694,11 @@ static int LuaFetch(lua_State *L) {
struct HttpUnchunker u;
const char *urlarg, *request, *body, *method;
char *conlenhdr = "";
char *headers = 0;
char *hosthdr = 0;
char *agenthdr = brand;
char *key, *val, *hdr;
size_t keylen, vallen;
size_t urlarglen, requestlen, paylen, bodylen;
size_t g, i, n, hdrsize;
int numredirects = 0, maxredirects = 5;
@ -3720,6 +3725,43 @@ static int LuaFetch(lua_State *L) {
maxredirects = luaL_optinteger(L, -1, maxredirects);
lua_getfield(L, 2, "numredirects");
numredirects = luaL_optinteger(L, -1, numredirects);
lua_getfield(L, 2, "headers");
if (!lua_isnil(L, -1)) {
if (!lua_istable(L, -1))
return luaL_argerror(L, 2, "invalid headers value; table expected");
lua_pushnil(L);
while (lua_next(L, -2)) {
if (lua_type(L, -2) == LUA_TSTRING) { // skip any non-string keys
key = lua_tolstring(L, -2, &keylen);
if (!IsValidHttpToken(key, keylen))
return luaL_argerror(L, 2, "invalid header name");
val = lua_tolstring(L, -1, &vallen);
if (!(hdr = gc(EncodeHttpHeaderValue(val, vallen, 0))))
return luaL_argerror(L, 2, "invalid header value encoding");
// Content-Length and Connection will be overwritten;
// skip them to avoid duplicates;
// also allow unknown headers
if ((hdridx = GetHttpHeader(key, keylen)) == -1 ||
hdridx != kHttpContentLength &&
hdridx != kHttpConnection) {
if (hdridx == kHttpUserAgent) {
agenthdr = hdr;
} else if (hdridx == kHttpHost) {
hosthdr = hdr;
} else {
appendd(&headers, key, keylen);
appendw(&headers, READ16LE(": "));
appends(&headers, hdr);
appendw(&headers, READ16LE("\r\n"));
}
}
}
lua_pop(L, 1); // pop the value, keep the key for the next iteration
}
}
lua_settop(L, 2); // drop all added elements to keep the stack balanced
} else if (lua_isnoneornil(L, 2)) {
body = "";
@ -3765,15 +3807,18 @@ static int LuaFetch(lua_State *L) {
port = usessl ? "443" : "80";
}
} else {
ip = ntohl(servers.p[0].addr.sin_addr.s_addr);
ip = servers.n ? ntohl(servers.p[0].addr.sin_addr.s_addr) : INADDR_LOOPBACK;
host =
gc(xasprintf("%hhu.%hhu.%hhu.%hhu", ip >> 24, ip >> 16, ip >> 8, ip));
port = gc(xasprintf("%d", ntohs(servers.p[0].addr.sin_port)));
port =
gc(xasprintf("%d", servers.n ? ntohs(servers.p[0].addr.sin_port) : 80));
}
if (!IsAcceptableHost(host, -1)) {
luaL_argerror(L, 1, "invalid host");
unreachable;
}
if (!hosthdr) hosthdr = gc(xasprintf("%s:%s", host, port));
url.fragment.p = 0, url.fragment.n = 0;
url.scheme.p = 0, url.scheme.n = 0;
url.user.p = 0, url.user.n = 0;
@ -3791,13 +3836,13 @@ static int LuaFetch(lua_State *L) {
* Create HTTP message.
*/
request = gc(xasprintf("%s %s HTTP/1.1\r\n"
"Host: %s:%s\r\n"
"Host: %s\r\n"
"Connection: close\r\n"
"User-Agent: %s\r\n"
"%s"
"%s%s"
"\r\n%s",
method, gc(EncodeUrl(&url, 0)), host, port, brand,
conlenhdr, body));
method, gc(EncodeUrl(&url, 0)), hosthdr, agenthdr,
conlenhdr, headers ? headers : "", body));
requestlen = strlen(request);
/*