613 lines
15 KiB
C
613 lines
15 KiB
C
/*Export kore's functions to lua*/
|
|
#include <kore/kore.h>
|
|
#include <kore/http.h>
|
|
#ifdef BUILD_PROD
|
|
#include <luajit.h>
|
|
#endif
|
|
#include <lauxlib.h>
|
|
#include <lua.h>
|
|
#include <lualib.h>
|
|
//#include <inet/in.h>//linux only I guess
|
|
#include "libkore.h"
|
|
#include "smr.h" //Where the error handler code is
|
|
#include <syslog.h>
|
|
|
|
// Used to push "string" = number onto the table at the top of the stack
|
|
#define LUA_PUSH_CONST(L,a) lua_pushnumber(L,a); lua_setfield(L,-2,#a);
|
|
|
|
/* md
|
|
@name lua/kore
|
|
@ref http_request
|
|
|
|
### http_request {#http_request}
|
|
|
|
An `http_request` userdata logically represents a request that the kore webserver has received. You can get arguments, files uploaded with the request, and respond to the request. The userdata does not have any methods on it. It is backed by a {{ kore_request }}
|
|
|
|
*/
|
|
|
|
/*
|
|
Checks that the argument at *pos* is a kore_request userdata
|
|
*/
|
|
struct http_request*
|
|
luaL_checkrequest(lua_State *L, int pos){
|
|
if(!lua_isuserdata(L,pos)){
|
|
lua_pushstring(L,"Bad argument, expected userdata, got ");
|
|
lua_pushstring(L,lua_typename(L,lua_type(L,pos)));
|
|
lua_concat(L,2);
|
|
lua_error(L);
|
|
}
|
|
return lua_touserdata(L,pos);
|
|
}
|
|
|
|
/* md
|
|
@name lua/kore
|
|
|
|
### http_response
|
|
|
|
Sends a response to the request given. After this method is called, calls to other methods that accept a request userdata may not work correctly (the data may have been garbage collected).
|
|
|
|
Parameters:
|
|
0. request - {{ http_request }} - The request to serve
|
|
0. errcode - {{ lua/number }} - The http error code. See [http error codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) for error codes generally accepted by web browsers, but you can return any number here and kore will attempt to send it.
|
|
0. data - {{ lua/string }} | {{ lua/nil }} - The data to return with the request. If `nil` is passed, the request's return will have an empty body.
|
|
|
|
No returns.
|
|
|
|
Example:
|
|
|
|
TODO
|
|
|
|
*/
|
|
/*
|
|
http_response(request::userdata, errcode::number, (data::string | nil))
|
|
*/
|
|
int
|
|
lhttp_response(lua_State *L){
|
|
size_t size;
|
|
const char *data;
|
|
if(lua_isnil(L,-1)){
|
|
data = NULL;
|
|
size = 0;
|
|
}else{
|
|
data = luaL_checklstring(L,-1,&size);
|
|
}
|
|
int httpcode = luaL_checkint(L,-2);
|
|
struct http_request *req = luaL_checkrequest(L,-3);
|
|
http_response(req,httpcode,data,size);
|
|
lua_pop(L,3);
|
|
return 0;
|
|
}
|
|
|
|
char response[] = "0\r\n\r\n";
|
|
|
|
/*Helpers for response coroutines*/
|
|
int
|
|
coroutine_iter_sent(struct netbuf *buf){
|
|
struct co_obj *obj = (struct co_obj*)buf->extra;
|
|
int ret;
|
|
lua_State *L = obj->L;
|
|
|
|
lua_getglobal(L,"coroutine");
|
|
lua_getfield(L,-1,"status");
|
|
lua_rawgeti(L,LUA_REGISTRYINDEX,obj->ref);
|
|
lua_call(L,1,1);
|
|
const char *status = luaL_checklstring(L,-1,NULL);
|
|
|
|
if(strcmp(status,"dead") == 0){
|
|
ret = KORE_RESULT_OK;
|
|
}else{
|
|
ret = coroutine_iter_next(obj);
|
|
}
|
|
|
|
if(ret == KORE_RESULT_RETRY){
|
|
ret = KORE_RESULT_OK;
|
|
}else{
|
|
if(obj->removed == 0){
|
|
http_start_recv(obj->c);
|
|
}
|
|
obj->c->hdlr_extra = NULL;
|
|
obj->c->disconnect = NULL;
|
|
obj->c->flags &= ~CONN_IS_BUSY;
|
|
net_send_queue(obj->c,response,strlen(response));
|
|
net_send_flush(obj->c);
|
|
free(obj);
|
|
}
|
|
return (ret);
|
|
}
|
|
|
|
int coroutine_iter_next(struct co_obj *obj){
|
|
lua_State *L = obj->L;
|
|
lua_getglobal(L,"coroutine");
|
|
lua_getfield(L,-1,"status");
|
|
lua_rawgeti(L,LUA_REGISTRYINDEX,obj->ref);
|
|
lua_call(L,1,1);
|
|
const char *status = luaL_checklstring(L,-1,NULL);
|
|
if(strcmp(status,"dead") == 0){
|
|
kore_log(LOG_ERR,"Coroutine was dead when it was passed to coroutine iter next");
|
|
lua_pushstring(L,"Coroutine was dead when passed to coroutine iter next");
|
|
lua_error(L);
|
|
}
|
|
lua_pop(L,lua_gettop(L));
|
|
lua_getglobal(L,"coroutine");
|
|
lua_getfield(L,-1,"resume");
|
|
lua_rawgeti(L,LUA_REGISTRYINDEX,obj->ref);
|
|
luaL_checktype(L,-1,LUA_TTHREAD);
|
|
int err = lua_pcall(L,1,2,0);
|
|
if(err != 0){
|
|
return (KORE_RESULT_ERROR);
|
|
}
|
|
if(!lua_toboolean(L,-2)){ //Runtime error
|
|
lua_pushstring(L,":\n");//"error",":"
|
|
lua_getglobal(L,"debug");//"error",":",{debug}
|
|
lua_getfield(L,-1,"traceback");//"error",":",{debug},debug.traceback()
|
|
lua_call(L,0,1);//"error",":",{debug},"traceback"
|
|
lua_remove(L,-2);//"error",":","traceback"
|
|
lua_concat(L,3);
|
|
size_t size;
|
|
const char *s = luaL_checklstring(L,-1,&size);
|
|
kore_log(LOG_ERR,"Error: %s\n",s);
|
|
lua_pop(L,lua_gettop(L));
|
|
return (KORE_RESULT_ERROR);
|
|
}
|
|
//No runtime error
|
|
if(lua_type(L,-1) == LUA_TSTRING){
|
|
size_t size;
|
|
const char *data = luaL_checklstring(L,-1,&size);
|
|
struct netbuf *nb;
|
|
struct kore_buf *kb = kore_buf_alloc(4096);
|
|
kore_buf_appendf(kb,"%lu\r\n",size);
|
|
kore_buf_append(kb,data,size);
|
|
kore_buf_appendf(kb,"\r\n");
|
|
//size_t ssize;
|
|
//char *sstr = kore_buf_stringify(kb,&ssize);
|
|
net_send_stream(obj->c, kb->data, kb->offset, coroutine_iter_sent, &nb);
|
|
nb->extra = obj;
|
|
lua_pop(L,lua_gettop(L));
|
|
kore_buf_free(kb);
|
|
return (KORE_RESULT_RETRY);
|
|
//return err == 0 ? (KORE_RESULT_OK) : (KORE_RESULT_RETRY);
|
|
}else if(lua_type(L,-1) == LUA_TNIL){
|
|
struct netbuf *nb;
|
|
struct kore_buf *kb = kore_buf_alloc(4096);
|
|
kore_buf_appendf(kb,"0\r\n\r\n");
|
|
net_send_queue(obj->c, kb->data, kb->offset);
|
|
net_send_stream(obj->c, response, strlen(response) + 0, coroutine_iter_sent, &nb);
|
|
nb->extra = obj;
|
|
|
|
lua_pop(L,lua_gettop(L));
|
|
kore_buf_free(kb);
|
|
return (KORE_RESULT_OK);
|
|
}else{
|
|
kore_log(LOG_CRIT,"Coroutine used for response returned something that was not a string:%s\n",lua_typename(L,lua_type(L,-1)));
|
|
return (KORE_RESULT_ERROR);
|
|
}
|
|
}
|
|
static void
|
|
coroutine_disconnect(struct connection *c){
|
|
kore_log(LOG_ERR,"Disconnect routine called\n");
|
|
struct co_obj *obj = (struct co_obj*)c->hdlr_extra;
|
|
lua_State *L = obj->L;
|
|
int ref = obj->ref;
|
|
int Lref = obj->Lref;
|
|
obj->removed = 1;
|
|
luaL_unref(L,LUA_REGISTRYINDEX,ref);
|
|
luaL_unref(L,LUA_REGISTRYINDEX,Lref);
|
|
c->hdlr_extra = NULL;
|
|
}
|
|
/*
|
|
The coroutine passed to this function should yield() the data to send to the
|
|
client, then return when done.
|
|
|
|
TODO: Broken and leaks memory
|
|
http_response_co(request::userdata, co::coroutine)
|
|
*/
|
|
int
|
|
lhttp_response_co(lua_State *L){
|
|
struct connection *c;
|
|
printf("Start response coroutine\n");
|
|
int coroutine_ref = luaL_ref(L,LUA_REGISTRYINDEX);
|
|
struct http_request *req = luaL_checkrequest(L,-1);
|
|
c = req->owner;
|
|
if(c->state == CONN_STATE_DISCONNECTING){
|
|
return 0;
|
|
}
|
|
lua_pop(L,1);
|
|
struct co_obj *obj = (struct co_obj*)malloc(sizeof(struct co_obj));
|
|
obj->removed = 0;
|
|
obj->L = lua_newthread(L);
|
|
obj->Lref = luaL_ref(L,LUA_REGISTRYINDEX);
|
|
obj->ref = coroutine_ref;
|
|
obj->c = c;
|
|
obj->c->disconnect = coroutine_disconnect;
|
|
|
|
obj->c->hdlr_extra = obj;
|
|
obj->c->flags |= CONN_IS_BUSY;
|
|
req->flags |= HTTP_REQUEST_NO_CONTENT_LENGTH;
|
|
http_response_header(req,"transfer-encoding","chunked");
|
|
http_response(req,200,NULL,0);
|
|
printf("About to call iter next\n");
|
|
coroutine_iter_next(obj);
|
|
printf("Done calling iter next\n");
|
|
return 0;
|
|
}
|
|
|
|
/* md
|
|
@name lua/kore
|
|
|
|
### http_method_text
|
|
|
|
Gets the http method the request was called with (ex `GET`, `POST`, ect.)
|
|
|
|
Parameters:
|
|
|
|
0. request - {{ http_request }} - The request to get the method string off of.
|
|
|
|
Returns:
|
|
|
|
0. method - {{ lua/string }} - The string from the request
|
|
|
|
Example:
|
|
TODO
|
|
*/
|
|
/*
|
|
http_method_text(request::userdata)::string
|
|
*/
|
|
int
|
|
lhttp_method_text(lua_State *L){
|
|
struct http_request *req = luaL_checkrequest(L,-1);
|
|
lua_pop(L,1);
|
|
const char *method = http_method_text(req->method);
|
|
lua_pushstring(L,method);
|
|
return 1;
|
|
}
|
|
|
|
/* md
|
|
@name lua/kore
|
|
|
|
### http_request_get_path
|
|
|
|
Gets the path from the end of the url.
|
|
|
|
Parameters:
|
|
|
|
0. request - {{http_request}} - The request to get the path from.
|
|
|
|
Returns:
|
|
|
|
0. path - {{ lua/string }} - The path part of the url.
|
|
|
|
Example:
|
|
|
|
local req = ...
|
|
print(http_request_get_path(req))
|
|
|
|
*/
|
|
/*
|
|
http_request_get_path(request::userdata)::string
|
|
*/
|
|
int
|
|
lhttp_request_get_path(lua_State *L){
|
|
struct http_request *req = luaL_checkrequest(L,-1);
|
|
lua_pop(L,1);
|
|
const char *path = req->path;
|
|
lua_pushstring(L,path);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
http_request_get_host(request::userdata)::string
|
|
*/
|
|
int
|
|
lhttp_request_get_host(lua_State *L){
|
|
struct http_request *req = luaL_checkrequest(L,-1);
|
|
lua_pop(L,1);
|
|
const char *host = req->host;
|
|
lua_pushstring(L,host);
|
|
return 1;
|
|
}
|
|
/*
|
|
http_request_populate_post(request::userdata)
|
|
*/
|
|
int
|
|
lhttp_request_populate_post(lua_State *L){
|
|
struct http_request *req = luaL_checkrequest(L,-1);
|
|
lua_pop(L,1);
|
|
http_populate_post(req);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
http_request_populate_qs(request::userdata)
|
|
*/
|
|
int
|
|
lhttp_request_populate_qs(lua_State *L){
|
|
struct http_request *req = luaL_checkrequest(L,-1);
|
|
lua_pop(L,1);
|
|
http_populate_qs(req);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
http_response_header(request::userdata, header::string, value::string)
|
|
*/
|
|
int
|
|
lhttp_response_header(lua_State *L){
|
|
const char *value = luaL_checkstring(L,-1);
|
|
const char *header = luaL_checkstring(L,-2);
|
|
struct http_request *req = luaL_checkrequest(L,-3);
|
|
lua_pop(L,3);
|
|
http_response_header(req, header, value);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
http_request_header(request::userdata, header::string)::(string || false, string)
|
|
*/
|
|
int
|
|
lhttp_request_header(lua_State *L){
|
|
const char *header = luaL_checkstring(L,-1);
|
|
struct http_request *req = luaL_checkrequest(L,-2);
|
|
lua_pop(L,2);
|
|
const char *data;
|
|
int err = http_request_header(req,header,&data);
|
|
if(err == KORE_RESULT_OK){
|
|
lua_pushstring(L,data);
|
|
return 1;
|
|
}else{
|
|
lua_pushboolean(L,0);
|
|
lua_pushstring(L,"Failed to get header: ");
|
|
lua_pushstring(L,header);
|
|
lua_concat(L,2);
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
http_response_cookie(req::userdata, name::string, value::string, path::string, expires::number, maxage::number)
|
|
*/
|
|
int
|
|
lhttp_response_cookie(lua_State *L){
|
|
//int flags = luaL_checkint(L,-1);//TODO: flags
|
|
int maxage = luaL_checkint(L,-1);
|
|
int expires = luaL_checkint(L,-2);
|
|
const char *path = luaL_checkstring(L,-3);
|
|
const char *value = luaL_checkstring(L,-4);
|
|
const char *name = luaL_checkstring(L,-5);
|
|
struct http_request *req = luaL_checkrequest(L,-6);
|
|
lua_pop(L,6);
|
|
http_response_cookie(req,name,value,path,expires,maxage,NULL);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
http_request_cookie(req::userdata, name::string)::string | nil
|
|
*/
|
|
int
|
|
lhttp_request_cookie(lua_State *L){
|
|
char *value;
|
|
const char *name = luaL_checkstring(L,-1);
|
|
struct http_request *req = luaL_checkrequest(L,-2);
|
|
lua_pop(L,2);
|
|
if(http_request_cookie(req,name,&value)){
|
|
lua_pushstring(L,value);
|
|
}else{
|
|
lua_pushnil(L);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
This method may fail, if it does, it will return false plus an error message
|
|
http_argument_get_string(request::userdata, name::string)::(string || false, string)
|
|
*/
|
|
int
|
|
lhttp_argument_get_string(lua_State *L){
|
|
const char *name = luaL_checkstring(L,-1);
|
|
struct http_request *req = luaL_checkrequest(L,-2);
|
|
lua_pop(L,2);
|
|
const char *value;
|
|
int err = http_argument_get_string(req,name,&value);
|
|
if(err == KORE_RESULT_OK){
|
|
lua_pushstring(L,value);
|
|
return 1;
|
|
}else{
|
|
lua_pushboolean(L,0);
|
|
lua_pushstring(L,"Failed to find argument: ");
|
|
lua_pushstring(L,name);
|
|
lua_concat(L,2);
|
|
return 2;
|
|
}
|
|
}
|
|
/*
|
|
Get the ip of this connection, ipv6 supported
|
|
http_request_get_ip(request::userdata)::string
|
|
*/
|
|
int
|
|
lhttp_request_get_ip(lua_State *L){
|
|
struct http_request *req = luaL_checkrequest(L,-1);
|
|
lua_pop(L,1);
|
|
char addr[INET6_ADDRSTRLEN];
|
|
switch(req->owner->family){
|
|
case AF_INET:
|
|
inet_ntop(
|
|
req->owner->family,
|
|
&(req->owner->addr.ipv4.sin_addr),
|
|
addr,
|
|
sizeof(addr)
|
|
);
|
|
break;
|
|
case AF_INET6:
|
|
inet_ntop(
|
|
req->owner->family,
|
|
&(req->owner->addr.ipv6.sin6_addr),
|
|
addr,
|
|
sizeof(addr)
|
|
);
|
|
break;
|
|
case AF_UNIX:
|
|
default:
|
|
lua_pushstring(L,"Tried to get IP from unknown socket family:");
|
|
lua_getglobal(L,"tostring");
|
|
lua_pushnumber(L,req->owner->family);
|
|
lua_call(L,1,1);
|
|
lua_concat(L,2);
|
|
lua_error(L);
|
|
break;
|
|
}
|
|
lua_pushstring(L,addr);
|
|
return 1;
|
|
|
|
}
|
|
|
|
/*
|
|
http_populate_cookies(request::userdata)
|
|
*/
|
|
int
|
|
lhttp_populate_cookies(lua_State *L){
|
|
struct http_request *req = luaL_checkrequest(L,-1);
|
|
lua_pop(L,1);
|
|
http_populate_cookies(req);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
http_populate_multipart_form(request::userdata)
|
|
*/
|
|
int
|
|
lhttp_populate_multipart_form(lua_State *L){
|
|
struct http_request *req = luaL_checkrequest(L,-1);
|
|
lua_pop(L,1);
|
|
http_populate_multipart_form(req);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
http_file_get(request::userdata, name::string)::string
|
|
*/
|
|
int
|
|
lhttp_file_get(lua_State *L){
|
|
const char *name = luaL_checkstring(L,-1);
|
|
struct http_request *req = luaL_checkrequest(L,-2);
|
|
lua_pop(L,2);
|
|
struct http_file *f = http_file_lookup(req, name);
|
|
if(f == NULL){
|
|
lua_pushstring(L,"No such file:");
|
|
lua_pushstring(L,name);
|
|
lua_concat(L,2);
|
|
lua_error(L);
|
|
}
|
|
char s[f->length + 1];
|
|
size_t read = http_file_read(f,s,f->length);
|
|
if(read < f->length){
|
|
lua_pushstring(L,"Failed to read contents of:");
|
|
lua_pushstring(L,name);
|
|
lua_concat(L,2);
|
|
lua_error(L);
|
|
}
|
|
s[f->length] = '\0';
|
|
lua_pushlstring(L,s,read);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
http_set_flags(request::userdata, flags::number)
|
|
*/
|
|
int
|
|
lhttp_set_flags(lua_State *L){
|
|
int flags = luaL_checkint(L,-1);
|
|
struct http_request *req = luaL_checkrequest(L,-2);
|
|
lua_pop(L,2);
|
|
req->flags = flags;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
http_get_flags(request::userdata) :: number
|
|
*/
|
|
int
|
|
lhttp_get_flags(lua_State *L){
|
|
struct http_request *req = luaL_checkrequest(L,-1);
|
|
lua_pop(L,1);
|
|
lua_pushnumber(L,req->flags);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
log(priority::integer,message::string) //formating must be done before calling
|
|
*/
|
|
int
|
|
lkore_log(lua_State *L){
|
|
const char *str = luaL_checkstring(L,-1);
|
|
int prio = luaL_checkint(L,-2);
|
|
lua_pop(L,2);
|
|
kore_log(prio,"%s",str);
|
|
return 0;
|
|
}
|
|
|
|
static const luaL_Reg kore_funcs[] = {
|
|
{"http_response", lhttp_response},
|
|
{"http_response_co", lhttp_response_co},
|
|
{"http_response_header", lhttp_response_header},
|
|
{"http_request_header", lhttp_request_header},
|
|
{"http_method_text",lhttp_method_text},
|
|
{"http_request_get_path",lhttp_request_get_path},
|
|
{"http_request_get_host",lhttp_request_get_host},
|
|
{"http_request_populate_post",lhttp_request_populate_post},
|
|
{"http_request_populate_qs",lhttp_request_populate_qs},
|
|
{"http_request_cookie",lhttp_request_cookie},
|
|
{"http_response_cookie",lhttp_response_cookie},
|
|
{"http_argument_get_string",lhttp_argument_get_string},
|
|
{"http_request_get_ip",lhttp_request_get_ip},
|
|
{"http_populate_cookies",lhttp_populate_cookies},
|
|
{"http_populate_multipart_form",lhttp_populate_multipart_form},
|
|
{"http_file_get",lhttp_file_get},
|
|
{"http_set_flags",lhttp_set_flags},
|
|
{"http_get_flags",lhttp_get_flags},
|
|
{"log",lkore_log},
|
|
{NULL,NULL}
|
|
};
|
|
|
|
static const luaL_Reg http_request_meta[] = {
|
|
{NULL,NULL}
|
|
};
|
|
|
|
void
|
|
load_kore_libs(lua_State *L){
|
|
luaL_newmetatable(L,"http_request");//{m_http_request}
|
|
lua_newtable(L);//{m_http_request},{}
|
|
luaL_register(L,NULL,http_request_meta);//{m_http_request},{meta}
|
|
lua_setfield(L,-2,"__index");//{m_http_request}
|
|
lua_pop(L,1);
|
|
|
|
lua_getglobal(L,"_G");
|
|
luaL_register(L,NULL,kore_funcs);
|
|
//Push priority constants for use with log()
|
|
LUA_PUSH_CONST(L,LOG_EMERG);
|
|
LUA_PUSH_CONST(L,LOG_ALERT);
|
|
LUA_PUSH_CONST(L,LOG_CRIT);
|
|
LUA_PUSH_CONST(L,LOG_ERR);
|
|
LUA_PUSH_CONST(L,LOG_WARNING);
|
|
LUA_PUSH_CONST(L,LOG_NOTICE);
|
|
LUA_PUSH_CONST(L,LOG_INFO);
|
|
LUA_PUSH_CONST(L,LOG_DEBUG);
|
|
|
|
//Push flags for use with http_set_flags()
|
|
LUA_PUSH_CONST(L,HTTP_REQUEST_COMPLETE);
|
|
LUA_PUSH_CONST(L,HTTP_REQUEST_DELETE);
|
|
LUA_PUSH_CONST(L,HTTP_REQUEST_SLEEPING);
|
|
LUA_PUSH_CONST(L,HTTP_REQUEST_EXPECT_BODY);
|
|
LUA_PUSH_CONST(L,HTTP_REQUEST_RETAIN_EXTRA);
|
|
LUA_PUSH_CONST(L,HTTP_REQUEST_NO_CONTENT_LENGTH);
|
|
LUA_PUSH_CONST(L,HTTP_REQUEST_AUTHED);
|
|
|
|
//Set a global variable "PRODUCTION" true or false
|
|
#ifdef BUILD_PROD
|
|
lua_pushboolean(L,1);
|
|
#else
|
|
lua_pushboolean(L,0);
|
|
#endif
|
|
lua_setfield(L,-2,"PRODUCTION");
|
|
lua_pop(L,1);
|
|
}
|