Browse Source

Added mg_upload()

Sergey Lyubka 12 năm trước cách đây
mục cha
commit
0d442058e5
3 tập tin đã thay đổi với 127 bổ sung92 xóa
  1. 8 91
      examples/upload.c
  2. 108 1
      mongoose.c
  3. 11 0
      mongoose.h

+ 8 - 91
examples/upload.c

@@ -26,97 +26,11 @@ static const char *html_form =
   "<input type=\"submit\" value=\"Upload\" />"
   "</form></body></html>";
 
-static const char *HTTP_500 = "HTTP/1.0 500 Server Error\r\n\r\n";
-
-static void handle_file_upload(struct mg_connection *conn) {
-  const char *cl_header;
-  char post_data[16 * 1024], path[999], file_name[1024], mime_type[100],
-       buf[BUFSIZ], *eop, *s, *p;
-  FILE *fp;
-  int64_t cl, written;
-  int fd, n, post_data_len;
-
-  // Figure out total content length. Return if it is not present or invalid.
-  cl_header = mg_get_header(conn, "Content-Length");
-  if (cl_header == NULL || (cl = strtoll(cl_header, NULL, 10)) <= 0) {
-    mg_printf(conn, "%s%s", HTTP_500, "Invalid Conent-Length");
-    return;
-  }
-
-  // Read the initial chunk into memory. This should be multipart POST data.
-  // Parse headers, where we should find file name and content-type.
-  post_data_len = mg_read(conn, post_data, sizeof(post_data));
-  file_name[0] = mime_type[0] = '\0';
-  for (s = p = post_data; p < &post_data[post_data_len]; p++) {
-    if (p[0] == '\r' && p[1] == '\n') {
-      if (s == p) {
-        p += 2;
-        break;  // End of headers
-      }
-      p[0] = p[1] = '\0';
-      sscanf(s, "Content-Type: %99s", mime_type);
-      // TODO(lsm): don't expect filename to be the 3rd field,
-      // parse the header properly instead.
-      sscanf(s, "Content-Disposition: %*s %*s filename=\"%1023[^\"]",
-             file_name);
-      s = p + 2;
-    }
-  }
-
-  // Finished parsing headers. Now "p" points to the first byte of data.
-  // Calculate file size
-  cl -= p - post_data;      // Subtract headers size
-  cl -= strlen(post_data);  // Subtract the boundary marker at the end
-  cl -= 6;                  // Subtract "\r\n" before and after boundary
-
-  // Construct destination file name. Write to /tmp, do not allow
-  // paths that contain slashes.
-  if ((s = strrchr(file_name, '/')) == NULL) {
-    s = file_name;
-  }
-  snprintf(path, sizeof(path), "/tmp/%s", s);
-
-  if (file_name[0] == '\0') {
-    mg_printf(conn, "%s%s", HTTP_500, "Can't get file name");
-  } else if (cl <= 0) {
-    mg_printf(conn, "%s%s", HTTP_500, "Empty file");
-  } else if ((fd = open(path, O_CREAT | O_TRUNC |
-#ifdef _WIN32
-                        O_BINARY |
-#else
-                        O_EXLOCK | O_CLOEXEC |
-#endif
-                        O_WRONLY)) < 0) {
-    // We're opening the file with exclusive lock held. This guarantee us that
-    // there is no other thread can save into the same file simultaneously.
-    mg_printf(conn, "%s%s", HTTP_500, "Cannot open file");
-  } else if ((fp = fdopen(fd, "w")) == NULL) {
-    mg_printf(conn, "%s%s", HTTP_500, "Cannot reopen file stream");
-    close(fd);
-  } else {
-    // Success. Write data into the file.
-    eop = post_data + post_data_len;
-    n = p + cl > eop ? (int) (eop - p) : (int) cl;
-    (void) fwrite(p, 1, n, fp);
-    written = n;
-    while (written < cl &&
-           (n = mg_read(conn, buf, cl - written > (int64_t) sizeof(buf) ?
-                        sizeof(buf) : cl - written)) > 0) {
-      (void) fwrite(buf, 1, n, fp);
-      written += n;
-    }
-    (void) fclose(fp);
-    mg_printf(conn, "HTTP/1.0 200 OK\r\n\r\n"
-              "Saved to [%s], written %llu bytes", path, cl);
-  }
-}
-
 static void *callback(enum mg_event event, struct mg_connection *conn) {
-  const struct mg_request_info *ri = mg_get_request_info(conn);
-
   if (event == MG_NEW_REQUEST) {
-    if (!strcmp(ri->uri, "/handle_post_request")) {
-      handle_file_upload(conn);
+    if (!strcmp(mg_get_request_info(conn)->uri, "/handle_post_request")) {
+      mg_printf(conn, "%s", "HTTP/1.0 200 OK\r\n\r\n");
+      mg_upload(conn, "/tmp");
     } else {
       // Show HTML form.
       mg_printf(conn, "HTTP/1.0 200 OK\r\n"
@@ -126,9 +40,11 @@ static void *callback(enum mg_event event, struct mg_connection *conn) {
     }
     // Mark as processed
     return "";
-  } else {
-    return NULL;
+  } else if (event == MG_UPLOAD) {
+    mg_printf(conn, "Saved [%s]", mg_get_request_info(conn)->ev_data);
   }
+
+  return NULL;
 }
 
 int main(void) {
@@ -137,6 +53,7 @@ int main(void) {
 
   ctx = mg_start(&callback, NULL, options);
   getchar();  // Wait until user hits "enter"
+  pause();
   mg_stop(ctx);
 
   return 0;

+ 108 - 1
mongoose.c

@@ -747,7 +747,7 @@ static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen,
 }
 
 // Skip the characters until one of the delimiters characters found.
-// 0-terminate resulting word. Skip the delimiter and following whitespaces if any.
+// 0-terminate resulting word. Skip the delimiter and following whitespaces.
 // Advance pointer to buffer to the next word. Return found 0-terminated word.
 // Delimiters can be quoted with quotechar.
 static char *skip_quoted(char **buf, const char *delimiters,
@@ -4035,6 +4035,113 @@ static void handle_lsp_request(struct mg_connection *conn, const char *path,
 }
 #endif // USE_LUA
 
+int mg_upload(struct mg_connection *conn, const char *destination_dir) {
+  const char *content_type_header, *boundary_start;
+  char buf[8192], path[PATH_MAX], fname[1024], boundary[100], *s;
+  FILE *fp;
+  int bl, n, i, j, headers_len, boundary_len, len = 0, num_uploaded_files = 0;
+
+  // Request looks like this:
+  //
+  // POST /upload HTTP/1.1
+  // Host: 127.0.0.1:8080
+  // Content-Length: 244894
+  // Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryRVr
+  //
+  // ------WebKitFormBoundaryRVr
+  // Content-Disposition: form-data; name="file"; filename="accum.png"
+  // Content-Type: image/png
+  //
+  //  <89>PNG
+  //  <PNG DATA>
+  // ------WebKitFormBoundaryRVr
+
+  // Extract boundary string from the Content-Type header
+  if ((content_type_header = mg_get_header(conn, "Content-Type")) == NULL ||
+      (boundary_start = strstr(content_type_header, "boundary=")) == NULL ||
+      (sscanf(boundary_start, "boundary=\"%99[^\"]\"", boundary) == 0 &&
+       sscanf(boundary_start, "boundary=%99s", boundary) == 0) ||
+      boundary[0] == '\0') {
+    return num_uploaded_files;
+  }
+
+  boundary_len = strlen(boundary);
+  bl = boundary_len + 4;  // \r\n--<boundary>
+  for (;;) {
+    // Pull in headers
+    assert(len >= 0 && len <= (int) sizeof(buf));
+    while ((n = mg_read(conn, buf + len, sizeof(buf) - len)) > 0) {
+      len += n;
+    }
+    if ((headers_len = get_request_len(buf, len)) <= 0) {
+      break;
+    }
+
+    // Fetch file name.
+    fname[0] = '\0';
+    for (i = j = 0; i < headers_len; i++) {
+      if (buf[i] == '\r' && buf[i + 1] == '\n') {
+        buf[i] = buf[i + 1] = '\0';
+        // TODO(lsm): don't expect filename to be the 3rd field,
+        // parse the header properly instead.
+        sscanf(&buf[j], "Content-Disposition: %*s %*s filename=\"%1023[^\"]",
+               fname);
+        j = i + 2;
+      }
+    }
+
+    // Give up if the headers are not what we expect
+    if (fname[0] == '\0') {
+      break;
+    }
+
+    // Move data to the beginning of the buffer
+    assert(len >= headers_len);
+    memmove(buf, &buf[headers_len], len - headers_len);
+    len -= headers_len;
+
+    // We open the file with exclusive lock held. This guarantee us
+    // there is no other thread can save into the same file simultaneously.
+    fp = NULL;
+    // Construct destination file name. Do not allow paths to have slashes.
+    if ((s = strrchr(fname, '/')) == NULL) {
+      s = fname;
+    }
+    // Open file in binary mode with exclusive lock set
+    snprintf(path, sizeof(path), "%s/%s", destination_dir, s);
+    if ((fp = fopen(path, "wbx")) == NULL) {
+      break;
+    }
+
+    // Read POST data, write into file until boundary is found.
+    n = 0;
+    do {
+      len += n;
+      for (i = 0; i < len - bl; i++) {
+        if (!memcmp(&buf[i], "\r\n--", 4) &&
+            !memcmp(&buf[i + 4], boundary, boundary_len)) {
+          // Found boundary, that's the end of file data.
+          (void) fwrite(buf, 1, i, fp);
+          num_uploaded_files++;
+          conn->request_info.ev_data = (void *) path;
+          call_user(conn, MG_UPLOAD);
+          memmove(buf, &buf[i + bl], len - (i + bl));
+          len -= i + bl;
+          break;
+        }
+      }
+      if (len > bl) {
+        fwrite(buf, 1, len - bl, fp);
+        memmove(buf, &buf[len - bl], len - bl);
+        len = bl;
+      }
+    } while ((n = mg_read(conn, buf + len, sizeof(buf) - len)) > 0);
+    fclose(fp);
+  }
+
+  return num_uploaded_files;
+}
+
 // This is the heart of the Mongoose's logic.
 // This function is called when the request is read, parsed and validated,
 // and Mongoose must decide what action to take: serve a file, or

+ 11 - 0
mongoose.h

@@ -127,6 +127,11 @@ enum mg_event {
   // Callback's return value is ignored.
   // ev_data contains lua_State pointer.
   MG_INIT_LUA,
+
+  // Mongoose has uploaded file to a temporary directory.
+  // Callback's return value is ignored.
+  // ev_data contains NUL-terminated file name.
+  MG_UPLOAD,
 };
 
 
@@ -336,6 +341,12 @@ FILE *mg_fetch(struct mg_context *ctx, const char *url, const char *path,
                char *buf, size_t buf_len, struct mg_request_info *request_info);
 
 
+// File upload functionality. Each uploaded file gets saved into a temporary
+// file and MG_UPLOAD event is sent.
+// Return number of uploaded files.
+int mg_upload(struct mg_connection *conn, const char *destination_dir);
+
+
 // Convenience function -- create detached thread.
 // Return: 0 on success, non-0 on error.
 typedef void * (*mg_thread_func_t)(void *);