commit 854fb6548b2c7cb3b960a17ef599f2ae8f679c75 Author: sandyx Date: Tue Nov 12 11:49:19 2024 -0600 it works diff --git a/src/html.h b/src/html.h new file mode 100644 index 0000000..5746475 --- /dev/null +++ b/src/html.h @@ -0,0 +1,15 @@ +#ifndef HTML_H +#define HTML_H + +#import + +//cheap html wrapper +@interface NSString (HTML) + +-(id) wrapWithTag: (NSString *) tag; +-(id) wrapWithTag: (NSString *) tag href: (NSString *) ref; +-(id) appendNewline; + +@end + +#endif diff --git a/src/html.m b/src/html.m new file mode 100644 index 0000000..4d1082e --- /dev/null +++ b/src/html.m @@ -0,0 +1,29 @@ +#import "html.h" + +//should maybe free the buffers + +@implementation NSString (HTML) +-(id) wrapWithTag: (NSString *) tag { + int len = [self length] + [tag length] + [@"<" length] + [@"" length] * 2); + char *buffer = calloc(len, sizeof(char) + 1); + char *tag_utf8 = [tag UTF8String]; + sprintf(buffer, "<%s>%s", tag_utf8, [self UTF8String], tag_utf8); + NSString *ret = [NSString stringWithUTF8String: buffer]; + //free(buffer); + return ret; +} + +-(id) wrapWithTag: (NSString *) tag href: (NSString *) ref { + int len = [self length] + [tag length] + [ref length] + [@"<" length] + [@"" length] * 2) + 1; + char *buffer = calloc(len, sizeof(char) + 1); + char *tag_utf8 = [tag UTF8String]; + sprintf(buffer, "<%s href=\"%s\">%s", tag_utf8, [ref UTF8String], [self UTF8String], tag_utf8); + NSString *ret = [NSString stringWithUTF8String: buffer]; + //free(buffer); + return ret; +} + +-(id) appendNewline { + return [self stringByAppendingString: @"\r\n"]; +} +@end diff --git a/src/http.h b/src/http.h new file mode 100644 index 0000000..3b4047a --- /dev/null +++ b/src/http.h @@ -0,0 +1,43 @@ +#ifndef HTTP_H +#define HTTP_H + +#import "socket.h" +#import "lambda.h" + +static NSString *httpHeader = @"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"; +static NSString *httpMultipart = @"HTTP/1.1 200 OK\r\nContent-Type: multipart/form-data\r\n\r\n"; + +enum RequestType { + GET = 0, + POST = 1, +}; + +@interface HTTPRequest : NSObject { + NSString *type; + NSString *uri; + NSString *host; +} +@property (assign,nonatomic) NSString *type; +@property (assign,nonatomic) NSString *uri; +@property (assign,nonatomic) NSString *host; ++(id) parseRequest: (NSString *) req; +@end + +@interface HTTPServer : NSObject { + NSSocket *sock; + NSMutableDictionary *handlers; + bool open; +} + ++(id) httpServerWithPort: (int) port; +-(id) read; +-(BOOL) listen; +-(BOOL) write: (NSString *) data; +-(BOOL) writeData: (NSData *) data; +-(void) close; +-(void) assignHandler: (NSLambda *) handler to: (NSString *) url; +-(void) handleRequest: (HTTPRequest *) req; +-(void) handleURI: (NSString *) URI; +@end + +#endif diff --git a/src/http.m b/src/http.m new file mode 100644 index 0000000..f73f644 --- /dev/null +++ b/src/http.m @@ -0,0 +1,99 @@ +#import +#import "http.h" + +//cheap parsing +@implementation HTTPRequest +@synthesize type; +@synthesize uri; +@synthesize host; + +//cheap parsing ++(id) parseRequest: (NSString *) req { + HTTPRequest *request = [[HTTPRequest alloc] init]; + [request autorelease]; + NSArray *arr = [req componentsSeparatedByString: @"\n"]; + NSArray *arr2 = [[arr objectAtIndex: 0] componentsSeparatedByString: @" "]; + request->type = [arr objectAtIndex: 0]; + if ([arr count] > 1) + request->host = [arr objectAtIndex: 1]; + if ([arr2 count] > 1) + request->uri = [arr2 objectAtIndex: 1]; + return request; +} +@end + +id default_handler(id server ,id obj) { + [server write: @"idk how to handle that"]; + return server; +} + +@implementation HTTPServer ++(id) httpServerWithPort: (int) port { + HTTPServer *server = [[super alloc] init]; + server->sock = [NSSocket socketWithPort: port]; + server->handlers = [NSMutableDictionary dictionary]; + server->open = false; + + [server assignHandler: [NSLambda lambdaWithReducer: default_handler] to: @"DEFAULT"]; + return server; +} + +-(id) read { + return [sock read]; +} + +-(BOOL) listen { + return [sock listen]; +} + +-(BOOL) write: (NSString *) data { + NSString *resp = data; + + if (!open) { + open = true; + resp = [httpHeader stringByAppendingString: data]; + } + + [sock write: resp]; +} + +-(BOOL) writeData: (NSData *) data { + if (!open) { + open = true; + [sock write: httpMultipart]; + } + + [sock writeData: data]; +} + +-(void) close { + open = false; + [sock close]; +} + +-(void) assignHandler: (NSLambda *) handler to: (NSString *) url { + [handlers setObject: handler forKey: url]; +} + +-(void) handleRequest: (HTTPRequest *) req { + id handler = [handlers objectForKey: [req uri]]; + + if (handler != nil) { + id nothing = [handler reduce: self with: [req uri]]; + return; + } + + id nothing = [[handlers objectForKey: @"DEFAULT"] call]; +} + +-(void) handleURI: (NSString *) URI { + id handler = [handlers objectForKey: URI]; + + if (handler != nil) { + id nothing = [handler reduce: self with: URI]; + return; + } + + id nothing = [[handlers objectForKey: @"DEFAULT"] call]; +} +@end diff --git a/src/lambda.h b/src/lambda.h new file mode 100644 index 0000000..5e284a0 --- /dev/null +++ b/src/lambda.h @@ -0,0 +1,35 @@ +#ifndef LAMBDA_H +#define LAMBDA_H + +typedef id (*lambda)(id); +typedef id (*sequence[])(id); +typedef id (*reducer)(id , id); + +typedef union Lambda { + id (*lambdaNoArgs)(void); + id (*lambda)(id); + id (*reduce)(id, id); +} Anon; + +//experimental +@interface NSLambda : NSObject { + Anon anon; + //reducer lambdaFunction; +} +@property Anon anon; + ++(id) lambdaWithLambda: (lambda) lambda; ++(id) lambdaWithReducer: (reducer) reducer; +-(id) call; +-(id) reduce: (id) acc with: (id) obj; +@end + +@interface NSMutableArray (Lambda) +-(NSMutableArray *) map: (lambda) fn; +-(NSMutableArray *) filter: (lambda) fn; +-(NSMutableArray *) reduce: (reducer) fn withAccumulator: (id) acc; +-(NSMutableArray *) mapSequence: (sequence) fnArr count: (int) n; +-(id) clean; +@end + +#endif diff --git a/src/lambda.m b/src/lambda.m new file mode 100644 index 0000000..041a027 --- /dev/null +++ b/src/lambda.m @@ -0,0 +1,88 @@ +#import +#import "lambda.h" + +@implementation NSLambda +@synthesize anon; + ++(id) lambdaWithLambda: (lambda) lambda { + NSLambda *l = [NSLambda new]; + [l autorelease]; + [l setAnon: (Anon)lambda]; + return l; +} + ++(id) lambdaWithReducer: (reducer) reducer { + NSLambda *l = [NSLambda new]; + [l autorelease]; + [l setAnon: (Anon)reducer]; + return l; +} + +-(id) call { + return anon.lambda(@"test"); +} + +-(id) reduce: (id) acc with: (id) obj { + return anon.reduce(acc, obj); +} +@end + +@implementation NSMutableArray (Lambda) + +//map function to array, return array of same length +-(NSMutableArray *) map: (lambda) fn { + NSMutableArray *r = [[NSMutableArray alloc] init]; + [r autorelease]; + + for (id object in self) { + [r addObject: fn(object)]; + } + + return r; +} + +//filter an array +-(NSMutableArray *) filter: (lambda) fn { + NSMutableArray *r = [[NSMutableArray alloc] init]; + [r autorelease]; + + for (id object in self) { + id new = fn(object); + + if ([new class] != [NSNull class]) { + [r addObject: new]; + } else { + [new release]; + } + } + + return r; +} + +-(NSMutableArray *) reduce: (reducer) fn withAccumulator: (id) acc { + for (id object in self) { + acc = fn(acc, object); + } + + return acc; +} + +-(NSMutableArray *) mapSequence: (sequence) fnArr count: (int) n { + id r = self; + + for (int i = 0; i < n; i++) { + r = [r map: *fnArr[i]]; + } + + return r; +} + +//clean the nulls from the array +-(id) clean { + for (int i = 0; i < [self count]; i++) { + [[self objectAtIndex: i] class] == [NSNull class] ? [self removeObjectAtIndex: i] : @"yes"; + } + + return self; +} +@end diff --git a/src/main.m b/src/main.m new file mode 100644 index 0000000..95d2ece --- /dev/null +++ b/src/main.m @@ -0,0 +1,142 @@ +#import +#import +#import +#import +#import "socket.h" +#import "lambda.h" +#import "http.h" +#import "html.h" +#define PORT 8080 + +id litag(id obj) { + return [obj wrapWithTag: @"li"]; +} + +id atag(id obj) { + return [obj wrapWithTag: @"a" href: obj]; +} + +id nl(id obj) { + return [obj appendNewline]; +} + +id localize(id obj) { + return [@"." stringByAppendingString: obj]; +} + +id slash(id obj) { + return [@"/" stringByAppendingString: obj]; +} + +id strip_slash(id obj) { + return [obj stringByReplacingOccurrencesOfString: @"/" withString: @""]; +} + +id not_exist(HTTPServer *server, id obj) { + [server write: @"404 file not found"]; + return NULL; +} + +id serve_file(HTTPServer *server, id obj) { + NSFileManager *fm = [NSFileManager defaultManager]; + BOOL isDir; + BOOL fileExists = [fm fileExistsAtPath: localize(obj) isDirectory: &isDir]; + + if (fileExists == NO) { + NSLog(@"%@ does not exist.", obj); + [server write: @"file does not exist"]; + return server; + } + + NSLog(@"sending data with: %@", localize(obj)); + NSData *data = [NSData dataWithContentsOfFile: localize(obj)]; + [server writeData: data]; + return server; +} + +id serve_directory(HTTPServer *server, NSString *dir) { + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *path = localize(dir); + NSArray *contents = [fm directoryContentsAtPath: path]; + NSMutableArray *mutContents = [NSMutableArray arrayWithArray: contents]; + [mutContents autorelease]; + NSMutableArray *newContents = [[NSMutableArray alloc] init]; + [newContents autorelease]; + + for (id thing in mutContents) { + if ([dir isEqualToString: @"/"]) + [newContents addObject: slash(thing)]; + else + [newContents addObject: [dir stringByAppendingString: slash(thing)]]; + } + + sequence seq = {atag, litag}; + id list = [[newContents mapSequence: seq count: 2] componentsJoinedByString: @"\r\n"]; + NSLog(@"sending directory: %@", dir); + [server write: [list wrapWithTag: @"ul"]]; + return server; +} + +void initialize_subdir(HTTPServer *server, NSString *root){ + NSFileManager *fm = [NSFileManager defaultManager]; + [fm autorelease]; + NSArray *contents = [fm directoryContentsAtPath: strip_slash(root)]; + + for (id file in contents) { + BOOL isDir; + BOOL exists = [fm fileExistsAtPath: file isDirectory: &isDir]; + + NSString *slashFile = [root stringByAppendingString: slash(file)]; + + if (isDir == YES) { + initialize_subdir(server, slashFile); + [server assignHandler: [NSLambda lambdaWithReducer: serve_directory] to: slashFile]; + } else { + [server assignHandler: [NSLambda lambdaWithReducer: serve_file] to: slashFile]; + } + } + + return; +} + +void initialize_root(HTTPServer *server, NSString *root) { + [server assignHandler: [NSLambda lambdaWithReducer: serve_directory] to: @"/"]; + NSFileManager *fm = [NSFileManager defaultManager]; + [fm autorelease]; + + NSString *path = [fm currentDirectoryPath]; + NSArray *contents = [fm directoryContentsAtPath: path]; + + for (id file in contents) { + BOOL isDir; + BOOL exists = [fm fileExistsAtPath: file isDirectory: &isDir]; + + NSString *slashFile = slash(file); + + if (isDir == YES) { + initialize_subdir(server, slashFile); + [server assignHandler: [NSLambda lambdaWithReducer: serve_directory] to: slashFile]; + } else { + [server assignHandler: [NSLambda lambdaWithReducer: serve_file] to: slashFile]; + } + } + + return; +} + +int main(int argc, char *argv[]) { + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + + HTTPServer *server = [HTTPServer httpServerWithPort: PORT]; + initialize_root(server, @"/"); + + while ([server listen]) { + id resp = [server read]; + HTTPRequest *req = [HTTPRequest parseRequest: resp]; + [server handleRequest: req]; + [server close]; + } + + [pool drain]; + return 0; +} diff --git a/src/socket+lambda.h b/src/socket+lambda.h new file mode 100644 index 0000000..ae6ad42 --- /dev/null +++ b/src/socket+lambda.h @@ -0,0 +1,9 @@ +#ifndef SOCKET_LAMBDA_H +#define SOCKET_LAMBDA_H + +@interface NSSocket (Lambda) + +@end + + +#endif diff --git a/src/socket.h b/src/socket.h new file mode 100644 index 0000000..76bd106 --- /dev/null +++ b/src/socket.h @@ -0,0 +1,24 @@ +#ifndef SOCKET_H +#define SOCKET_H + +#include +#include + +//simple socket, not very configurable yet +@interface NSSocket : NSObject { + int descriptor, nsocket; + int port; + int err; + struct sockaddr_in address; + char buffer[4096]; +} +-(id) initWithPort: (int) port; ++(id) socketWithPort: (int) port; +-(BOOL) listen; +-(id) read; +-(BOOL) write: (NSString *) data; +-(BOOL) writeData: (NSData *) data; +-(void) close; +@end + +#endif diff --git a/src/socket.m b/src/socket.m new file mode 100644 index 0000000..cd3d34c --- /dev/null +++ b/src/socket.m @@ -0,0 +1,98 @@ +#import +#import "socket.h" + +@implementation NSSocket +-(id) initWithPort: (int) p { + self = [super init]; + + if (self) { + descriptor = socket(AF_INET, SOCK_STREAM, 0); + if (descriptor < 0) { + return NULL; + } + + int opt = 1; + + err = setsockopt(descriptor, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); + if (err != 0) { + return NULL; + } + + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons(port); + + self->port = p; + + err = bind(descriptor, (struct sockaddr *)&address, sizeof(address)); + if (err < 0) { + return NULL; + } + } + + return self; +} + ++(id) socketWithPort: (int) port { + NSSocket *sock = [[super alloc] init]; + [sock autorelease]; + sock->descriptor = socket(AF_INET, SOCK_STREAM, 0); + + if (sock->descriptor < 0) { + return [NSNull new]; + } + + int opt = 1; + + int err = setsockopt(sock->descriptor, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); + if (err != 0) { + return [NSNull new]; + } + + sock->address.sin_family = AF_INET; + sock->address.sin_addr.s_addr = INADDR_ANY; + sock->address.sin_port = htons(port); + + sock->port = 8080; + + err = bind(sock->descriptor, (struct sockaddr *)&sock->address, sizeof(sock->address)); + if (err < 0) { + return [NSNull new]; + } + + return sock; +} + +-(BOOL) listen { + err = listen(descriptor, 3); + if (err < 0) { + return NO; + } + + socklen_t addrlen = sizeof(address); + nsocket = accept(descriptor, (struct sockaddr *)&address, &addrlen); + if (socket < 0) { + return NO; + } + + return YES; +} + +-(id) read { + read(nsocket, buffer, 1024 - 1); + return [NSString stringWithUTF8String: buffer]; +} + +-(BOOL) write: (NSString *) data { + send(nsocket, [data UTF8String], [data length], 0); +} + +-(BOOL) writeData: (NSData *) data { + send(nsocket, [data bytes], [data length], 0); +} + +//the thing returns a new socket so you gotta close it +-(void) close { + close(nsocket); +} +@end