upload.c 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. // Copyright (c) 2004-2012 Sergey Lyubka
  2. // This file is a part of mongoose project, http://github.com/valenok/mongoose
  3. #include <stdio.h>
  4. #include <string.h>
  5. #include <fcntl.h>
  6. #include <stdlib.h>
  7. #ifdef _WIN32
  8. #include <windows.h>
  9. #include <io.h>
  10. #define strtoll strtol
  11. typedef __int64 int64_t;
  12. #define O_CLOEXEC 0
  13. #define O_EXLOCK 0
  14. #else
  15. #include <inttypes.h>
  16. #include <unistd.h>
  17. #ifndef O_BINARY
  18. #define O_BINARY 0
  19. #endif
  20. #endif // !_WIN32
  21. #include "mongoose.h"
  22. // Make sure that form has enctype="multipart/form-data" attribute
  23. static const char *html_form =
  24. "<html><body>Upload example."
  25. "<form method=\"POST\" action=\"/handle_post_request\" "
  26. " enctype=\"multipart/form-data\">"
  27. "<input type=\"file\" name=\"file\" /> <br/>"
  28. "<input type=\"submit\" value=\"Upload\" />"
  29. "</form></body></html>";
  30. static const char *HTTP_500 = "HTTP/1.0 500 Server Error\r\n\r\n";
  31. static void handle_file_upload(struct mg_connection *conn) {
  32. const char *cl_header;
  33. char post_data[16 * 1024], path[999], file_name[1024], mime_type[100],
  34. buf[BUFSIZ], *eop, *s, *p;
  35. FILE *fp;
  36. int64_t cl, written;
  37. int fd, n, post_data_len;
  38. // Figure out total content length. Return if it is not present or invalid.
  39. cl_header = mg_get_header(conn, "Content-Length");
  40. if (cl_header == NULL || (cl = strtoll(cl_header, NULL, 10)) <= 0) {
  41. mg_printf(conn, "%s%s", HTTP_500, "Invalid Conent-Length");
  42. return;
  43. }
  44. // Read the initial chunk into memory. This should be multipart POST data.
  45. // Parse headers, where we should find file name and content-type.
  46. post_data_len = mg_read(conn, post_data, sizeof(post_data));
  47. file_name[0] = mime_type[0] = '\0';
  48. for (s = p = post_data; p < &post_data[post_data_len]; p++) {
  49. if (p[0] == '\r' && p[1] == '\n') {
  50. if (s == p) {
  51. p += 2;
  52. break; // End of headers
  53. }
  54. p[0] = p[1] = '\0';
  55. sscanf(s, "Content-Type: %99s", mime_type);
  56. // TODO(lsm): don't expect filename to be the 3rd field,
  57. // parse the header properly instead.
  58. sscanf(s, "Content-Disposition: %*s %*s filename=\"%1023[^\"]",
  59. file_name);
  60. s = p + 2;
  61. }
  62. }
  63. // Finished parsing headers. Now "p" points to the first byte of data.
  64. // Calculate file size
  65. cl -= p - post_data; // Subtract headers size
  66. cl -= strlen(post_data); // Subtract the boundary marker at the end
  67. cl -= 6; // Subtract "\r\n" before and after boundary
  68. // Construct destination file name. Write to /tmp, do not allow
  69. // paths that contain slashes.
  70. if ((s = strrchr(file_name, '/')) == NULL) {
  71. s = file_name;
  72. }
  73. snprintf(path, sizeof(path), "/tmp/%s", s);
  74. if (file_name[0] == '\0') {
  75. mg_printf(conn, "%s%s", HTTP_500, "Can't get file name");
  76. } else if (cl <= 0) {
  77. mg_printf(conn, "%s%s", HTTP_500, "Empty file");
  78. } else if ((fd = open(path, O_CREAT | O_TRUNC | O_BINARY |
  79. O_WRONLY | O_EXLOCK | O_CLOEXEC)) < 0) {
  80. // We're opening the file with exclusive lock held. This guarantee us that
  81. // there is no other thread can save into the same file simultaneously.
  82. mg_printf(conn, "%s%s", HTTP_500, "Cannot open file");
  83. } else if ((fp = fdopen(fd, "w")) == NULL) {
  84. mg_printf(conn, "%s%s", HTTP_500, "Cannot reopen file stream");
  85. close(fd);
  86. } else {
  87. // Success. Write data into the file.
  88. eop = post_data + post_data_len;
  89. n = p + cl > eop ? (int) (eop - p) : (int) cl;
  90. (void) fwrite(p, 1, n, fp);
  91. written = n;
  92. while (written < cl &&
  93. (n = mg_read(conn, buf, cl - written > (int64_t) sizeof(buf) ?
  94. sizeof(buf) : cl - written)) > 0) {
  95. (void) fwrite(buf, 1, n, fp);
  96. written += n;
  97. }
  98. (void) fclose(fp);
  99. mg_printf(conn, "HTTP/1.0 200 OK\r\n\r\n"
  100. "Saved to [%s], written %llu bytes", path, cl);
  101. }
  102. }
  103. static void *callback(enum mg_event event, struct mg_connection *conn) {
  104. const struct mg_request_info *ri = mg_get_request_info(conn);
  105. if (event == MG_NEW_REQUEST) {
  106. if (!strcmp(ri->uri, "/handle_post_request")) {
  107. handle_file_upload(conn);
  108. } else {
  109. // Show HTML form.
  110. mg_printf(conn, "HTTP/1.0 200 OK\r\n"
  111. "Content-Length: %d\r\n"
  112. "Content-Type: text/html\r\n\r\n%s",
  113. (int) strlen(html_form), html_form);
  114. }
  115. // Mark as processed
  116. return "";
  117. } else {
  118. return NULL;
  119. }
  120. }
  121. int main(void) {
  122. struct mg_context *ctx;
  123. const char *options[] = {"listening_ports", "8080", NULL};
  124. ctx = mg_start(&callback, NULL, options);
  125. getchar(); // Wait until user hits "enter"
  126. mg_stop(ctx);
  127. return 0;
  128. }