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 |