Just some random programs in porth because I saw Tsoding's videos/stream and I thought it looked cool.
Licence: MIT
Just some random programs in porth because I saw Tsoding's videos/stream and I thought it looked cool.
Licence: MIT
| # Ignore all without extensions | |
| * | |
| !*.* | |
| *.asm | |
| *.o |
| // A really bad http server example in Porth | |
| // It does not comply to anything. (yet?) | |
| const SERVER_PORT 8080 end | |
| include "../porth/std/std.porth" | |
| const AF_INET 2 end | |
| const SOCK_STREAM 1 end | |
| const SOL_SOCKET 1 end | |
| const SO_REUSEADDR 2 end | |
| const O_DIRECTORY 65536 end | |
| const S_IFMT 61440 end // 00170000 | |
| const S_IFREG 32768 end // 0x0100000 | |
| const S_IFDIR 16384 end // 0x0040000 | |
| inline proc S_ISREG int -- bool in S_IFMT and S_IFREG = end | |
| inline proc S_ISDIR int -- bool in S_IFMT and S_IFDIR = end | |
| const sizeof(sockaddr) 16 end | |
| const sizeof(linux_dirent) 1024 end | |
| proc main in | |
| memory socket sizeof(int) end | |
| memory sockaddr sizeof(sockaddr) end | |
| memory connection sizeof(int) end | |
| memory dirent_buf sizeof(linux_dirent) end | |
| memory stat_buf sizeof(stat) end | |
| const client_read_buf_size 1024 end | |
| memory client_read_buf client_read_buf_size end | |
| const file_buf_size 1024 end | |
| memory file_buf file_buf_size end | |
| // Create socket | |
| // socket(AF_INET, SOCK_STREAM, 0) | |
| 0 SOCK_STREAM AF_INET SYS_socket syscall3 | |
| dup 0 < if | |
| "ERROR: Could not create socket, errcode " eputs | |
| dup print | |
| 1 exit | |
| end | |
| socket !64 | |
| "INFO: Got socket.\n" eputs | |
| // We make the socket's address reusable for better debugging | |
| // setsockopt(socket, level, option_name, *option_value, option_len) | |
| // Load '1' into the temp buffer area | |
| memory ONE 8 end | |
| 1 ONE !8 | |
| 8 ONE SO_REUSEADDR SOL_SOCKET socket @64 SYS_setsockopt syscall5 drop | |
| // Now we make the 'sockaddr' struct to pass into bind() | |
| // struct sockaddr_in { | |
| // sa_family_t sin_family; /* address family: AF_INET */ | |
| // in_port_t sin_port; /* port in network byte order */ | |
| // struct in_addr sin_addr; /* internet address */ | |
| // }; | |
| // | |
| // struct in_addr { | |
| // uint32_t s_addr; /* address in network byte order */ | |
| // }; | |
| // Keep in mind little endian | |
| // sin_family 16 bit unsigned int | |
| AF_INET sockaddr !8 | |
| 0 sockaddr 1 ptr+ !8 | |
| // sin_port 16 bit (network byte order) | |
| // e.g. port 3000 - 00001011 10111000 = 11 184 | |
| // we can shift and truncate into the 1byte memory locations | |
| SERVER_PORT 8 shr sockaddr 2 ptr+ !8 | |
| SERVER_PORT sockaddr 3 ptr+ !8 | |
| // sin_addr 32 bit (in_addr.s_addr) listen on 0.0.0.0 | |
| 0 sockaddr 4 ptr+ !32 | |
| // Actually bind | |
| // bind(socket_fd, &sockaddr, size) | |
| 16 sockaddr socket @64 SYS_bind syscall3 | |
| dup 0 < if | |
| "ERROR: Could not bind, errcode " eputs | |
| dup print | |
| 1 exit | |
| end drop | |
| "INFO: Bound socket on all addresses to port " eputs | |
| SERVER_PORT print | |
| // Listen to the socket now that it's bound (queue 2) | |
| 2 socket @64 SYS_listen syscall2 | |
| dup 0 < if | |
| "ERROR: Could not listen, errcode " eputs | |
| dup print | |
| 1 exit | |
| end drop | |
| "INFO: Listening.\n" eputs | |
| // Accept any connections that come our way and serve them our HTTP :^) | |
| //while 1 cast(bool) do | |
| while true do | |
| 0 0 socket @64 SYS_accept syscall3 | |
| dup 0 < if | |
| "WARN: Failed to accept a connection, errcode " eputs | |
| else | |
| "INFO: Accepted a connection.\n" eputs | |
| // Accepted connection gives us another fd to write back to them | |
| // Keep another one on the stack to close later | |
| dup connection !64 | |
| // TODO: Handle writes failing with a debug message (when we can break loops?) | |
| // Read the TCP contents | |
| client_read_buf_size client_read_buf connection @64 read | |
| dup 0 < if | |
| "WARN: Error reading from socket but going to continue! errcode " eputs | |
| else | |
| drop | |
| "\n\nDEBUG: The client said...\n----------\n" eputs | |
| client_read_buf dup cstrlen swap eputs | |
| "\n----------\n\n" eputs | |
| // Read "GET", return error otherwise | |
| // FIXME: This is a hacky way of just reading and comparing as null terminated, but it works I guess (but it destroys the original read buffer) | |
| 0 client_read_buf 3 ptr+ !8 | |
| client_read_buf "GET"c cstreq if | |
| "It was a GET\n" eputs | |
| // TODO: Skip over "GET " and then get the path until a " " | |
| // FIXME: Another hack. We go until the " " (ASCII 32) and add a null terminator there and then we can use it as a C string. | |
| // The string will be at 'client_read_buf 4 +' | |
| client_read_buf 4 ptr+ | |
| while dup @8 dup 32 != swap 0 != land do // keep going until space or end of string | |
| 1 ptr+ | |
| end 0 swap !8 | |
| // FIXME: Another weird thing we can do. Set 'client_read_buf 3 +' as '.' (ASCII 46), then we can use that whole C string as the path to read locally. | |
| 46 client_read_buf 3 ptr+ !8 | |
| "Trying to stat '" eputs | |
| client_read_buf 3 ptr+ dup cstrlen swap eputs | |
| "'...\n" eputs | |
| // stat() the path to test if dir/file/unknown | |
| stat_buf client_read_buf 3 ptr+ stat | |
| 0 < if | |
| "Couldn't stat.\n" eputs | |
| "HTTP/1.0 404 OK\nContent-Type: text/html\n\n<html><head><title>Error</title></head><body>File not found.</body></html>" connection @64 fputs | |
| else | |
| // Determine if file or folder with st_mode | |
| // struct stat { | |
| // dev_t st_dev; /* ID of device containing file */ | |
| // ino_t st_ino; /* Inode number */ | |
| // mode_t st_mode; /* File type and mode */ | |
| // ... | |
| // } | |
| // mode_t will (probably) be 16 bytes in, and is 4 bytes long - read it as such (little endian) | |
| // TODO: Turn this into a macro to read these various lengths/at least more elegant? | |
| stat_buf stat.st_mode @32 | |
| // TODO: If file, read file and send back | |
| // TODO: Determine Content-Type to send back nicely | |
| // TODO: If dir, read that dir and send back | |
| // TODO: Else, return error | |
| dup S_ISREG if | |
| // Just open and serve the file | |
| 0 client_read_buf 3 ptr+ SYS_open syscall2 | |
| dup 0 < if | |
| "HTTP/1.0 500 OK\nContent-Type: text/html\n\n<html><head><title>Error</title></head><body>Could not open the file.</body></html>" connection @64 fputs | |
| "WARN: Could not open file! errcode " eputs | |
| else | |
| "HTTP/1.0 200 OK\nContent-Type: " connection @64 fputs | |
| // TODO: Handle types properly | |
| // Naive .html ending check | |
| client_read_buf 3 ptr+ cstrlen | |
| dup 7 > swap | |
| 5 - client_read_buf 3 ptr+ swap ptr+ ".html"c cstreq | |
| land if | |
| "HTML found\n" eputs | |
| "text/html" connection @64 fputs | |
| else | |
| "Not HTML, serving as plain text\n" eputs | |
| "text/plain" connection @64 fputs | |
| end | |
| "\n\n" connection @64 fputs | |
| // Read in the file (fd is on the stack, save a copy) | |
| // (also, read returns the bytes read) | |
| // TODO: fix this awkward read twice syntax | |
| dup file_buf_size swap file_buf swap read | |
| while dup 0 > do | |
| file_buf connection @64 fputs | |
| dup file_buf_size swap file_buf swap read | |
| end drop | |
| // Close the file as we're done | |
| close drop | |
| end | |
| else dup S_ISDIR if* | |
| // Enumerate folder contents | |
| // Open directory, then read its contents | |
| O_DIRECTORY client_read_buf 3 ptr+ SYS_open syscall2 | |
| dup 0 < if | |
| "HTTP/1.0 500 OK\nContent-Type: text/html\n\n<html><head><title>Error</title></head><body>Could not open the directory.</body></html>" connection @64 fputs | |
| "WARN: Could not open directory! errcode " eputs | |
| else | |
| "HTTP/1.0 200 OK\nContent-Type: text/html\n\n<html><head><title>Directory listing</title></head><body><h1>Directory listing for " connection @64 fputs | |
| client_read_buf 3 ptr+ dup cstrlen swap connection @64 fputs | |
| "</h1><ul>" connection @64 fputs | |
| "INFO: Reading directory...\n" eputs | |
| // Do the content reading of the directory stream | |
| // (keep pointer to opendir with dup) | |
| // getdents(fd, buf, buf_size) | |
| dup sizeof(linux_dirent) swap dirent_buf swap SYS_getdents syscall3 | |
| while dup 0 > do | |
| // Enumerate the contents as per returned, keep the offset on the stack | |
| // Initial as 0 | |
| 0 while over over > do | |
| // We have a 'Linux dirent' | |
| // | |
| // struct linux_dirent { | |
| // unsigned long d_ino; | |
| // off_t d_off; | |
| // unsigned short d_reclen; | |
| // char d_name[]; | |
| // }; | |
| // | |
| // So to jump to the filename, we jump 18 bytes (8+8+2) | |
| // TODO: sizeof() - will we ever run into any issues with this assumption? | |
| // Main pointer to linux_dirent struct | |
| dup dirent_buf swap ptr+ | |
| // Add in offset and jump to filename | |
| "<li><a href=\"./" connection @64 fputs | |
| dup 18 ptr+ dup cstrlen swap connection @64 fputs | |
| "\">" connection @64 fputs | |
| dup 18 ptr+ dup cstrlen swap connection @64 fputs | |
| "</a></li>" connection @64 fputs | |
| // Jump by the offset 'd_reclen' | |
| // ... it is a unsigned short, so we'll need to deal with it as such... | |
| // (Remember little endian) | |
| 16 ptr+ @16 + | |
| end drop drop // drop twice to get rid of the extra duped getdents return value as well | |
| // TODO: Fix this awkward syntax | |
| dup sizeof(linux_dirent) swap dirent_buf swap SYS_getdents syscall3 | |
| end drop | |
| "INFO: Closing directory...\n" eputs | |
| close drop | |
| "</ul><i>powered by porth-nothttpd</i></body></html>\n" connection @64 fputs | |
| "INFO: Replied to connection with directory listing.\n" eputs | |
| end | |
| else | |
| "Not a regular file or directory.\n" eputs | |
| "HTTP/1.0 404 OK\nContent-Type: text/html\n\n<html><head><title>Error</title></head><body>File not found.</body></html>" connection @64 fputs | |
| end drop | |
| end | |
| else | |
| "It was NOT a GET\n" eputs | |
| "HTTP/1.0 400 OK\nContent-Type: text/html\n\n<html><head><title>Error</title></head><body>The server does not accept non-GET requests.</body></html>" connection @64 fputs | |
| end | |
| end | |
| "INFO: Closing connection.\n" eputs | |
| close drop | |
| end | |
| end | |
| end |