//*****************************************************************************
/*
Copyright © 2010 Jim Schimpf. Permission is hereby granted,
free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
//*****************************************************************************
/*
Module Name: HTTP interface for Firenet
Description: This uses libmicrohttpd to build a RESTful
interface to Firenet.
Library Extensions rest_start() - Start HTTP interface
rest_stop() - Stop HTTP interface
rest_close() - Close current connection & return data
rest_url() - Extract URL from current connection
rest_data() - Get data from current connection
rest_client_lock() - Lock server to current client
Revision History:
24 Oct 2010 Initial version
20 Nov 2010 UC both http & rest base
16 Dec 2010 Add client lock to lock the webserver to one client
7-Mar-2011 Dropping POST data reader, as post data does not work...
23-Dec-2011 [7b5a95ed75] Update to get rest base
size in structure
29-Jan-2012 [28bcb55998] Change Minnow so that it can work as a regular
web server
*/
//****************************************************************************
#include <ctype.h>
#include <time.h>
#include "lauxlib.h"
#include "lua.h"
#include "lualib.h"
#include "lstate.h"
#include "lapi.h"
#include "qdj.h"
#include "rest.h"
#include "restclient.h"
#if 0
#pragma mark -
#pragma mark DATA
#endif
#define RESTFUL_HANDLE_VALID 'rest'
typedef struct rest_dummy
{
unsigned long valid;
RESTQEL *rdata;
} RESTFUL_HANDLE;
static int rest_start( lua_State *L );
static int rest_stop( lua_State *L );
static int rest_open(lua_State *L);
static int rest_close(lua_State *L);
static int rest_url(lua_State *L);
static int rest_http_data(lua_State *L);
static int rest_client_lock(lua_State *L);
// Support
static int get_http_base( RSERV_DATA *rsrv );
static RESTFUL_HANDLE *checkRESTFUL_HANDLE( lua_State *L );
static void uppercase_it(char *ptr );
#if 0
#pragma mark -
#pragma mark Lua API
#endif
static struct luaL_reg httplib_f[] = {
{"start",rest_start},
{"stop",rest_stop},
{"open",rest_open},
{NULL,NULL}
};
static struct luaL_reg httplib_m[] = {
{"close",rest_close},
{"url",rest_url},
// {"data",rest_http_data}, // TBD
{"lock",rest_client_lock},
{NULL,NULL}
};
#if 0
#pragma mark --
#pragma mark -- LUA SUPPORT --
#endif
/*******************************************************************************
*
* luaopen_parselib - Attach HTTP class to LUA
*
* INPUT: L - Lua state
*
* OUTPUT: 1 - Stacked HTTP library table
*
*********************************************************************************/
LUALIB_API int luaopen_httplib (lua_State *L)
{
// (1) Create the class Metatable
luaL_newmetatable(L,"PP.http");
// (2) Put the __index element with the metatable in the
// metatable
lua_pushstring(L,"__index");
lua_pushvalue(L,-2); // Put the meta table (1) on stack
lua_settable(L,-3); // Add it to the metatable
// (3) Add the method table to the stack
luaL_openlib(L,NULL,httplib_m,0);
// (3) Add the class table to the system & return that it's on the
// stack
luaL_openlib(L, "http", httplib_f, 0);
return 1;
}
/*******************************************************************************
*
* RESTFUL_HANDLE *checkRESTFUL_HANDLE( lua_State *L ) - Check TOS for RESTFUL handle
*
* INPUT: NONE
* Examine TOS
*
* OUTPUT: Pointer to RESTFUL_HANDLE object if it was TOS
* NULL if not
*
*********************************************************************************/
static RESTFUL_HANDLE *checkRESTFUL_HANDLE( lua_State *L )
{
RESTFUL_HANDLE *h;
// (1) Check the TOS to see if we have TG.limo metatable
h = (RESTFUL_HANDLE *)luaL_checkudata(L,1,"PP.http");
luaL_argcheck( L,(h != NULL),1,"Restful object expected");
return( h );
}
#if 0
#pragma mark -
#pragma mark CREATE/DELETE API
#endif
//***************************************************************************
/*
* rest_start( http_port,http_base,http_site,rest_base)
*
* INPUT: http_port - # Value, port used for HTTP transactions
* http_base - ABSOLUTE loccation of site files (HTML file root)
* rest_base - RESTful tree root
*
* OUTPUT: 1 if started nil if not
*
*/
//***************************************************************************/
static int rest_start( lua_State *L )
{
size_t llen;
double dval;
char *ptr;
RSERV_DATA *rsrv = rest_data();
int rtnval = 0;
// (1) Pull in the values 1 at a time
// PORT, BASE then Restful base
if( lua_isnumber(L,1))
{
dval = lua_tonumber(L, 1);
rsrv->http_port = (int)dval;
// (2) Get the path to the files
if( lua_isstring(L,2 ) )
{
ptr = (char *)luaL_checklstring(L,2,&llen);
rsrv->http_site = (char *)MEMALLOC(llen+1);
if( rsrv->http_site != NULL )
{
// 3 Copy and set up http_base value
// if OK continue
memcpy(rsrv->http_site,ptr,llen);
if( get_http_base(rsrv) )
{
uppercase_it(rsrv->http_base);
// (3) Get the path to the RESTful location
// if present
if( lua_isstring(L,3 ) )
{
ptr = (char *)luaL_checklstring(L,3,&llen);
rsrv->rest_base = (char *)MEMALLOC(llen+1);
rsrv->len_rest_base = llen;
if( rsrv->rest_base != NULL )
{
memcpy(rsrv->rest_base,ptr,llen);
uppercase_it(rsrv->rest_base);
}
else
rsrv->len_rest_base = 0; // No rest base
}
else
{
// (3a) There is no REST input so handle that
rsrv->len_rest_base = 0;
rsrv->rest_base = NULL;
}
// (5) Start server
if( rest_server() == 0 )
{
// (6) Start client
if( rest_support_open() == 0 )
{
dval = 1.0;
lua_pushnumber( L,dval ); // Return true
rtnval = 1;
}
}
}
}
}
}
// (7) If rtnval == 0 call close to shut things down
if( rtnval == 0 )
rest_stop(L);
return( rtnval );
}
//***************************************************************************
/*
* rest_stop( )
*
* INPUT: NONE
*
* OUTPUT: Stop HTTP server
*/
//***************************************************************************/
static int rest_stop( lua_State *L )
{
RSERV_DATA *rsrv = rest_data();
// (1) Close com channel and stop server
rest_support_close();
rsrv->run_flag = 0;
rest_server_close();
usleep( 5000000 ); // Wait 5 seconds
// (2) Delete data
if( rsrv->http_base != NULL )
{
MEMFREE( rsrv->http_base );
rsrv->http_base = NULL;
}
if( rsrv->http_site != NULL )
{
MEMFREE( rsrv->http_site );
rsrv->http_site = NULL;
}
if( rsrv->rest_base != NULL )
{
MEMFREE( rsrv->rest_base );
rsrv->rest_base = NULL;
}
return( 0 );
}
#if 0
#pragma mark -
#pragma mark I/O API
#endif
//***************************************************************************
/*
* http.open() - NON-Blocking receive
*
* INPUT: NONE
*
* OUTPUT: NIL if no data, REST structure if receive
*
*/
//***************************************************************************/
static int rest_open(lua_State *L)
{
int rtnval = 0;
RESTQEL *packet;
RESTFUL_HANDLE *h;
// (1) Did we get anything ?
packet = rest_support_receive();
if( packet != NULL )
{
// (2) Build a lua user structure and return this to
// the user
h = (RESTFUL_HANDLE *)lua_newuserdata(L,sizeof(RESTFUL_HANDLE));
if( h != NULL )
{
h->valid = RESTFUL_HANDLE_VALID;
h->rdata = packet;
// (2a) Return handle to user, attach metatable to mark this
// as part of http class
luaL_getmetatable(L,"PP.http");
lua_setmetatable(L, -2);
rtnval = 1;
}
}
return rtnval;
}
//***************************************************************************
/*
* http.close(h,data) - Close http transaction and return data
*
* INPUT: data - Returned data for http I/O
*
* OUTPUT: NONE
*
*/
//***************************************************************************/
static int rest_close(lua_State *L)
{
RESTFUL_HANDLE *h = checkRESTFUL_HANDLE(L);
size_t llen = 0;
char *ptr = NULL;
char *send_data;
if( h != NULL )
{
// (2) Check for data
if( lua_isstring(L,2) )
{
ptr = (char *)luaL_checklstring(L, 2, &llen);
}
else
{
llen = 0;
ptr = "";
}
// (3) We must locally allocate the data
// to make MHD happy as it will deallocate
// this copy of the data
send_data = (char *)MEMALLOC( llen );
if( send_data != NULL )
{
memcpy(send_data,ptr,llen);
// (4) Now send the data off to the server
rest_support_send((int)llen,send_data,h->rdata);
}
}
return(0);
}
#if 0
#pragma mark -
#pragma mark HTTP DATA API
#endif
//***************************************************************************
/*
* h:url() - Return requested URL from restful handle
*
* INPUT: h - Restful handle
*
* OUTPUT: Lua numeric list with URL elements starting at REST start = [1]
* Followed by a number for the HTTP type of request
* See HTTP_TYPE in rest.h
*
* Uppercase all URL elements
*
*/
//***************************************************************************/
static int rest_url(lua_State *L)
{
int rtnval = 0;
RESTFUL_HANDLE *h = checkRESTFUL_HANDLE(L);
int i,n;
int count = 1;
double dval;
char *ptr;
char *url;
int list;
// (1) If we have a handle then proceed
if( h != NULL )
{
// (2) Create a table on the stack and set for return
lua_newtable(L);
// (3) Start the work on the URL,
n = strlen(h->rdata->url);
url = (char *) MEMALLOC(n+1);
if( url != NULL )
{
memcpy(url,h->rdata->url,n);
ptr = url;
for( i=0; i<n; i++ )
{
if( url[i] == '/' )
{
// We have a path element, so NULL out the
// the / and put the path onto the table
url[i] = '\0';
if( strlen( ptr ) > 0 )
{
list = lua_gettop(L);
dval = (double)count++; // Index
lua_pushnumber(L, dval);
uppercase_it( ptr ); // Upper case name
lua_pushstring(L, (const char *)ptr); // String value
lua_rawset(L,list); // Add to table
}
ptr = (char *)&url[i+1];
}
}
// Add in last path element if present
if( ptr != NULL && strlen(ptr) != 0 )
{
list = lua_gettop(L);
dval = (double)count++; // Index
lua_pushnumber(L, dval);
uppercase_it( ptr ); // Upper case name
lua_pushstring(L, (const char *)ptr); // String value
lua_rawset(L,list);
}
MEMFREE(url);
}
// Add n=count-1 to table
list = lua_gettop(L);
lua_pushstring(L,"n");
dval = (double)(count - 1);
lua_pushnumber(L, dval);
lua_rawset(L,list); // Add to table
// Put on HTTP type return as part of the result
dval = (double)h->rdata->type;
lua_pushnumber(L, dval);
rtnval = 2;
}
return rtnval;
}
// ********** POST DATA READS DON'T WORK **********
#if 0
//***************************************************************************
/*
* h:data() - Return data from restful handle
*
* INPUT: h - Restful handle
*
* OUTPUT: Lua data as a string
*
*/
//***************************************************************************/
static int rest_http_data(lua_State *L)
{
int rtnval = 0;
RESTFUL_HANDLE *h = checkRESTFUL_HANDLE(L);
// (1) Do we have a handle ?
if( h != NULL )
{
// (2) Ok then push the data as a string
lua_pushlstring(L, (const char *)h->rdata->data,h->rdata->size);
rtnval = 1;
}
return rtnval;
}
#endif
#if 0
#pragma mark -
#pragma mark HTTP LOGIN API
#endif
//***************************************************************************
/*
* h:rest_client_lock(flag) - Lock connection to current client (by IP)
*
* INPUT: h - Restful handle
* flag - If <> nil then lock client, if nil unlock
*
* OUTPUT: NONE
*
*/
//***************************************************************************/
static int rest_client_lock(lua_State *L)
{
int rtnval = 0;
RESTFUL_HANDLE *h = checkRESTFUL_HANDLE(L);
RSERV_DATA *rsrv = rest_data();
// (1) Do we have a handle ?
if( h != NULL )
{
// (2) Is this lock or unlock
if( lua_isnil(L,2) )
{
// DELETE the lock
rsrv->lock_addr_flag = 0; // Clear flag
}
else
{
// Lock requested, copy the current request IP
// to checker location and set the flag
strcpy(rsrv->client_ip_addr,rsrv->request_ip_addr);
rsrv->lock_addr_flag = 1; // Set flag
}
}
return rtnval;
}
#if 0
#pragma mark -
#pragma mark Internal API
#endif
//***************************************************************************
/*
* int get_http_site( RSERV_DATA *rsrv ) - Get HTTP base name from http_base
*
* INPUT: rsrv - Struct with filled out http_base
*
* OUTPUT: 1 if site name set up 0 if not
*
*/
//***************************************************************************/
static int get_http_base( RSERV_DATA *rsrv )
{
int rtnval = 0;
int n,i;
char *ptr;
// (1) Scan backward from the end of the http_site and find first "/"
n = strlen(rsrv->http_site);
for( i=n-1; i>=0; i-- )
{
if( rsrv->http_site[i] == '/' )
break;
}
// (2) At this point i -> '/' so move fwd 1 and set string pointer there
ptr = (char *)&(rsrv->http_site[i+1]);
// (3) Move this string into structure
n = strlen(ptr);
rsrv->http_base = (char *)MEMALLOC(n+1);
rsrv->len_http_base = n;
if( rsrv->http_base != NULL )
{
memcpy(rsrv->http_base,ptr,n);
rtnval = 1;
}
return rtnval;
}
//***************************************************************************
/*
* void uppercase_it(char *ptr ) - Uppercase the URL parts
*
* INPUT: ptr - Part of URL
*
* OUTPUT: NAME url part converted to uppercase
*
*/
//***************************************************************************/
static void uppercase_it(char *ptr )
{
int n = strlen(ptr);
int i;
for( i=0; i<n; i++ )
ptr[i] = toupper(ptr[i]);
}