webserv.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. /**
  2. * Webserver for the FPGC
  3. * Uses multiple sockets to handle multiple connections (except for socket 7 which is reserved by netHID)
  4. * Note that all files are stored in the filesystem per word and not in bytes
  5. * So currently the only the rightmost byte of each file are sent over the network
  6. * This is to stay compatible with text files
  7. */
  8. #define word char
  9. #include "lib/math.c"
  10. #include "lib/stdlib.c"
  11. #include "lib/sys.c"
  12. #include "lib/brfs.c"
  13. #include "lib/wiz5500.c"
  14. #define WIZNET_IP 213
  15. char wiz_rbuf[WIZNET_MAX_RBUF];
  16. word percentage_done(word remaining, word full)
  17. {
  18. word x = remaining * 100;
  19. return 100 - MATH_divU(x, full);
  20. }
  21. /**
  22. * Write a file from the filesystem to a socket
  23. * Sends the file in parts of WIZNET_MAX_TBUF
  24. * Assumes the filepath is valid
  25. */
  26. void write_file_from_fs(word s, char* path, word filesize)
  27. {
  28. // Open the file
  29. word fd = fs_open(path);
  30. if (fd == -1)
  31. {
  32. // Should not happen
  33. bdos_println("UNEXPECTED: File not found!");
  34. return;
  35. }
  36. word fullsize = filesize;
  37. // Read file in chunks of WIZNET_MAX_TBUF
  38. word file_buffer[WIZNET_MAX_RBUF];
  39. word chunk_to_read;
  40. char dbuf[10]; // Percentage done for progress indication
  41. dbuf[0] = 0;
  42. while (filesize > 0)
  43. {
  44. chunk_to_read = filesize > WIZNET_MAX_RBUF ? WIZNET_MAX_RBUF : filesize;
  45. fs_read(fd, (char*)file_buffer, chunk_to_read);
  46. wiz_write_data(s, file_buffer, chunk_to_read);
  47. filesize -= chunk_to_read;
  48. // Calculate percentage done
  49. word percentage = percentage_done(filesize, fullsize);
  50. // Remove previous percentage
  51. word i = strlen(dbuf);
  52. while (i > 0)
  53. {
  54. bdos_printc(0x8); // Backspace
  55. i--;
  56. }
  57. if (strlen(dbuf) != 0)
  58. {
  59. bdos_printc(0x8); // Backspace
  60. }
  61. itoa(percentage, dbuf);
  62. bdos_print(dbuf);
  63. bdos_printc('%');
  64. }
  65. bdos_printc(' '); // Add space after percentage
  66. // Close file
  67. fs_close(fd);
  68. }
  69. /**
  70. * Send a 404 response
  71. * Could be extended by sending a custom 404 page if found in the filesystem
  72. */
  73. void send_404_response(word s)
  74. {
  75. char* response_header = "HTTP/1.1 404 Not Found\nServer: FPGC/2.0\nContent-Type: text/html\n\n";
  76. wiz_write_data(s, response_header, strlen(response_header));
  77. char* response_body = "<!DOCTYPE html><html><head><title>ERROR404</title></head><body>ERROR 404: Could not find the requested file or directory :(</body></html>";
  78. wiz_write_data(s, response_body, strlen(response_body));
  79. // Disconnect after sending a response
  80. wiz_send_cmd(s, WIZNET_CR_DISCON);
  81. }
  82. /**
  83. * Parse the file path from a request header in rbuf
  84. * Writes field to pbuf
  85. * File path is assumed to be the second word in the request header
  86. */
  87. void parse_file_path(char* rbuf, char* pbuf)
  88. {
  89. strtok(rbuf, " "); // Skip the first word
  90. char* file_path = strtok((char*)-1, " ");
  91. if ((word)file_path != -1 && strlen(file_path) < MAX_PATH_LENGTH)
  92. {
  93. strcpy(pbuf, file_path);
  94. }
  95. else
  96. {
  97. strcpy(pbuf, "/");
  98. }
  99. }
  100. /**
  101. * Send directory listing to socket s as part of send_dir
  102. * Only sends the body of the response and should not disconnect the socket
  103. * Assumes path is a valid directory
  104. */
  105. void send_list_dir(word s, char* path)
  106. {
  107. // Write start of html page
  108. char* html_start = "<!DOCTYPE html><html><body><h2>";
  109. wiz_write_data(s, html_start, strlen(html_start));
  110. // Print path as header
  111. wiz_write_data(s, path, strlen(path));
  112. // Start table
  113. char* table_start = "</h2><table><tr><th>Name</th><th>Size</th></tr>";
  114. wiz_write_data(s, table_start, strlen(table_start));
  115. // Obtain directory entries
  116. struct brfs_dir_entry entries[MAX_DIR_ENTRIES];
  117. word num_entries = fs_readdir(path, (char*)entries);
  118. // Keep track of the longest filename for formatting
  119. word max_filename_length = 0;
  120. // Sort entries by filename
  121. word i, j;
  122. for (i = 0; i < num_entries - 1; i++)
  123. {
  124. for (j = 0; j < num_entries - i - 1; j++)
  125. {
  126. char decompressed_filename1[17];
  127. strdecompress(decompressed_filename1, entries[j].filename);
  128. char decompressed_filename2[17];
  129. strdecompress(decompressed_filename2, entries[j + 1].filename);
  130. // Update max_filename_length
  131. // This works because . is always the first entry (skipped by this check)
  132. if (strlen(decompressed_filename2) > max_filename_length)
  133. {
  134. max_filename_length = strlen(decompressed_filename2);
  135. }
  136. // Sort by filename
  137. if (strcmp(decompressed_filename1, decompressed_filename2) > 0)
  138. {
  139. // Swap filenames
  140. struct brfs_dir_entry temp = entries[j];
  141. entries[j] = entries[j + 1];
  142. entries[j + 1] = temp;
  143. }
  144. }
  145. }
  146. // Loop through sorted entries and print them
  147. for (i = 0; i < num_entries; i++)
  148. {
  149. // Send start of table row
  150. char* table_row_start = "<tr><td style=\"padding:0 15px 0 15px;\"><a href=\"";
  151. wiz_write_data(s, table_row_start, strlen(table_row_start));
  152. // Create entry string
  153. char entry_str[128];
  154. // Add filename
  155. struct brfs_dir_entry entry = entries[i];
  156. char filename[17];
  157. strdecompress(filename, entry.filename);
  158. strcpy(entry_str, filename);
  159. // Add / if directory
  160. if ((entry.flags & 0x01) == 1)
  161. {
  162. strcat(entry_str, "/");
  163. }
  164. // Add end of HTML link tag
  165. strcat(entry_str, "\">");
  166. // Add filename again for the link text
  167. strcat(entry_str, filename);
  168. if ((entry.flags & 0x01) == 1)
  169. {
  170. strcat(entry_str, "/");
  171. }
  172. // End the hyperlink
  173. strcat(entry_str, "</a></td><td style=\"padding:0 15px 0 15px;\">");
  174. // Add filesize if file
  175. if ((entry.flags & 0x01) == 0)
  176. {
  177. char filesize_str[11];
  178. itoa(entry.filesize, filesize_str);
  179. strcat(entry_str, filesize_str);
  180. }
  181. // End the table row
  182. strcat(entry_str, "</td></tr>\n");
  183. // Write the entry to the socket
  184. wiz_write_data(s, entry_str, strlen(entry_str));
  185. }
  186. // write end of html page
  187. wiz_write_data(s, "</table></body></html>", 22);
  188. }
  189. /**
  190. * Serve a file
  191. * Sends the response to socket s
  192. * Includes the content length in the header so the client knows how large a download is
  193. * Assumes the file is valid
  194. */
  195. void serve_file(word s, char* path)
  196. {
  197. // Get file size
  198. struct brfs_dir_entry* entry = (struct brfs_dir_entry*)fs_stat(path);
  199. if ((word)entry == -1)
  200. {
  201. // Should not happen, so just disconnect the session
  202. bdos_println("UNEXPECTED: File not found!");
  203. wiz_send_cmd(s, WIZNET_CR_DISCON);
  204. }
  205. word filesize = entry->filesize;
  206. bdos_print("200 ");
  207. // Write header (currently omitting content type)
  208. char header[128];
  209. strcpy(header, "HTTP/1.1 200 OK\nServer: FPGC/2.0\nContent-Length: ");
  210. char filesize_str[12];
  211. itoa(filesize, filesize_str);
  212. strcat(header, filesize_str);
  213. strcat(header, "\n\n");
  214. wiz_write_data(s, header, strlen(header));
  215. // Write the response from filesystem
  216. write_file_from_fs(s, path, filesize);
  217. bdos_println("Done");
  218. // Disconnect after sending a response
  219. wiz_send_cmd(s, WIZNET_CR_DISCON);
  220. }
  221. /**
  222. * Serve a directory
  223. * Sends the response to socket s
  224. * Assumes the directory is valid
  225. */
  226. void serve_dir(word s, char* path)
  227. {
  228. // If path does not end with a slash, redirect to the same path with a slash
  229. if (path[strlen(path) - 1] != '/')
  230. {
  231. strcat(path, "/");
  232. char* response = "HTTP/1.1 301 Moved Permanently\nLocation: ";
  233. bdos_print("Redirect to ");
  234. bdos_println(path);
  235. wiz_write_data(s, response, strlen(response));
  236. wiz_write_data(s, path, strlen(path));
  237. wiz_write_data(s, "\n", 1);
  238. }
  239. else
  240. {
  241. bdos_print("200 ");
  242. // Write header (currently omitting content type)
  243. char* header = "HTTP/1.1 200 OK\nServer: FPGC/2.0\n\n";
  244. wiz_write_data(s, header, strlen(header));
  245. send_list_dir(s, path);
  246. bdos_println("Done");
  247. }
  248. // Disconnect after sending a response
  249. wiz_send_cmd(s, WIZNET_CR_DISCON);
  250. }
  251. /**
  252. * Serve a path from the file system
  253. * Sends the response to socket s
  254. */
  255. void serve_path(word s, char* path)
  256. {
  257. // Redirect "/" to "/index.html"
  258. if (strcmp(path, "/") == 0)
  259. {
  260. // Send an actual redirect to the browser
  261. char *response = "HTTP/1.1 301 Moved Permanently\nLocation: /index.html\n";
  262. bdos_println("Redirect to /index.html\n");
  263. wiz_write_data(s, response, strlen(response));
  264. // Disconnect after sending the redirect
  265. wiz_send_cmd(s, WIZNET_CR_DISCON);
  266. return;
  267. }
  268. // Check if the path is a directory
  269. char path_copy[MAX_PATH_LENGTH];
  270. strcpy(path_copy, path); // Make a copy as stat remove trailing slashes
  271. struct brfs_dir_entry* entry = (struct brfs_dir_entry*)fs_stat(path_copy);
  272. if ((word)entry == -1)
  273. {
  274. bdos_println("Path not found");
  275. send_404_response(s);
  276. return;
  277. }
  278. if ((entry->flags & 0x01) == 0)
  279. {
  280. // File
  281. serve_file(s, path);
  282. }
  283. else
  284. {
  285. // Directory
  286. serve_dir(s, path);
  287. }
  288. }
  289. /**
  290. * Handle a session on socket s
  291. */
  292. void handle_session(word s)
  293. {
  294. // Size of received data
  295. word rsize = wiz_get_sock_reg_16(s, WIZNET_SnRX_RSR);
  296. // Disconnect on no data
  297. if (rsize == 0)
  298. {
  299. wiz_send_cmd(s, WIZNET_CR_DISCON);
  300. return;
  301. }
  302. // Read data into buffer
  303. wiz_read_recv_data(s, wiz_rbuf, rsize);
  304. // Read rbuf for requested page
  305. char path[MAX_PATH_LENGTH]; // Buffer for path name
  306. parse_file_path(wiz_rbuf, path);
  307. bdos_print(path);
  308. bdos_print(" ");
  309. serve_path(s, path);
  310. }
  311. /**
  312. * Reinitialize the W5500
  313. */
  314. word reinit_w5500()
  315. {
  316. word ip_addr[4] = {192, 168, 0, WIZNET_IP};
  317. word gateway_addr[4] = {192, 168, 0, 1};
  318. word mac_addr[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0x24, 0x64};
  319. word sub_mask[4] = {255, 255, 255, 0};
  320. wiz_init(ip_addr, gateway_addr, mac_addr, sub_mask);
  321. }
  322. /**
  323. * Open all sockets in TCP Server mode at port 80
  324. */
  325. word open_all_sockets_tcp_server()
  326. {
  327. word s;
  328. for (s = 0; s < 7; s++)
  329. {
  330. wiz_init_socket_tcp_host(s, 80);
  331. }
  332. }
  333. /**
  334. * Main function
  335. */
  336. int main()
  337. {
  338. bdos_println("FPGC Webserver v2.0");
  339. reinit_w5500();
  340. open_all_sockets_tcp_server();
  341. // Main loop
  342. while (1)
  343. {
  344. if (hid_checkfifo())
  345. {
  346. // Return on any key press
  347. hid_fiforead();
  348. return 'q';
  349. }
  350. // Handle sockets 0-6
  351. word s_status;
  352. word s;
  353. for (s = 0; s < 7; s++)
  354. {
  355. s_status = wiz_get_sock_reg_8(s, WIZNET_SnSR);
  356. if (s_status == WIZNET_SOCK_CLOSED)
  357. {
  358. // Open the socket when closed
  359. // Set socket s in TCP Server mode at port 80
  360. wiz_init_socket_tcp_host(s, 80);
  361. }
  362. else if (s_status == WIZNET_SOCK_ESTABLISHED)
  363. {
  364. // Handle session when a connection is established
  365. handle_session(s);
  366. // Afterwards, reinitialize socket
  367. wiz_init_socket_tcp_host(s, 80);
  368. }
  369. else if (s_status == WIZNET_SOCK_LISTEN || s_status == WIZNET_SOCK_SYNSENT || s_status == WIZNET_SOCK_SYNRECV)
  370. {
  371. // Do nothing in these cases
  372. }
  373. else
  374. {
  375. // In other cases, reset the socket
  376. // Set socket s in TCP Server mode at port 80
  377. wiz_init_socket_tcp_host(s, 80);
  378. }
  379. }
  380. delay(20);
  381. }
  382. return 'q';
  383. }
  384. void interrupt()
  385. {
  386. // Handle all interrupts
  387. word i = get_int_id();
  388. switch(i)
  389. {
  390. case INTID_TIMER1:
  391. timer1Value = 1; // Notify ending of timer1
  392. break;
  393. }
  394. }