smr/src/libkore.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);
}