webserv.c 12 KB

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