/** * Webserver for the FPGC * Uses multiple sockets to handle multiple connections (except for socket 7 which is reserved by netHID) * Note that all files are stored in the filesystem per word and not in bytes * So currently the only the rightmost byte of each file are sent over the network * This is to stay compatible with text files */ #define word char #include "lib/math.c" #include "lib/stdlib.c" #include "lib/sys.c" #include "lib/brfs.c" #include "lib/wiz5500.c" #define WIZNET_IP 213 char wiz_rbuf[WIZNET_MAX_RBUF]; word percentage_done(word remaining, word full) { word x = remaining * 100; return 100 - MATH_divU(x, full); } /** * Write a file from the filesystem to a socket * Sends the file in parts of WIZNET_MAX_TBUF * Assumes the filepath is valid */ void write_file_from_fs(word s, char* path, word filesize) { // Open the file word fd = fs_open(path); if (fd == -1) { // Should not happen bdos_println("UNEXPECTED: File not found!"); return; } word fullsize = filesize; // Read file in chunks of WIZNET_MAX_TBUF word file_buffer[WIZNET_MAX_RBUF]; word chunk_to_read; char dbuf[10]; // Percentage done for progress indication dbuf[0] = 0; while (filesize > 0) { chunk_to_read = filesize > WIZNET_MAX_RBUF ? WIZNET_MAX_RBUF : filesize; fs_read(fd, (char*)file_buffer, chunk_to_read); wiz_write_data(s, file_buffer, chunk_to_read); filesize -= chunk_to_read; // Calculate percentage done word percentage = percentage_done(filesize, fullsize); // Remove previous percentage word i = strlen(dbuf); while (i > 0) { bdos_printc(0x8); // Backspace i--; } if (strlen(dbuf) != 0) { bdos_printc(0x8); // Backspace } itoa(percentage, dbuf); bdos_print(dbuf); bdos_printc('%'); } bdos_printc(' '); // Add space after percentage // Close file fs_close(fd); } /** * Send a 404 response * Could be extended by sending a custom 404 page if found in the filesystem */ void send_404_response(word s) { char* response_header = "HTTP/1.1 404 Not Found\nServer: FPGC/2.0\nContent-Type: text/html\n\n"; wiz_write_data(s, response_header, strlen(response_header)); char* response_body = "ERROR404ERROR 404: Could not find the requested file or directory :("; wiz_write_data(s, response_body, strlen(response_body)); // Disconnect after sending a response wiz_send_cmd(s, WIZNET_CR_DISCON); } /** * Parse the file path from a request header in rbuf * Writes field to pbuf * File path is assumed to be the second word in the request header */ void parse_file_path(char* rbuf, char* pbuf) { strtok(rbuf, " "); // Skip the first word char* file_path = strtok((char*)-1, " "); if ((word)file_path != -1 && strlen(file_path) < MAX_PATH_LENGTH) { strcpy(pbuf, file_path); } else { strcpy(pbuf, "/"); } } /** * Send directory listing to socket s as part of send_dir * Only sends the body of the response and should not disconnect the socket * Assumes path is a valid directory */ void send_list_dir(word s, char* path) { // Write start of html page char* html_start = "

"; wiz_write_data(s, html_start, strlen(html_start)); // Print path as header wiz_write_data(s, path, strlen(path)); // Start table char* table_start = "

