hammerspoon/extensions/httpserver/libhttpserver.m

785 lines
27 KiB
Objective-C

#import <LuaSkin/LuaSkin.h>
#import "CocoaHTTPServer/HTTPServer.h"
#import "CocoaHTTPServer/HTTPMessage.h"
#import "CocoaHTTPServer/HTTPConnection.h"
#import "CocoaHTTPServer/HTTPDataResponse.h"
#import "CocoaHTTPServer/WebSocket.h"
#import "CocoaAsyncSocket/GCDAsyncSocket.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wexpansion-to-defined"
#import "CocoaLumberjack/CocoaLumberjack.h"
#pragma clang diagnostic pop
#import "MYAnonymousIdentity.h"
// From HTTPConnection.m
#define TIMEOUT_WRITE_ERROR 30
#define HTTP_FINAL_RESPONSE 91
// Defines
#define USERDATA_TAG "hs.httpserver"
#define get_item_arg(L, idx) ((httpserver_t *)luaL_checkudata(L, idx, USERDATA_TAG))
#define getUserData(L, idx) (__bridge HSHTTPServer *)((httpserver_t *)get_item_arg(L, idx))->server
static LSRefTable refTable;
// ObjC Class definitions
@interface HSWebSocket : WebSocket
@property int callback;
@end
@interface HSHTTPServer : HTTPServer
@property int fn;
@property NSUInteger maxBodySize;
@property SecIdentityRef sslIdentity;
@property (nonatomic, copy) NSString *httpPassword;
@property int wsCallback;
@property (nonatomic) NSString *wsPath;
@property HSWebSocket *ws;
@end
@interface HSHTTPDataResponse : HTTPDataResponse
@property int hsStatus;
@property (nonatomic, copy) NSDictionary *hsHeaders;
@end
@interface HSHTTPConnection : HTTPConnection
@end
@interface HSHTTPSConnection : HSHTTPConnection
@end
// ObjC Class implementations
@implementation HSHTTPServer
- (id)init {
self = [super init];
if (self) {
self.httpPassword = nil;
self.maxBodySize = 10 * 1024 * 1024; // set initial max body size to 10 MB
self.wsCallback = LUA_NOREF ;
self.fn = LUA_NOREF ;
}
return self;
}
@end
@implementation HSWebSocket
- (void)didOpen
{
[super didOpen];
[LuaSkin logInfo:@"Opened websocket connection"];
}
- (void)didReceiveData:(NSData *)msg
{
__block NSData *response = nil;
void (^responseCallbackBlock)(void) = ^{
if (self.callback != LUA_NOREF) {
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
_lua_stackguard_entry(skin.L);
[skin pushLuaRef:refTable ref:self.callback];
[skin pushNSObject:msg];
if (![skin protectedCallAndTraceback:1 nresults:1]) {
const char *errorMsg = lua_tostring(skin.L, -1);
[skin logError:[NSString stringWithFormat:@"hs.httpserver:websocket callback error: %s", errorMsg]];
// No need to lua_pop() here, nresults is 1 so the lua_pop() below catches successful results and error messages
} else {
response = [skin toNSObjectAtIndex:-1];
}
lua_pop(skin.L, 1);
_lua_stackguard_exit(skin.L);
}
};
// Make sure we do all the above Lua work on the main thread
if ([NSThread isMainThread]) {
responseCallbackBlock();
} else {
dispatch_sync(dispatch_get_main_queue(), responseCallbackBlock);
}
[self sendMessage:[NSString stringWithFormat:@"%@", response]];
}
- (void)didReceiveMessage:(NSString *)msg
{
__block NSData *response = nil;
void (^responseCallbackBlock)(void) = ^{
if (self.callback != LUA_NOREF) {
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
_lua_stackguard_entry(skin.L);
[skin pushLuaRef:refTable ref:self.callback];
lua_pushstring(skin.L, [msg UTF8String]);
if (![skin protectedCallAndTraceback:1 nresults:1]) {
const char *errorMsg = lua_tostring(skin.L, -1);
[skin logError:[NSString stringWithFormat:@"hs.httpserver:websocket callback error: %s", errorMsg]];
// No need to lua_pop() here, nresults is 1 so the lua_pop() below catches successful results and error messages
} else {
response = [skin toNSObjectAtIndex:-1];
}
lua_pop(skin.L, 1);
_lua_stackguard_exit(skin.L);
}
};
// Make sure we do all the above Lua work on the main thread
if ([NSThread isMainThread]) {
responseCallbackBlock();
} else {
dispatch_sync(dispatch_get_main_queue(), responseCallbackBlock);
}
[self sendMessage:[NSString stringWithFormat:@"%@", response]];
}
- (void)didClose
{
[super didClose];
[LuaSkin logInfo:@"Closed websocket connection"];
}
@end
@implementation HSHTTPDataResponse
- (NSInteger)status {
return self.hsStatus;
}
- (NSDictionary *)httpHeaders {
return self.hsHeaders;
}
@end
@implementation HSHTTPConnection
- (BOOL)supportsMethod:(NSString * __unused)method atPath:(NSString * __unused)path {
if ([method isEqualToString:@"POST"] || [method isEqualToString:@"PUT"])
return requestContentLength <= ((HSHTTPServer *)config.server).maxBodySize ;
return YES;
}
- (void)handleUnknownMethod:(NSString *)method
{
if (requestContentLength > ((HSHTTPServer *)config.server).maxBodySize) {
// Status code 413 - Request Entity Too Large
HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:413 description:nil version:HTTPVersion1_1];
[response setHeaderField:@"Content-Length" value:@"0"];
[response setHeaderField:@"Connection" value:@"close"];
NSData *responseData = [self preprocessErrorResponse:response];
[asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_FINAL_RESPONSE];
} else {
[super handleUnknownMethod:method];
}
}
- (NSData *)preprocessErrorResponse:(HTTPMessage *)response {
if ([response statusCode] == 413) {
NSString *msg = [NSString stringWithFormat:@"<html><head><title>Request Entity Too Large</title><head><body><H1>HTTP/1.1 413 Request Entity Too Large</H1><br/>The %@ method is not supported for requests larger than %lu bytes.<br/><hr/></body></html>", [request method], ((HSHTTPServer *)config.server).maxBodySize];
NSData *msgData = [msg dataUsingEncoding:NSUTF8StringEncoding];
[response setBody:msgData];
NSString *contentLengthStr = [NSString stringWithFormat:@"%lu", (unsigned long)[msgData length]];
[response setHeaderField:@"Content-Length" value:contentLengthStr];
}
return [super preprocessErrorResponse:response];
}
- (void)processBodyData:(NSData *)postDataChunk
{
[request appendData:postDataChunk];
}
- (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path {
__block int responseCode = 0;
__block NSMutableDictionary *responseHeaders = nil;
__block NSData *responseBody = nil;
void (^responseCallbackBlock)(void) = ^{
if (((HSHTTPServer *)self->config.server).fn != LUA_NOREF) {
LuaSkin *skin = [LuaSkin sharedWithState:NULL];
lua_State *L = skin.L;
_lua_stackguard_entry(L);
// add some headers for callback function to access
[self->request setHeaderField:@"X-Remote-Addr" value:self->asyncSocket.connectedHost];
[self->request setHeaderField:@"X-Remote-Port" value:[NSString stringWithFormat:@"%hu", self->asyncSocket.connectedPort]];
[self->request setHeaderField:@"X-Server-Addr" value:self->asyncSocket.localHost];
[self->request setHeaderField:@"X-Server-Port" value:[NSString stringWithFormat:@"%hu", self->asyncSocket.localPort]];
[skin pushLuaRef:refTable ref:((HSHTTPServer *)self->config.server).fn];
lua_pushstring(L, [method UTF8String]);
lua_pushstring(L, [path UTF8String]);
[skin pushNSObject:[self->request allHeaderFields]];
[skin pushNSObject:[self->request body] withOptions:LS_NSLuaStringAsDataOnly];
if (![skin protectedCallAndTraceback:4 nresults:3]) {
const char *errorMsg = lua_tostring(L, -1);
[skin logError:[NSString stringWithFormat:@"hs.httpserver:setCallback() callback error: %s", errorMsg]];
responseCode = 503;
responseBody = [NSData dataWithData:[@"An error occurred during hs.httpserver callback handling" dataUsingEncoding:NSUTF8StringEncoding]];
lua_pop(L, 1) ; // the error message
} else {
if (!(lua_type(L, -3) == LUA_TSTRING && lua_type(L, -2) == LUA_TNUMBER && lua_type(L, -1) == LUA_TTABLE)) {
[skin logError:@"hs.httpserver:setCallback() callbacks must return three values. A string for the response body, an integer response code, and a table of headers"];
responseCode = 503;
responseBody = [NSData dataWithData:[@"Callback handler returned invalid values" dataUsingEncoding:NSUTF8StringEncoding]];
} else {
responseBody = [skin toNSObjectAtIndex:-3 withOptions:LS_NSLuaStringAsDataOnly];
responseCode = (int)lua_tointeger(L, -2);
responseHeaders = [[NSMutableDictionary alloc] init];
BOOL headerTypeError = NO;
// Push nil onto the stack, which means that the table has moved from -1 to -2
lua_pushnil(L);
while (lua_next(L, -2)) {
if (lua_type(L, -1) == LUA_TSTRING && lua_type(L, -2) == LUA_TSTRING) {
NSString *key = [skin toNSObjectAtIndex:-2];
NSString *value = [skin toNSObjectAtIndex:-1];
[responseHeaders setObject:value forKey:key];
} else {
headerTypeError = YES;
}
lua_pop(L, 1);
}
if (headerTypeError) {
[skin logError:@"hs.httpserver:setCallback() callback returned a header table that contains non-strings"];
}
}
lua_pop(L, 3) ; // our results... don't leave them on the stack
}
_lua_stackguard_exit(L);
}
};
// Make sure we do all the above Lua work on the main thread
if ([NSThread isMainThread]) {
responseCallbackBlock();
} else {
dispatch_sync(dispatch_get_main_queue(), responseCallbackBlock);
}
HSHTTPDataResponse *response = [[HSHTTPDataResponse alloc] initWithData:responseBody];
response.hsStatus = responseCode;
response.hsHeaders = responseHeaders;
return response;
}
- (BOOL)isPasswordProtected:(NSString * __unused)path {
return ((HSHTTPServer *)config.server).httpPassword != nil;
}
- (BOOL)useDigestAccessAuthentication {
return YES;
}
- (NSString *)passwordForUser:(NSString * __unused)username {
return ((HSHTTPServer *)config.server).httpPassword;
}
- (WebSocket *)webSocketForURI:(NSString *)path
{
if([path isEqualToString:((HSHTTPServer *)config.server).wsPath])
{
HSWebSocket *ws = [[HSWebSocket alloc] initWithRequest:request socket:asyncSocket];
ws.callback = ((HSHTTPServer *)config.server).wsCallback;
((HSHTTPServer *)config.server).ws = ws;
return ws;
}
return [super webSocketForURI:path];
}
@end
@implementation HSHTTPSConnection
- (BOOL)isSecureServer {
return YES;
}
- (NSArray *)sslIdentityAndCertificates {
NSArray *chain;
NSError *certError;
SecIdentityRef identity = MYGetOrCreateAnonymousIdentity(@"Hammerspoon HTTP Server", 20 * kMYAnonymousIdentityDefaultExpirationInterval, &certError);
if (!identity) {
NSLog(@"ERROR: Unable to find/generate a certificate: %@", certError);
return nil;
}
((HSHTTPServer *)config.server).sslIdentity = identity;
chain = [NSArray arrayWithObject:(__bridge id)identity];
return chain;
}
// We're overriding this because CocoaHTTPServer seems to have not been updated for deprecated APIs
- (void)startConnection
{
// Override me to do any custom work before the connection starts.
//
// Be sure to invoke [super startConnection] when you're done.
//HTTPLogTrace();
if ([self isSecureServer])
{
// We are configured to be an HTTPS server.
// That is, we secure via SSL/TLS the connection prior to any communication.
NSArray *certificates = [self sslIdentityAndCertificates];
if ([certificates count] > 0)
{
// All connections are assumed to be secure. Only secure connections are allowed on this server.
NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:3];
// Configure this connection as the server
[settings setObject:[NSNumber numberWithBool:YES]
forKey:(NSString *)kCFStreamSSLIsServer];
[settings setObject:certificates
forKey:(NSString *)kCFStreamSSLCertificates];
// Configure this connection to use the highest possible SSL level
[settings setObject:[NSNumber numberWithInteger:kTLSProtocol12] forKey:GCDAsyncSocketSSLProtocolVersionMin];
[settings setObject:[NSNumber numberWithInteger:kTLSProtocol12] forKey:GCDAsyncSocketSSLProtocolVersionMax];
[asyncSocket startTLS:settings];
}
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
[(HTTPConnection *)self performSelector:@selector(startReadingRequest)];
#pragma clang diagnostic pop
}
@end
typedef struct _httpserver_t {
void *server;
} httpserver_t;
/// hs.httpserver.new([ssl], [bonjour]) -> object
/// Function
/// Creates a new HTTP or HTTPS server
///
/// Parameters:
/// * ssl - An optional boolean. If true, the server will start using HTTPS. Defaults to false.
/// * bonjour - An optional boolean. If true, the server will advertise itself with Bonjour. Defaults to true. Note that in order to change this, you must supply a true or false value for the `ssl` argument.
///
/// Returns:
/// * An `hs.httpserver` object
///
/// Notes:
/// * By default, the server will start on a random TCP port and advertise itself with Bonjour. You can check the port with `hs.httpserver:getPort()`
/// * By default, the server will listen on all network interfaces. You can override this with `hs.httpserver:setInterface()` before starting the server
/// * Currently, in HTTPS mode, the server will use a self-signed certificate, which most browsers will warn about. If you want/need to be able to use `hs.httpserver` with a certificate signed by a trusted Certificate Authority, please file an bug on Hammerspoon requesting support for this.
static int httpserver_new(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TBOOLEAN | LS_TOPTIONAL, LS_TBOOLEAN | LS_TOPTIONAL, LS_TBREAK];
BOOL useSSL = (lua_type(L, 1) == LUA_TBOOLEAN) ? (BOOL)lua_toboolean(L, 1) : false;
BOOL useBonjour = (lua_type(L, 2) == LUA_TBOOLEAN) ? (BOOL)lua_toboolean(L, 2) : true;
httpserver_t *httpServer = lua_newuserdata(L, sizeof(httpserver_t));
memset(httpServer, 0, sizeof(httpserver_t));
HSHTTPServer *server = [[HSHTTPServer alloc] init];
if (useSSL) {
[server setConnectionClass:[HSHTTPSConnection class]];
} else {
[server setConnectionClass:[HSHTTPConnection class]];
}
if (useBonjour) [server setType:@"_http._tcp."];
server.fn = LUA_NOREF;
httpServer->server = (__bridge_retained void *)server;
luaL_getmetatable(L, USERDATA_TAG);
lua_setmetatable(L, -2);
return 1;
}
/// hs.httpserver:websocket(path, callback) -> object
/// Method
/// Enables a websocket endpoint on the HTTP server
///
/// Parameters:
/// * path - A string containing the websocket path such as '/ws'
/// * callback - A function returning a string for each recieved websocket message
///
/// Returns:
/// * The `hs.httpserver` object
///
/// Notes:
/// * The callback is passed one string parameter containing the received message
/// * The callback must return a string containing the response message
/// * Given a path '/mysock' and a port of 8000, the websocket URL is as follows:
/// * ws://localhost:8000/mysock
/// * wss://localhost:8000/mysock (if SSL enabled)
static int httpserver_websocket(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TSTRING, LS_TFUNCTION, LS_TBREAK];
HSHTTPServer *server = getUserData(L, 1);
server.wsPath = [skin toNSObjectAtIndex:2];
server.wsCallback = [skin luaUnref:refTable ref:server.wsCallback];
lua_pushvalue(L, 3);
server.wsCallback = [skin luaRef:refTable];
lua_pushvalue(L, 1);
return 1;
}
/// hs.httpserver:send(message) -> object
/// Method
/// Sends a message to the websocket client
///
/// Parameters:
/// * message - A string containing the message to send
///
/// Returns:
/// * The `hs.httpserver` object
static int httpserver_send(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TSTRING, LS_TBREAK];
HSHTTPServer *server = getUserData(L, 1);
[server.ws sendMessage:[skin toNSObjectAtIndex:2]];
lua_pushvalue(L, 1);
return 1;
}
/// hs.httpserver:setCallback([callback]) -> object
/// Method
/// Sets the request handling callback for an HTTP server object
///
/// Parameters:
/// * callback - An optional function that will be called to process each incoming HTTP request, or nil to remove an existing callback. See the notes section below for more information about this callback
///
/// Returns:
/// * The `hs.httpserver` object
///
/// Notes:
/// * The callback will be passed four arguments:
/// * A string containing the type of request (i.e. `GET`/`POST`/`DELETE`/etc)
/// * A string containing the path element of the request (e.g. `/index.html`)
/// * A table containing the request headers
/// * A string containing the raw contents of the request body, or the empty string if no body is included in the request.
/// * The callback *must* return three values:
/// * A string containing the body of the response
/// * An integer containing the response code (e.g. 200 for a successful request)
/// * A table containing additional HTTP headers to set (or an empty table, `{}`, if no extra headers are required)
///
/// Notes:
/// * A POST request, often used by HTML forms, will store the contents of the form in the body of the request.
static int httpserver_setCallback(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
HSHTTPServer *server = getUserData(L, 1);
switch (lua_type(L, 2)) {
case LUA_TFUNCTION:
server.fn = [skin luaUnref:refTable ref:server.fn];
lua_pushvalue(L, 2);
server.fn = [skin luaRef:refTable];
break;
case LUA_TNIL:
case LUA_TNONE:
server.fn = [skin luaUnref:refTable ref:server.fn];
break;
default:
[skin logError:@"Unknown type passed to hs.httpserver:setCallback(). Argument must be a function or nil"];
break;
}
lua_pushvalue(L, 1);
return 1;
}
/// hs.httpserver:maxBodySize([size]) -> object | current-value
/// Method
/// Get or set the maximum allowed body size for an incoming HTTP request.
///
/// Parameters:
/// * size - An optional integer value specifying the maximum body size allowed for an incoming HTTP request in bytes. Defaults to 10485760 (10 MB).
///
/// Returns:
/// * If a new size is specified, returns the `hs.httpserver` object; otherwise the current value.
///
/// Notes:
/// * Because the Hammerspoon http server processes incoming requests completely in memory, this method puts a limit on the maximum size for a POST or PUT request.
static int httpserver_maxBodySize(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TNUMBER | LS_TINTEGER | LS_TOPTIONAL, LS_TBREAK];
HSHTTPServer *server = getUserData(L, 1);
if (lua_gettop(L) == 2) {
server.maxBodySize = (NSUInteger)lua_tointeger(L, 2);
lua_pushvalue(L, 1);
} else {
lua_pushinteger(L, (lua_Integer)server.maxBodySize);
}
return 1;
}
/// hs.httpserver:setPassword([password]) -> object
/// Method
/// Sets a password for an HTTP server object
///
/// Parameters:
/// * password - An optional string that contains the server password, or nil to remove an existing password
///
/// Returns:
/// * The `hs.httpserver` object
///
/// Notes:
/// * It is not currently possible to set multiple passwords for different users, or passwords only on specific paths
static int httpserver_setPassword(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TSTRING | LS_TNIL | LS_TOPTIONAL, LS_TBREAK];
HSHTTPServer *server = getUserData(L, 1);
switch (lua_type(L, 2)) {
case LUA_TNIL:
case LUA_TNONE:
server.httpPassword = nil;
break;
case LUA_TSTRING:
server.httpPassword = [skin toNSObjectAtIndex:2];
break;
default:
[skin logError:@"Unknown type passed to hs.httpserver:setPassword(). Argument must be a string or nil"];
break;
}
lua_pushvalue(L, 1);
return 1;
}
/// hs.httpserver:start() -> object
/// Method
/// Starts an HTTP server object
///
/// Parameters:
/// * None
///
/// Returns:
/// * The `hs.httpserver` object
static int httpserver_start(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
HSHTTPServer *server = getUserData(L, 1);
if (server.fn == LUA_NOREF) {
[skin logError:@"hs.httpserver:start() called with no callback set. You must call hs.httpserver:setCallback() first"];
} else {
NSError *error = nil;
if (![server start:&error]) {
[skin logError:[NSString stringWithFormat:@"hs.httpserver:start() Unable to start object: %@", error]];
}
}
lua_pushvalue(L, 1);
return 1;
}
/// hs.httpserver:stop() -> object
/// Method
/// Stops an HTTP server object
///
/// Parameters:
/// * None
///
/// Returns:
/// * The `hs.httpserver` object
static int httpserver_stop(lua_State *L) {
HSHTTPServer *server = getUserData(L, 1);
[server stop];
lua_pushvalue(L, 1);
return 1;
}
/// hs.httpserver:getPort() -> number
/// Method
/// Gets the TCP port the server is configured to listen on
///
/// Parameters:
/// * None
///
/// Returns:
/// * A number containing the TCP port
static int httpserver_getPort(lua_State *L) {
HSHTTPServer *server = getUserData(L, 1);
lua_pushinteger(L, [server listeningPort]);
return 1;
}
/// hs.httpserver:setPort(port) -> object
/// Method
/// Sets the TCP port the server is configured to listen on
///
/// Parameters:
/// * port - An integer containing a TCP port to listen on
///
/// Returns:
/// * The `hs.httpserver` object
static int httpserver_setPort(lua_State *L) {
HSHTTPServer *server = getUserData(L, 1);
[server setPort:(UInt16)luaL_checkinteger(L, 2)];
lua_pushvalue(L, 1);
return 1;
}
/// hs.httpserver:getInterface() -> string or nil
/// Method
/// Gets the network interface the server is configured to listen on
///
/// Parameters:
/// * None
///
/// Returns:
/// * A string containing the network interface name, or nil if the server will listen on all interfaces
static int httpserver_getInterface(lua_State *L) {
HSHTTPServer *server = getUserData(L, 1);
lua_pushstring(L, [[server interface] UTF8String]);
return 1;
}
/// hs.httpserver:setInterface(interface) -> object
/// Method
/// Sets the network interface the server is configured to listen on
///
/// Parameters:
/// * interface - A string containing an interface name
///
/// Returns:
/// * The `hs.httpserver` object
///
/// Notes:
/// * As well as real interface names (e.g. `en0`) the following values are valid:
/// * An IP address of one of your interfaces
/// * localhost
/// * loopback
/// * nil (which means all interfaces, and is the default)
static int httpserver_setInterface(lua_State *L) {
HSHTTPServer *server = getUserData(L, 1);
if (lua_isnoneornil(L, 2)) {
[server setInterface:nil];
} else {
[server setInterface:[NSString stringWithUTF8String:luaL_checkstring(L, 2)]];
}
lua_pushvalue(L, 1);
return 1;
}
/// hs.httpserver:getName() -> string
/// Method
/// Gets the Bonjour name the server is configured to advertise itself as
///
/// Parameters:
/// * None
///
/// Returns:
/// * A string containing the Bonjour name of this server
///
/// Notes:
/// * This is not the hostname of the server, just its name in Bonjour service lists (e.g. Safari's Bonjour bookmarks menu)
static int httpserver_getName(lua_State *L) {
HSHTTPServer *server = getUserData(L, 1);
lua_pushstring(L, [[server name] UTF8String]);
return 1;
}
/// hs.httpserver:setName(name) -> object
/// Method
/// Sets the Bonjour name the server should advertise itself as
///
/// Parameters:
/// * name - A string containing the Bonjour name for the server
///
/// Returns:
/// * The `hs.httpserver` object
///
/// Notes:
/// * This is not the hostname of the server, just its name in Bonjour service lists (e.g. Safari's Bonjour bookmarks menu)
static int httpserver_setName(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
[skin checkArgs:LS_TUSERDATA, USERDATA_TAG, LS_TSTRING, LS_TBREAK];
HSHTTPServer *server = getUserData(L, 1);
[server setName:[skin toNSObjectAtIndex:2]];
lua_pushvalue(L, 1);
return 1;
}
static int httpserver_objectGC(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L] ;
httpserver_t *httpServer = get_item_arg(L, 1);
HSHTTPServer *server = (__bridge_transfer HSHTTPServer *)httpServer->server;
[server stop];
server.fn = [skin luaUnref:refTable ref:server.fn];
server.wsCallback = [skin luaUnref:refTable ref:server.wsCallback];
server = nil;
return 0;
}
static int userdata_tostring(lua_State* L) {
HSHTTPServer *server = getUserData(L, 1);
NSString *theName = [server name] ;
int thePort = [server listeningPort] ;
if (!theName) theName = @"unnamed" ;
lua_pushstring(L, [[NSString stringWithFormat:@"%s: %@:%d (%p)", USERDATA_TAG, theName, thePort, lua_topointer(L, 1)] UTF8String]) ;
return 1 ;
}
static const luaL_Reg httpserverLib[] = {
{"new", httpserver_new},
{NULL, NULL}
};
static const luaL_Reg httpserverObjectLib[] = {
{"websocket", httpserver_websocket},
{"send", httpserver_send},
{"start", httpserver_start},
{"stop", httpserver_stop},
{"getPort", httpserver_getPort},
{"setPort", httpserver_setPort},
{"getInterface", httpserver_getInterface},
{"setInterface", httpserver_setInterface},
{"getName", httpserver_getName},
{"setName", httpserver_setName},
{"setCallback", httpserver_setCallback},
{"setPassword", httpserver_setPassword},
{"maxBodySize", httpserver_maxBodySize},
{"__tostring", userdata_tostring},
{"__gc", httpserver_objectGC},
{NULL, NULL}
};
int luaopen_hs_libhttpserver(lua_State *L) {
LuaSkin *skin = [LuaSkin sharedWithState:L];
refTable = [skin registerLibraryWithObject:"hs.httpserver" functions:httpserverLib metaFunctions:nil objectFunctions:httpserverObjectLib];
[DDLog addLogger:[DDOSLogger sharedInstance]];
return 1;
}