"; wiz_write_data(s, table_start, strlen(table_start)); // Obtain directory entries struct brfs_dir_entry entries[MAX_DIR_ENTRIES]; word num_entries = fs_readdir(path, (char*)entries); // Keep track of the longest filename for formatting word max_filename_length = 0; // Sort entries by filename word i, j; for (i = 0; i < num_entries - 1; i++) { for (j = 0; j < num_entries - i - 1; j++) { char decompressed_filename1[17]; strdecompress(decompressed_filename1, entries[j].filename); char decompressed_filename2[17]; strdecompress(decompressed_filename2, entries[j + 1].filename); // Update max_filename_length // This works because . is always the first entry (skipped by this check) if (strlen(decompressed_filename2) > max_filename_length) { max_filename_length = strlen(decompressed_filename2); } // Sort by filename if (strcmp(decompressed_filename1, decompressed_filename2) > 0) { // Swap filenames struct brfs_dir_entry temp = entries[j]; entries[j] = entries[j + 1]; entries[j + 1] = temp; } } } // Loop through sorted entries and print them for (i = 0; i < num_entries; i++) { // Send start of table row char* table_row_start = "\n"); // Write the entry to the socket wiz_write_data(s, entry_str, strlen(entry_str)); } // write end of html page wiz_write_data(s, "
NameSize
"); // Add filename again for the link text strcat(entry_str, filename); if ((entry.flags & 0x01) == 1) { strcat(entry_str, "/"); } // End the hyperlink strcat(entry_str, ""); // Add filesize if file if ((entry.flags & 0x01) == 0) { char filesize_str[11]; itoa(entry.filesize, filesize_str); strcat(entry_str, filesize_str); } // End the table row strcat(entry_str, "
", 22); } /** * Serve a file * Sends the response to socket s * Includes the content length in the header so the client knows how large a download is * Assumes the file is valid */ void serve_file(word s, char* path) { // Get file size struct brfs_dir_entry* entry = (struct brfs_dir_entry*)fs_stat(path); if ((word)entry == -1) { // Should not happen, so just disconnect the session bdos_println("UNEXPECTED: File not found!"); wiz_send_cmd(s, WIZNET_CR_DISCON); } word filesize = entry->filesize; bdos_print("200 "); // Write header (currently omitting content type) char header[128]; strcpy(header, "HTTP/1.1 200 OK\nServer: FPGC/2.0\nContent-Length: "); char filesize_str[12]; itoa(filesize, filesize_str); strcat(header, filesize_str); strcat(header, "\n\n"); wiz_write_data(s, header, strlen(header)); // Write the response from filesystem write_file_from_fs(s, path, filesize); bdos_println("Done"); // Disconnect after sending a response wiz_send_cmd(s, WIZNET_CR_DISCON); } /** * Serve a directory * Sends the response to socket s * Assumes the directory is valid */ void serve_dir(word s, char* path) { // If path does not end with a slash, redirect to the same path with a slash if (path[strlen(path) - 1] != '/') { strcat(path, "/"); char* response = "HTTP/1.1 301 Moved Permanently\nLocation: "; bdos_print("Redirect to "); bdos_println(path); wiz_write_data(s, response, strlen(response)); wiz_write_data(s, path, strlen(path)); wiz_write_data(s, "\n", 1); } else { bdos_print("200 "); // Write header (currently omitting content type) char* header = "HTTP/1.1 200 OK\nServer: FPGC/2.0\n\n"; wiz_write_data(s, header, strlen(header)); send_list_dir(s, path); bdos_println("Done"); } // Disconnect after sending a response wiz_send_cmd(s, WIZNET_CR_DISCON); } /** * Serve a path from the file system * Sends the response to socket s */ void serve_path(word s, char* path) { // Redirect "/" to "/index.html" if (strcmp(path, "/") == 0) { // Send an actual redirect to the browser char *response = "HTTP/1.1 301 Moved Permanently\nLocation: /index.html\n"; bdos_println("Redirect to /index.html\n"); wiz_write_data(s, response, strlen(response)); // Disconnect after sending the redirect wiz_send_cmd(s, WIZNET_CR_DISCON); return; } // Check if the path is a directory char path_copy[MAX_PATH_LENGTH]; strcpy(path_copy, path); // Make a copy as stat remove trailing slashes struct brfs_dir_entry* entry = (struct brfs_dir_entry*)fs_stat(path_copy); if ((word)entry == -1) { bdos_println("Path not found"); send_404_response(s); return; } if ((entry->flags & 0x01) == 0) { // File serve_file(s, path); } else { // Directory serve_dir(s, path); } } /** * Handle a session on socket s */ void handle_session(word s) { // Size of received data word rsize = wiz_get_sock_reg_16(s, WIZNET_SnRX_RSR); // Disconnect on no data if (rsize == 0) { wiz_send_cmd(s, WIZNET_CR_DISCON); return; } // Read data into buffer wiz_read_recv_data(s, wiz_rbuf, rsize); // Read rbuf for requested page char path[MAX_PATH_LENGTH]; // Buffer for path name parse_file_path(wiz_rbuf, path); bdos_print(path); bdos_print(" "); serve_path(s, path); } /** * Reinitialize the W5500 */ word reinit_w5500() { word ip_addr[4] = {192, 168, 0, WIZNET_IP}; word gateway_addr[4] = {192, 168, 0, 1}; word mac_addr[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0x24, 0x64}; word sub_mask[4] = {255, 255, 255, 0}; wiz_init(ip_addr, gateway_addr, mac_addr, sub_mask); } /** * Open all sockets in TCP Server mode at port 80 */ word open_all_sockets_tcp_server() { word s; for (s = 0; s < 7; s++) { wiz_init_socket_tcp_host(s, 80); } } /** * Main function */ int main() { bdos_println("FPGC Webserver v2.0"); reinit_w5500(); open_all_sockets_tcp_server(); // Main loop while (1) { if (hid_checkfifo()) { // Return on any key press hid_fiforead(); return 'q'; } // Handle sockets 0-6 word s_status; word s; for (s = 0; s < 7; s++) { s_status = wiz_get_sock_reg_8(s, WIZNET_SnSR); if (s_status == WIZNET_SOCK_CLOSED) { // Open the socket when closed // Set socket s in TCP Server mode at port 80 wiz_init_socket_tcp_host(s, 80); } else if (s_status == WIZNET_SOCK_ESTABLISHED) { // Handle session when a connection is established handle_session(s); // Afterwards, reinitialize socket wiz_init_socket_tcp_host(s, 80); } else if (s_status == WIZNET_SOCK_LISTEN || s_status == WIZNET_SOCK_SYNSENT || s_status == WIZNET_SOCK_SYNRECV) { // Do nothing in these cases } else { // In other cases, reset the socket // Set socket s in TCP Server mode at port 80 wiz_init_socket_tcp_host(s, 80); } } delay(20); } return 'q'; } void interrupt() { // Handle all interrupts word i = get_int_id(); switch(i) { case INTID_TIMER1: timer1Value = 1; // Notify ending of timer1 break; } }