Remove src subdir
authorPetr Štetiar <ynezz@true.cz>
Sun, 11 Oct 2020 12:47:43 +0000 (14:47 +0200)
committerPetr Štetiar <ynezz@true.cz>
Sun, 11 Oct 2020 12:47:43 +0000 (14:47 +0200)
It makes no sense now as the project now lives as a separate C project.

Signed-off-by: Petr Štetiar <ynezz@true.cz>
CMakeLists.txt [new file with mode: 0644]
main.c [new file with mode: 0644]
multipart_parser.c [new file with mode: 0644]
multipart_parser.h [new file with mode: 0644]
src/CMakeLists.txt [deleted file]
src/main.c [deleted file]
src/multipart_parser.c [deleted file]
src/multipart_parser.h [deleted file]

diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c7c9d40
--- /dev/null
@@ -0,0 +1,25 @@
+cmake_minimum_required(VERSION 2.6)
+
+PROJECT(cgi-io C)
+
+INCLUDE(CheckFunctionExists)
+
+FIND_PATH(ubus_include_dir libubus.h)
+FIND_LIBRARY(ubox NAMES ubox)
+FIND_LIBRARY(ubus NAMES ubus)
+INCLUDE_DIRECTORIES(${ubus_include_dir})
+
+ADD_DEFINITIONS(-Os -Wall -Werror -Wextra --std=gnu99 -g3)
+ADD_DEFINITIONS(-Wno-unused-parameter -Wmissing-declarations)
+
+SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
+
+IF(APPLE)
+  INCLUDE_DIRECTORIES(/opt/local/include)
+  LINK_DIRECTORIES(/opt/local/lib)
+ENDIF()
+
+ADD_EXECUTABLE(cgi-io main.c multipart_parser.c)
+TARGET_LINK_LIBRARIES(cgi-io ${ubox} ${ubus})
+
+INSTALL(TARGETS cgi-io RUNTIME DESTINATION sbin)
diff --git a/main.c b/main.c
new file mode 100644 (file)
index 0000000..549121f
--- /dev/null
+++ b/main.c
@@ -0,0 +1,1116 @@
+/*
+ * cgi-io - LuCI non-RPC helper
+ *
+ *   Copyright (C) 2013 Jo-Philipp Wich <jo@mein.io>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define _GNU_SOURCE /* splice(), SPLICE_F_MORE */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/sendfile.h>
+#include <sys/ioctl.h>
+#include <linux/fs.h>
+
+#include <libubus.h>
+#include <libubox/blobmsg.h>
+
+#include "multipart_parser.h"
+
+#ifndef O_TMPFILE
+#define O_TMPFILE      (020000000 | O_DIRECTORY)
+#endif
+
+#define READ_BLOCK 4096
+#define POST_LIMIT 131072
+
+enum part {
+       PART_UNKNOWN,
+       PART_SESSIONID,
+       PART_FILENAME,
+       PART_FILEMODE,
+       PART_FILEDATA
+};
+
+const char *parts[] = {
+       "(bug)",
+       "sessionid",
+       "filename",
+       "filemode",
+       "filedata",
+};
+
+struct state
+{
+       bool is_content_disposition;
+       enum part parttype;
+       char *sessionid;
+       char *filename;
+       bool filedata;
+       int filemode;
+       int filefd;
+       int tempfd;
+};
+
+enum {
+       SES_ACCESS,
+       __SES_MAX,
+};
+
+static const struct blobmsg_policy ses_policy[__SES_MAX] = {
+       [SES_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_BOOL },
+};
+
+
+static struct state st;
+
+static void
+session_access_cb(struct ubus_request *req, int type, struct blob_attr *msg)
+{
+       struct blob_attr *tb[__SES_MAX];
+       bool *allow = (bool *)req->priv;
+
+       if (!msg)
+               return;
+
+       blobmsg_parse(ses_policy, __SES_MAX, tb, blob_data(msg), blob_len(msg));
+
+       if (tb[SES_ACCESS])
+               *allow = blobmsg_get_bool(tb[SES_ACCESS]);
+}
+
+static bool
+session_access(const char *sid, const char *scope, const char *obj, const char *func)
+{
+       uint32_t id;
+       bool allow = false;
+       struct ubus_context *ctx;
+       static struct blob_buf req;
+
+       ctx = ubus_connect(NULL);
+
+       if (!ctx || ubus_lookup_id(ctx, "session", &id))
+               goto out;
+
+       blob_buf_init(&req, 0);
+       blobmsg_add_string(&req, "ubus_rpc_session", sid);
+       blobmsg_add_string(&req, "scope", scope);
+       blobmsg_add_string(&req, "object", obj);
+       blobmsg_add_string(&req, "function", func);
+
+       ubus_invoke(ctx, id, "access", req.head, session_access_cb, &allow, 500);
+
+out:
+       if (ctx)
+               ubus_free(ctx);
+
+       return allow;
+}
+
+static char *
+checksum(const char *applet, size_t sumlen, const char *file)
+{
+       pid_t pid;
+       int r;
+       int fds[2];
+       static char chksum[65];
+
+       if (pipe(fds))
+               return NULL;
+
+       switch ((pid = fork()))
+       {
+       case -1:
+               return NULL;
+
+       case 0:
+               uloop_done();
+
+               dup2(fds[1], 1);
+
+               close(0);
+               close(2);
+               close(fds[0]);
+               close(fds[1]);
+
+               if (execl("/bin/busybox", "/bin/busybox", applet, file, NULL))
+                       return NULL;
+
+               break;
+
+       default:
+               memset(chksum, 0, sizeof(chksum));
+               r = read(fds[0], chksum, sumlen);
+
+               waitpid(pid, NULL, 0);
+               close(fds[0]);
+               close(fds[1]);
+
+               if (r < 0)
+                       return NULL;
+       }
+
+       return chksum;
+}
+
+static char *
+datadup(const void *in, size_t len)
+{
+       char *out = malloc(len + 1);
+
+       if (!out)
+               return NULL;
+
+       memcpy(out, in, len);
+
+       *(out + len) = 0;
+
+       return out;
+}
+
+static bool
+urldecode(char *buf)
+{
+       char *c, *p;
+
+       if (!buf || !*buf)
+               return true;
+
+#define hex(x) \
+       (((x) <= '9') ? ((x) - '0') : \
+               (((x) <= 'F') ? ((x) - 'A' + 10) : \
+                       ((x) - 'a' + 10)))
+
+       for (c = p = buf; *p; c++)
+       {
+               if (*p == '%')
+               {
+                       if (!isxdigit(*(p + 1)) || !isxdigit(*(p + 2)))
+                               return false;
+
+                       *c = (char)(16 * hex(*(p + 1)) + hex(*(p + 2)));
+
+                       p += 3;
+               }
+               else if (*p == '+')
+               {
+                       *c = ' ';
+                       p++;
+               }
+               else
+               {
+                       *c = *p++;
+               }
+       }
+
+       *c = 0;
+
+       return true;
+}
+
+static char *
+postdecode(char **fields, int n_fields)
+{
+       const char *var;
+       char *p, *postbuf;
+       int i, field, found = 0;
+       ssize_t len = 0, rlen = 0, content_length = 0;
+
+       var = getenv("CONTENT_TYPE");
+
+       if (!var || strncmp(var, "application/x-www-form-urlencoded", 33))
+               return NULL;
+
+       var = getenv("CONTENT_LENGTH");
+
+       if (!var)
+               return NULL;
+
+       content_length = strtol(var, &p, 10);
+
+       if (p == var || content_length <= 0 || content_length >= POST_LIMIT)
+               return NULL;
+
+       postbuf = calloc(1, content_length + 1);
+
+       if (postbuf == NULL)
+               return NULL;
+
+       for (len = 0; len < content_length; )
+       {
+               rlen = read(0, postbuf + len, content_length - len);
+
+               if (rlen <= 0)
+                       break;
+
+               len += rlen;
+       }
+
+       if (len < content_length)
+       {
+               free(postbuf);
+               return NULL;
+       }
+
+       for (p = postbuf, i = 0; i <= len; i++)
+       {
+               if (postbuf[i] == '=')
+               {
+                       postbuf[i] = 0;
+
+                       for (field = 0; field < (n_fields * 2); field += 2)
+                       {
+                               if (!strcmp(p, fields[field]))
+                               {
+                                       fields[field + 1] = postbuf + i + 1;
+                                       found++;
+                               }
+                       }
+               }
+               else if (postbuf[i] == '&' || postbuf[i] == '\0')
+               {
+                       postbuf[i] = 0;
+
+                       if (found >= n_fields)
+                               break;
+
+                       p = postbuf + i + 1;
+               }
+       }
+
+       for (field = 0; field < (n_fields * 2); field += 2)
+       {
+               if (!urldecode(fields[field + 1]))
+               {
+                       free(postbuf);
+                       return NULL;
+               }
+       }
+
+       return postbuf;
+}
+
+static char *
+canonicalize_path(const char *path, size_t len)
+{
+       char *canonpath, *cp;
+       const char *p, *e;
+
+       if (path == NULL || *path == '\0')
+               return NULL;
+
+       canonpath = datadup(path, len);
+
+       if (canonpath == NULL)
+               return NULL;
+
+       /* normalize */
+       for (cp = canonpath, p = path, e = path + len; p < e; ) {
+               if (*p != '/')
+                       goto next;
+
+               /* skip repeating / */
+               if ((p + 1 < e) && (p[1] == '/')) {
+                       p++;
+                       continue;
+               }
+
+               /* /./ or /../ */
+               if ((p + 1 < e) && (p[1] == '.')) {
+                       /* skip /./ */
+                       if ((p + 2 >= e) || (p[2] == '/')) {
+                               p += 2;
+                               continue;
+                       }
+
+                       /* collapse /x/../ */
+                       if ((p + 2 < e) && (p[2] == '.') && ((p + 3 >= e) || (p[3] == '/'))) {
+                               while ((cp > canonpath) && (*--cp != '/'))
+                                       ;
+
+                               p += 3;
+                               continue;
+                       }
+               }
+
+next:
+               *cp++ = *p++;
+       }
+
+       /* remove trailing slash if not root / */
+       if ((cp > canonpath + 1) && (cp[-1] == '/'))
+               cp--;
+       else if (cp == canonpath)
+               *cp++ = '/';
+
+       *cp = '\0';
+
+       return canonpath;
+}
+
+static int
+response(bool success, const char *message)
+{
+       char *chksum;
+       struct stat s;
+
+       printf("Status: 200 OK\r\n");
+       printf("Content-Type: text/plain\r\n\r\n{\n");
+
+       if (success)
+       {
+               if (!stat(st.filename, &s))
+                       printf("\t\"size\": %u,\n", (unsigned int)s.st_size);
+               else
+                       printf("\t\"size\": null,\n");
+
+               chksum = checksum("md5sum", 32, st.filename);
+               printf("\t\"checksum\": %s%s%s,\n",
+                       chksum ? "\"" : "",
+                       chksum ? chksum : "null",
+                       chksum ? "\"" : "");
+
+               chksum = checksum("sha256sum", 64, st.filename);
+               printf("\t\"sha256sum\": %s%s%s\n",
+                       chksum ? "\"" : "",
+                       chksum ? chksum : "null",
+                       chksum ? "\"" : "");
+       }
+       else
+       {
+               if (message)
+                       printf("\t\"message\": \"%s\",\n", message);
+
+               printf("\t\"failure\": [ %u, \"%s\" ]\n", errno, strerror(errno));
+
+               if (st.filefd > -1)
+                       unlink(st.filename);
+       }
+
+       printf("}\n");
+
+       return -1;
+}
+
+static int
+failure(int code, int e, const char *message)
+{
+       printf("Status: %d %s\r\n", code, message);
+       printf("Content-Type: text/plain\r\n\r\n");
+       printf("%s", message);
+
+       if (e)
+               printf(": %s", strerror(e));
+
+       printf("\n");
+
+       return -1;
+}
+
+static int
+filecopy(void)
+{
+       int len;
+       char buf[READ_BLOCK];
+
+       if (!st.filedata)
+       {
+               close(st.tempfd);
+               errno = EINVAL;
+               return response(false, "No file data received");
+       }
+
+       snprintf(buf, sizeof(buf), "/proc/self/fd/%d", st.tempfd);
+
+       if (unlink(st.filename) < 0 && errno != ENOENT)
+       {
+               close(st.tempfd);
+               return response(false, "Failed to unlink existing file");
+       }
+
+       if (linkat(AT_FDCWD, buf, AT_FDCWD, st.filename, AT_SYMLINK_FOLLOW) < 0)
+       {
+               if (lseek(st.tempfd, 0, SEEK_SET) < 0)
+               {
+                       close(st.tempfd);
+                       return response(false, "Failed to rewind temp file");
+               }
+
+               st.filefd = open(st.filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
+
+               if (st.filefd < 0)
+               {
+                       close(st.tempfd);
+                       return response(false, "Failed to open target file");
+               }
+
+               while ((len = read(st.tempfd, buf, sizeof(buf))) > 0)
+               {
+                       if (write(st.filefd, buf, len) != len)
+                       {
+                               close(st.tempfd);
+                               close(st.filefd);
+                               return response(false, "I/O failure while writing target file");
+                       }
+               }
+
+               close(st.filefd);
+       }
+
+       close(st.tempfd);
+
+       if (chmod(st.filename, st.filemode))
+               return response(false, "Failed to chmod target file");
+
+       return 0;
+}
+
+static int
+header_field(multipart_parser *p, const char *data, size_t len)
+{
+       st.is_content_disposition = !strncasecmp(data, "Content-Disposition", len);
+       return 0;
+}
+
+static int
+header_value(multipart_parser *p, const char *data, size_t len)
+{
+       size_t i, j;
+
+       if (!st.is_content_disposition)
+               return 0;
+
+       if (len < 10 || strncasecmp(data, "form-data", 9))
+               return 0;
+
+       for (data += 9, len -= 9; *data == ' ' || *data == ';'; data++, len--);
+
+       if (len < 8 || strncasecmp(data, "name=\"", 6))
+               return 0;
+
+       for (data += 6, len -= 6, i = 0; i <= len; i++)
+       {
+               if (*(data + i) != '"')
+                       continue;
+
+               for (j = 1; j < sizeof(parts) / sizeof(parts[0]); j++)
+                       if (!strncmp(data, parts[j], i))
+                               st.parttype = j;
+
+               break;
+       }
+
+       return 0;
+}
+
+static int
+data_begin_cb(multipart_parser *p)
+{
+       if (st.parttype == PART_FILEDATA)
+       {
+               if (!st.sessionid)
+                       return response(false, "File data without session");
+
+               if (!st.filename)
+                       return response(false, "File data without name");
+
+               if (!session_access(st.sessionid, "file", st.filename, "write"))
+                       return response(false, "Access to path denied by ACL");
+
+               st.tempfd = open("/tmp", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
+
+               if (st.tempfd < 0)
+                       return response(false, "Failed to create temporary file");
+       }
+
+       return 0;
+}
+
+static int
+data_cb(multipart_parser *p, const char *data, size_t len)
+{
+       int wlen = len;
+
+       switch (st.parttype)
+       {
+       case PART_SESSIONID:
+               st.sessionid = datadup(data, len);
+               break;
+
+       case PART_FILENAME:
+               st.filename = canonicalize_path(data, len);
+               break;
+
+       case PART_FILEMODE:
+               st.filemode = strtoul(data, NULL, 8);
+               break;
+
+       case PART_FILEDATA:
+               if (write(st.tempfd, data, len) != wlen)
+               {
+                       close(st.tempfd);
+                       return response(false, "I/O failure while writing temporary file");
+               }
+
+               if (!st.filedata)
+                       st.filedata = !!wlen;
+
+               break;
+
+       default:
+               break;
+       }
+
+       return 0;
+}
+
+static int
+data_end_cb(multipart_parser *p)
+{
+       if (st.parttype == PART_SESSIONID)
+       {
+               if (!session_access(st.sessionid, "cgi-io", "upload", "write"))
+               {
+                       errno = EPERM;
+                       return response(false, "Upload permission denied");
+               }
+       }
+       else if (st.parttype == PART_FILEDATA)
+       {
+               if (st.tempfd < 0)
+                       return response(false, "Internal program failure");
+
+#if 0
+               /* prepare directory */
+               for (ptr = st.filename; *ptr; ptr++)
+               {
+                       if (*ptr == '/')
+                       {
+                               *ptr = 0;
+
+                               if (mkdir(st.filename, 0755))
+                               {
+                                       unlink(st.tmpname);
+                                       return response(false, "Failed to create destination directory");
+                               }
+
+                               *ptr = '/';
+                       }
+               }
+#endif
+
+               if (filecopy())
+                       return -1;
+
+               return response(true, NULL);
+       }
+
+       st.parttype = PART_UNKNOWN;
+       return 0;
+}
+
+static multipart_parser *
+init_parser(void)
+{
+       char *boundary;
+       const char *var;
+
+       multipart_parser *p;
+       static multipart_parser_settings s = {
+               .on_part_data        = data_cb,
+               .on_headers_complete = data_begin_cb,
+               .on_part_data_end    = data_end_cb,
+               .on_header_field     = header_field,
+               .on_header_value     = header_value
+       };
+
+       var = getenv("CONTENT_TYPE");
+
+       if (!var || strncmp(var, "multipart/form-data;", 20))
+               return NULL;
+
+       for (var += 20; *var && *var != '='; var++);
+
+       if (*var++ != '=')
+               return NULL;
+
+       boundary = malloc(strlen(var) + 3);
+
+       if (!boundary)
+               return NULL;
+
+       strcpy(boundary, "--");
+       strcpy(boundary + 2, var);
+
+       st.tempfd = -1;
+       st.filefd = -1;
+       st.filemode = 0600;
+
+       p = multipart_parser_init(boundary, &s);
+
+       free(boundary);
+
+       return p;
+}
+
+static int
+main_upload(int argc, char *argv[])
+{
+       int rem, len;
+       bool done = false;
+       char buf[READ_BLOCK];
+       multipart_parser *p;
+
+       p = init_parser();
+
+       if (!p)
+       {
+               errno = EINVAL;
+               return response(false, "Invalid request");
+       }
+
+       while ((len = read(0, buf, sizeof(buf))) > 0)
+       {
+               if (!done) {
+                       rem = multipart_parser_execute(p, buf, len);
+                       done = (rem < len);
+               }
+       }
+
+       multipart_parser_free(p);
+
+       return 0;
+}
+
+static void
+free_charp(char **ptr)
+{
+       free(*ptr);
+}
+
+#define autochar __attribute__((__cleanup__(free_charp))) char
+
+static int
+main_download(int argc, char **argv)
+{
+       char *fields[] = { "sessionid", NULL, "path", NULL, "filename", NULL, "mimetype", NULL };
+       unsigned long long size = 0;
+       char *p, buf[READ_BLOCK];
+       ssize_t len = 0;
+       struct stat s;
+       int rfd;
+
+       autochar *post = postdecode(fields, 4);
+
+       if (!fields[1] || !session_access(fields[1], "cgi-io", "download", "read"))
+               return failure(403, 0, "Download permission denied");
+
+       if (!fields[3] || !session_access(fields[1], "file", fields[3], "read"))
+               return failure(403, 0, "Access to path denied by ACL");
+
+       if (stat(fields[3], &s))
+               return failure(404, errno, "Failed to stat requested path");
+
+       if (!S_ISREG(s.st_mode) && !S_ISBLK(s.st_mode))
+               return failure(403, 0, "Requested path is not a regular file or block device");
+
+       for (p = fields[5]; p && *p; p++)
+               if (!isalnum(*p) && !strchr(" ()<>@,;:[]?.=%-", *p))
+                       return failure(400, 0, "Invalid characters in filename");
+
+       for (p = fields[7]; p && *p; p++)
+               if (!isalnum(*p) && !strchr(" .;=/-", *p))
+                       return failure(400, 0, "Invalid characters in mimetype");
+
+       rfd = open(fields[3], O_RDONLY);
+
+       if (rfd < 0)
+               return failure(500, errno, "Failed to open requested path");
+
+       if (S_ISBLK(s.st_mode))
+               ioctl(rfd, BLKGETSIZE64, &size);
+       else
+               size = (unsigned long long)s.st_size;
+
+       printf("Status: 200 OK\r\n");
+       printf("Content-Type: %s\r\n", fields[7] ? fields[7] : "application/octet-stream");
+
+       if (fields[5])
+               printf("Content-Disposition: attachment; filename=\"%s\"\r\n", fields[5]);
+
+       if (size > 0) {
+               printf("Content-Length: %llu\r\n\r\n", size);
+               fflush(stdout);
+
+               while (size > 0) {
+                       len = sendfile(1, rfd, NULL, size);
+
+                       if (len == -1) {
+                               if (errno == ENOSYS || errno == EINVAL) {
+                                       while ((len = read(rfd, buf, sizeof(buf))) > 0)
+                                               fwrite(buf, len, 1, stdout);
+
+                                       fflush(stdout);
+                                       break;
+                               }
+
+                               if (errno == EINTR || errno == EAGAIN)
+                                       continue;
+                       }
+
+                       if (len <= 0)
+                               break;
+
+                       size -= len;
+               }
+       }
+       else {
+               printf("\r\n");
+
+               while ((len = read(rfd, buf, sizeof(buf))) > 0)
+                       fwrite(buf, len, 1, stdout);
+
+               fflush(stdout);
+       }
+
+       close(rfd);
+
+       return 0;
+}
+
+static int
+main_backup(int argc, char **argv)
+{
+       pid_t pid;
+       time_t now;
+       int r;
+       int len;
+       int status;
+       int fds[2];
+       char datestr[16] = { 0 };
+       char hostname[64] = { 0 };
+       char *fields[] = { "sessionid", NULL };
+
+       autochar *post = postdecode(fields, 1);
+
+       if (!fields[1] || !session_access(fields[1], "cgi-io", "backup", "read"))
+               return failure(403, 0, "Backup permission denied");
+
+       if (pipe(fds))
+               return failure(500, errno, "Failed to spawn pipe");
+
+       switch ((pid = fork()))
+       {
+       case -1:
+               return failure(500, errno, "Failed to fork process");
+
+       case 0:
+               dup2(fds[1], 1);
+
+               close(0);
+               close(2);
+               close(fds[0]);
+               close(fds[1]);
+
+               r = chdir("/");
+               if (r < 0)
+                       return failure(500, errno, "Failed chdir('/')");
+
+               execl("/sbin/sysupgrade", "/sbin/sysupgrade",
+                     "--create-backup", "-", NULL);
+
+               return -1;
+
+       default:
+               close(fds[1]);
+
+               now = time(NULL);
+               strftime(datestr, sizeof(datestr) - 1, "%Y-%m-%d", localtime(&now));
+
+               if (gethostname(hostname, sizeof(hostname) - 1))
+                       sprintf(hostname, "OpenWrt");
+
+               printf("Status: 200 OK\r\n");
+               printf("Content-Type: application/x-targz\r\n");
+               printf("Content-Disposition: attachment; "
+                      "filename=\"backup-%s-%s.tar.gz\"\r\n\r\n", hostname, datestr);
+
+               fflush(stdout);
+
+               do {
+                       len = splice(fds[0], NULL, 1, NULL, READ_BLOCK, SPLICE_F_MORE);
+               } while (len > 0);
+
+               waitpid(pid, &status, 0);
+
+               close(fds[0]);
+
+               return 0;
+       }
+}
+
+
+static const char *
+lookup_executable(const char *cmd)
+{
+       size_t plen = 0, clen = strlen(cmd) + 1;
+       static char path[PATH_MAX];
+       char *search, *p;
+       struct stat s;
+
+       if (!stat(cmd, &s) && S_ISREG(s.st_mode))
+               return cmd;
+
+       search = getenv("PATH");
+
+       if (!search)
+               search = "/bin:/usr/bin:/sbin:/usr/sbin";
+
+       p = search;
+
+       do {
+               if (*p != ':' && *p != '\0')
+                       continue;
+
+               plen = p - search;
+
+               if ((plen + clen) >= sizeof(path))
+                       continue;
+
+               strncpy(path, search, plen);
+               sprintf(path + plen, "/%s", cmd);
+
+               if (!stat(path, &s) && S_ISREG(s.st_mode))
+                       return path;
+
+               search = p + 1;
+       } while (*p++);
+
+       return NULL;
+}
+
+static char **
+parse_command(const char *cmdline)
+{
+       const char *p = cmdline, *s;
+       char **argv = NULL, *out;
+       size_t arglen = 0;
+       int argnum = 0;
+       bool esc;
+
+       while (isspace(*cmdline))
+               cmdline++;
+
+       for (p = cmdline, s = p, esc = false; p; p++) {
+               if (esc) {
+                       esc = false;
+               }
+               else if (*p == '\\' && p[1] != 0) {
+                       esc = true;
+               }
+               else if (isspace(*p) || *p == 0) {
+                       if (p > s) {
+                               argnum += 1;
+                               arglen += sizeof(char *) + (p - s) + 1;
+                       }
+
+                       s = p + 1;
+               }
+
+               if (*p == 0)
+                       break;
+       }
+
+       if (arglen == 0)
+               return NULL;
+
+       argv = calloc(1, arglen + sizeof(char *));
+
+       if (!argv)
+               return NULL;
+
+       out = (char *)argv + sizeof(char *) * (argnum + 1);
+       argv[0] = out;
+
+       for (p = cmdline, s = p, esc = false, argnum = 0; p; p++) {
+               if (esc) {
+                       esc = false;
+                       *out++ = *p;
+               }
+               else if (*p == '\\' && p[1] != 0) {
+                       esc = true;
+               }
+               else if (isspace(*p) || *p == 0) {
+                       if (p > s) {
+                               *out++ = ' ';
+                               argv[++argnum] = out;
+                       }
+
+                       s = p + 1;
+               }
+               else {
+                       *out++ = *p;
+               }
+
+               if (*p == 0)
+                       break;
+       }
+
+       argv[argnum] = NULL;
+       out[-1] = 0;
+
+       return argv;
+}
+
+static int
+main_exec(int argc, char **argv)
+{
+       char *fields[] = { "sessionid", NULL, "command", NULL, "filename", NULL, "mimetype", NULL };
+       int i, devnull, status, fds[2];
+       bool allowed = false;
+       ssize_t len = 0;
+       const char *exe;
+       char *p, **args;
+       pid_t pid;
+
+       autochar *post = postdecode(fields, 4);
+
+       if (!fields[1] || !session_access(fields[1], "cgi-io", "exec", "read"))
+               return failure(403, 0, "Exec permission denied");
+
+       for (p = fields[5]; p && *p; p++)
+               if (!isalnum(*p) && !strchr(" ()<>@,;:[]?.=%-", *p))
+                       return failure(400, 0, "Invalid characters in filename");
+
+       for (p = fields[7]; p && *p; p++)
+               if (!isalnum(*p) && !strchr(" .;=/-", *p))
+                       return failure(400, 0, "Invalid characters in mimetype");
+
+       args = fields[3] ? parse_command(fields[3]) : NULL;
+
+       if (!args)
+               return failure(400, 0, "Invalid command parameter");
+
+       /* First check if we find an ACL match for the whole cmdline ... */
+       allowed = session_access(fields[1], "file", args[0], "exec");
+
+       /* Now split the command vector... */
+       for (i = 1; args[i]; i++)
+               args[i][-1] = 0;
+
+       /* Find executable... */
+       exe = lookup_executable(args[0]);
+
+       if (!exe) {
+               free(args);
+               return failure(404, 0, "Executable not found");
+       }
+
+       /* If there was no ACL match, check for a match on the executable */
+       if (!allowed && !session_access(fields[1], "file", exe, "exec")) {
+               free(args);
+               return failure(403, 0, "Access to command denied by ACL");
+       }
+
+       if (pipe(fds)) {
+               free(args);
+               return failure(500, errno, "Failed to spawn pipe");
+       }
+
+       switch ((pid = fork()))
+       {
+       case -1:
+               free(args);
+               close(fds[0]);
+               close(fds[1]);
+               return failure(500, errno, "Failed to fork process");
+
+       case 0:
+               devnull = open("/dev/null", O_RDWR);
+
+               if (devnull > -1) {
+                       dup2(devnull, 0);
+                       dup2(devnull, 2);
+                       close(devnull);
+               }
+               else {
+                       close(0);
+                       close(2);
+               }
+
+               dup2(fds[1], 1);
+               close(fds[0]);
+               close(fds[1]);
+
+               if (chdir("/") < 0) {
+                       free(args);
+                       return failure(500, errno, "Failed chdir('/')");
+               }
+
+               if (execv(exe, args) < 0) {
+                       free(args);
+                       return failure(500, errno, "Failed execv(...)");
+               }
+
+               return -1;
+
+       default:
+               close(fds[1]);
+
+               printf("Status: 200 OK\r\n");
+               printf("Content-Type: %s\r\n",
+                      fields[7] ? fields[7] : "application/octet-stream");
+
+               if (fields[5])
+                       printf("Content-Disposition: attachment; filename=\"%s\"\r\n",
+                              fields[5]);
+
+               printf("\r\n");
+               fflush(stdout);
+
+               do {
+                       len = splice(fds[0], NULL, 1, NULL, READ_BLOCK, SPLICE_F_MORE);
+               } while (len > 0);
+
+               waitpid(pid, &status, 0);
+
+               close(fds[0]);
+               free(args);
+
+               return 0;
+       }
+}
+
+int main(int argc, char **argv)
+{
+       if (strstr(argv[0], "cgi-upload"))
+               return main_upload(argc, argv);
+       else if (strstr(argv[0], "cgi-download"))
+               return main_download(argc, argv);
+       else if (strstr(argv[0], "cgi-backup"))
+               return main_backup(argc, argv);
+       else if (strstr(argv[0], "cgi-exec"))
+               return main_exec(argc, argv);
+
+       return -1;
+}
diff --git a/multipart_parser.c b/multipart_parser.c
new file mode 100644 (file)
index 0000000..ee82c82
--- /dev/null
@@ -0,0 +1,309 @@
+/* Based on node-formidable by Felix Geisendörfer
+ * Igor Afonov - afonov@gmail.com - 2012
+ * MIT License - http://www.opensource.org/licenses/mit-license.php
+ */
+
+#include "multipart_parser.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+static void multipart_log(const char * format, ...)
+{
+#ifdef DEBUG_MULTIPART
+    va_list args;
+    va_start(args, format);
+
+    fprintf(stderr, "[HTTP_MULTIPART_PARSER] %s:%d: ", __FILE__, __LINE__);
+    vfprintf(stderr, format, args);
+    fprintf(stderr, "\n");
+#endif
+}
+
+#define NOTIFY_CB(FOR)                                                 \
+do {                                                                   \
+  if (p->settings->on_##FOR) {                                         \
+    if (p->settings->on_##FOR(p) != 0) {                               \
+      return i;                                                        \
+    }                                                                  \
+  }                                                                    \
+} while (0)
+
+#define EMIT_DATA_CB(FOR, ptr, len)                                    \
+do {                                                                   \
+  if (p->settings->on_##FOR) {                                         \
+    if (p->settings->on_##FOR(p, ptr, len) != 0) {                     \
+      return i;                                                        \
+    }                                                                  \
+  }                                                                    \
+} while (0)
+
+
+#define LF 10
+#define CR 13
+
+struct multipart_parser {
+  void * data;
+
+  size_t index;
+  size_t boundary_length;
+
+  unsigned char state;
+
+  const multipart_parser_settings* settings;
+
+  char* lookbehind;
+  char multipart_boundary[1];
+};
+
+enum state {
+  s_uninitialized = 1,
+  s_start,
+  s_start_boundary,
+  s_header_field_start,
+  s_header_field,
+  s_headers_almost_done,
+  s_header_value_start,
+  s_header_value,
+  s_header_value_almost_done,
+  s_part_data_start,
+  s_part_data,
+  s_part_data_almost_boundary,
+  s_part_data_boundary,
+  s_part_data_almost_end,
+  s_part_data_end,
+  s_part_data_final_hyphen,
+  s_end
+};
+
+multipart_parser* multipart_parser_init
+    (const char *boundary, const multipart_parser_settings* settings) {
+
+  multipart_parser* p = malloc(sizeof(multipart_parser) +
+                               strlen(boundary) +
+                               strlen(boundary) + 9);
+
+  strcpy(p->multipart_boundary, boundary);
+  p->boundary_length = strlen(boundary);
+
+  p->lookbehind = (p->multipart_boundary + p->boundary_length + 1);
+
+  p->index = 0;
+  p->state = s_start;
+  p->settings = settings;
+
+  return p;
+}
+
+void multipart_parser_free(multipart_parser* p) {
+  free(p);
+}
+
+void multipart_parser_set_data(multipart_parser *p, void *data) {
+    p->data = data;
+}
+
+void *multipart_parser_get_data(multipart_parser *p) {
+    return p->data;
+}
+
+size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len) {
+  size_t i = 0;
+  size_t mark = 0;
+  char c, cl;
+  int is_last = 0;
+
+  while(i < len) {
+    c = buf[i];
+    is_last = (i == (len - 1));
+    switch (p->state) {
+      case s_start:
+        multipart_log("s_start");
+        p->index = 0;
+        p->state = s_start_boundary;
+
+      /* fallthrough */
+      case s_start_boundary:
+        multipart_log("s_start_boundary");
+        if (p->index == p->boundary_length) {
+          if (c != CR) {
+            return i;
+          }
+          p->index++;
+          break;
+        } else if (p->index == (p->boundary_length + 1)) {
+          if (c != LF) {
+            return i;
+          }
+          p->index = 0;
+          NOTIFY_CB(part_data_begin);
+          p->state = s_header_field_start;
+          break;
+        }
+        if (c != p->multipart_boundary[p->index]) {
+          return i;
+        }
+        p->index++;
+        break;
+
+      case s_header_field_start:
+        multipart_log("s_header_field_start");
+        mark = i;
+        p->state = s_header_field;
+
+      /* fallthrough */
+      case s_header_field:
+        multipart_log("s_header_field");
+        if (c == CR) {
+          p->state = s_headers_almost_done;
+          break;
+        }
+
+        if (c == '-') {
+          break;
+        }
+
+        if (c == ':') {
+          EMIT_DATA_CB(header_field, buf + mark, i - mark);
+          p->state = s_header_value_start;
+          break;
+        }
+
+        cl = tolower(c);
+        if (cl < 'a' || cl > 'z') {
+          multipart_log("invalid character in header name");
+          return i;
+        }
+        if (is_last)
+            EMIT_DATA_CB(header_field, buf + mark, (i - mark) + 1);
+        break;
+
+      case s_headers_almost_done:
+        multipart_log("s_headers_almost_done");
+        if (c != LF) {
+          return i;
+        }
+
+        p->state = s_part_data_start;
+        break;
+
+      case s_header_value_start:
+        multipart_log("s_header_value_start");
+        if (c == ' ') {
+          break;
+        }
+
+        mark = i;
+        p->state = s_header_value;
+
+      /* fallthrough */
+      case s_header_value:
+        multipart_log("s_header_value");
+        if (c == CR) {
+          EMIT_DATA_CB(header_value, buf + mark, i - mark);
+          p->state = s_header_value_almost_done;
+        }
+        if (is_last)
+            EMIT_DATA_CB(header_value, buf + mark, (i - mark) + 1);
+        break;
+
+      case s_header_value_almost_done:
+        multipart_log("s_header_value_almost_done");
+        if (c != LF) {
+          return i;
+        }
+        p->state = s_header_field_start;
+        break;
+
+      case s_part_data_start:
+        multipart_log("s_part_data_start");
+        NOTIFY_CB(headers_complete);
+        mark = i;
+        p->state = s_part_data;
+
+      /* fallthrough */
+      case s_part_data:
+        multipart_log("s_part_data");
+        if (c == CR) {
+            EMIT_DATA_CB(part_data, buf + mark, i - mark);
+            mark = i;
+            p->state = s_part_data_almost_boundary;
+            p->lookbehind[0] = CR;
+            break;
+        }
+        if (is_last)
+            EMIT_DATA_CB(part_data, buf + mark, (i - mark) + 1);
+        break;
+
+      case s_part_data_almost_boundary:
+        multipart_log("s_part_data_almost_boundary");
+        if (c == LF) {
+            p->state = s_part_data_boundary;
+            p->lookbehind[1] = LF;
+            p->index = 0;
+            break;
+        }
+        EMIT_DATA_CB(part_data, p->lookbehind, 1);
+        p->state = s_part_data;
+        mark = i --;
+        break;
+
+      case s_part_data_boundary:
+        multipart_log("s_part_data_boundary");
+        if (p->multipart_boundary[p->index] != c) {
+          EMIT_DATA_CB(part_data, p->lookbehind, 2 + p->index);
+          p->state = s_part_data;
+          mark = i --;
+          break;
+        }
+        p->lookbehind[2 + p->index] = c;
+        if ((++ p->index) == p->boundary_length) {
+            NOTIFY_CB(part_data_end);
+            p->state = s_part_data_almost_end;
+        }
+        break;
+
+      case s_part_data_almost_end:
+        multipart_log("s_part_data_almost_end");
+        if (c == '-') {
+            p->state = s_part_data_final_hyphen;
+            break;
+        }
+        if (c == CR) {
+            p->state = s_part_data_end;
+            break;
+        }
+        return i;
+
+      case s_part_data_final_hyphen:
+        multipart_log("s_part_data_final_hyphen");
+        if (c == '-') {
+            NOTIFY_CB(body_end);
+            p->state = s_end;
+            break;
+        }
+        return i;
+
+      case s_part_data_end:
+        multipart_log("s_part_data_end");
+        if (c == LF) {
+            p->state = s_header_field_start;
+            NOTIFY_CB(part_data_begin);
+            break;
+        }
+        return i;
+
+      case s_end:
+        multipart_log("s_end: %02X", (int) c);
+        break;
+
+      default:
+        multipart_log("Multipart parser unrecoverable error");
+        return 0;
+    }
+    ++ i;
+  }
+
+  return len;
+}
diff --git a/multipart_parser.h b/multipart_parser.h
new file mode 100644 (file)
index 0000000..87e67f4
--- /dev/null
@@ -0,0 +1,48 @@
+/* Based on node-formidable by Felix Geisendörfer
+ * Igor Afonov - afonov@gmail.com - 2012
+ * MIT License - http://www.opensource.org/licenses/mit-license.php
+ */
+#ifndef _multipart_parser_h
+#define _multipart_parser_h
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <stdlib.h>
+#include <ctype.h>
+
+typedef struct multipart_parser multipart_parser;
+typedef struct multipart_parser_settings multipart_parser_settings;
+typedef struct multipart_parser_state multipart_parser_state;
+
+typedef int (*multipart_data_cb) (multipart_parser*, const char *at, size_t length);
+typedef int (*multipart_notify_cb) (multipart_parser*);
+
+struct multipart_parser_settings {
+  multipart_data_cb on_header_field;
+  multipart_data_cb on_header_value;
+  multipart_data_cb on_part_data;
+
+  multipart_notify_cb on_part_data_begin;
+  multipart_notify_cb on_headers_complete;
+  multipart_notify_cb on_part_data_end;
+  multipart_notify_cb on_body_end;
+};
+
+multipart_parser* multipart_parser_init
+    (const char *boundary, const multipart_parser_settings* settings);
+
+void multipart_parser_free(multipart_parser* p);
+
+size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len);
+
+void multipart_parser_set_data(multipart_parser* p, void* data);
+void * multipart_parser_get_data(multipart_parser* p);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
deleted file mode 100644 (file)
index c7c9d40..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-cmake_minimum_required(VERSION 2.6)
-
-PROJECT(cgi-io C)
-
-INCLUDE(CheckFunctionExists)
-
-FIND_PATH(ubus_include_dir libubus.h)
-FIND_LIBRARY(ubox NAMES ubox)
-FIND_LIBRARY(ubus NAMES ubus)
-INCLUDE_DIRECTORIES(${ubus_include_dir})
-
-ADD_DEFINITIONS(-Os -Wall -Werror -Wextra --std=gnu99 -g3)
-ADD_DEFINITIONS(-Wno-unused-parameter -Wmissing-declarations)
-
-SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
-
-IF(APPLE)
-  INCLUDE_DIRECTORIES(/opt/local/include)
-  LINK_DIRECTORIES(/opt/local/lib)
-ENDIF()
-
-ADD_EXECUTABLE(cgi-io main.c multipart_parser.c)
-TARGET_LINK_LIBRARIES(cgi-io ${ubox} ${ubus})
-
-INSTALL(TARGETS cgi-io RUNTIME DESTINATION sbin)
diff --git a/src/main.c b/src/main.c
deleted file mode 100644 (file)
index 549121f..0000000
+++ /dev/null
@@ -1,1116 +0,0 @@
-/*
- * cgi-io - LuCI non-RPC helper
- *
- *   Copyright (C) 2013 Jo-Philipp Wich <jo@mein.io>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define _GNU_SOURCE /* splice(), SPLICE_F_MORE */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdbool.h>
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <ctype.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <sys/sendfile.h>
-#include <sys/ioctl.h>
-#include <linux/fs.h>
-
-#include <libubus.h>
-#include <libubox/blobmsg.h>
-
-#include "multipart_parser.h"
-
-#ifndef O_TMPFILE
-#define O_TMPFILE      (020000000 | O_DIRECTORY)
-#endif
-
-#define READ_BLOCK 4096
-#define POST_LIMIT 131072
-
-enum part {
-       PART_UNKNOWN,
-       PART_SESSIONID,
-       PART_FILENAME,
-       PART_FILEMODE,
-       PART_FILEDATA
-};
-
-const char *parts[] = {
-       "(bug)",
-       "sessionid",
-       "filename",
-       "filemode",
-       "filedata",
-};
-
-struct state
-{
-       bool is_content_disposition;
-       enum part parttype;
-       char *sessionid;
-       char *filename;
-       bool filedata;
-       int filemode;
-       int filefd;
-       int tempfd;
-};
-
-enum {
-       SES_ACCESS,
-       __SES_MAX,
-};
-
-static const struct blobmsg_policy ses_policy[__SES_MAX] = {
-       [SES_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_BOOL },
-};
-
-
-static struct state st;
-
-static void
-session_access_cb(struct ubus_request *req, int type, struct blob_attr *msg)
-{
-       struct blob_attr *tb[__SES_MAX];
-       bool *allow = (bool *)req->priv;
-
-       if (!msg)
-               return;
-
-       blobmsg_parse(ses_policy, __SES_MAX, tb, blob_data(msg), blob_len(msg));
-
-       if (tb[SES_ACCESS])
-               *allow = blobmsg_get_bool(tb[SES_ACCESS]);
-}
-
-static bool
-session_access(const char *sid, const char *scope, const char *obj, const char *func)
-{
-       uint32_t id;
-       bool allow = false;
-       struct ubus_context *ctx;
-       static struct blob_buf req;
-
-       ctx = ubus_connect(NULL);
-
-       if (!ctx || ubus_lookup_id(ctx, "session", &id))
-               goto out;
-
-       blob_buf_init(&req, 0);
-       blobmsg_add_string(&req, "ubus_rpc_session", sid);
-       blobmsg_add_string(&req, "scope", scope);
-       blobmsg_add_string(&req, "object", obj);
-       blobmsg_add_string(&req, "function", func);
-
-       ubus_invoke(ctx, id, "access", req.head, session_access_cb, &allow, 500);
-
-out:
-       if (ctx)
-               ubus_free(ctx);
-
-       return allow;
-}
-
-static char *
-checksum(const char *applet, size_t sumlen, const char *file)
-{
-       pid_t pid;
-       int r;
-       int fds[2];
-       static char chksum[65];
-
-       if (pipe(fds))
-               return NULL;
-
-       switch ((pid = fork()))
-       {
-       case -1:
-               return NULL;
-
-       case 0:
-               uloop_done();
-
-               dup2(fds[1], 1);
-
-               close(0);
-               close(2);
-               close(fds[0]);
-               close(fds[1]);
-
-               if (execl("/bin/busybox", "/bin/busybox", applet, file, NULL))
-                       return NULL;
-
-               break;
-
-       default:
-               memset(chksum, 0, sizeof(chksum));
-               r = read(fds[0], chksum, sumlen);
-
-               waitpid(pid, NULL, 0);
-               close(fds[0]);
-               close(fds[1]);
-
-               if (r < 0)
-                       return NULL;
-       }
-
-       return chksum;
-}
-
-static char *
-datadup(const void *in, size_t len)
-{
-       char *out = malloc(len + 1);
-
-       if (!out)
-               return NULL;
-
-       memcpy(out, in, len);
-
-       *(out + len) = 0;
-
-       return out;
-}
-
-static bool
-urldecode(char *buf)
-{
-       char *c, *p;
-
-       if (!buf || !*buf)
-               return true;
-
-#define hex(x) \
-       (((x) <= '9') ? ((x) - '0') : \
-               (((x) <= 'F') ? ((x) - 'A' + 10) : \
-                       ((x) - 'a' + 10)))
-
-       for (c = p = buf; *p; c++)
-       {
-               if (*p == '%')
-               {
-                       if (!isxdigit(*(p + 1)) || !isxdigit(*(p + 2)))
-                               return false;
-
-                       *c = (char)(16 * hex(*(p + 1)) + hex(*(p + 2)));
-
-                       p += 3;
-               }
-               else if (*p == '+')
-               {
-                       *c = ' ';
-                       p++;
-               }
-               else
-               {
-                       *c = *p++;
-               }
-       }
-
-       *c = 0;
-
-       return true;
-}
-
-static char *
-postdecode(char **fields, int n_fields)
-{
-       const char *var;
-       char *p, *postbuf;
-       int i, field, found = 0;
-       ssize_t len = 0, rlen = 0, content_length = 0;
-
-       var = getenv("CONTENT_TYPE");
-
-       if (!var || strncmp(var, "application/x-www-form-urlencoded", 33))
-               return NULL;
-
-       var = getenv("CONTENT_LENGTH");
-
-       if (!var)
-               return NULL;
-
-       content_length = strtol(var, &p, 10);
-
-       if (p == var || content_length <= 0 || content_length >= POST_LIMIT)
-               return NULL;
-
-       postbuf = calloc(1, content_length + 1);
-
-       if (postbuf == NULL)
-               return NULL;
-
-       for (len = 0; len < content_length; )
-       {
-               rlen = read(0, postbuf + len, content_length - len);
-
-               if (rlen <= 0)
-                       break;
-
-               len += rlen;
-       }
-
-       if (len < content_length)
-       {
-               free(postbuf);
-               return NULL;
-       }
-
-       for (p = postbuf, i = 0; i <= len; i++)
-       {
-               if (postbuf[i] == '=')
-               {
-                       postbuf[i] = 0;
-
-                       for (field = 0; field < (n_fields * 2); field += 2)
-                       {
-                               if (!strcmp(p, fields[field]))
-                               {
-                                       fields[field + 1] = postbuf + i + 1;
-                                       found++;
-                               }
-                       }
-               }
-               else if (postbuf[i] == '&' || postbuf[i] == '\0')
-               {
-                       postbuf[i] = 0;
-
-                       if (found >= n_fields)
-                               break;
-
-                       p = postbuf + i + 1;
-               }
-       }
-
-       for (field = 0; field < (n_fields * 2); field += 2)
-       {
-               if (!urldecode(fields[field + 1]))
-               {
-                       free(postbuf);
-                       return NULL;
-               }
-       }
-
-       return postbuf;
-}
-
-static char *
-canonicalize_path(const char *path, size_t len)
-{
-       char *canonpath, *cp;
-       const char *p, *e;
-
-       if (path == NULL || *path == '\0')
-               return NULL;
-
-       canonpath = datadup(path, len);
-
-       if (canonpath == NULL)
-               return NULL;
-
-       /* normalize */
-       for (cp = canonpath, p = path, e = path + len; p < e; ) {
-               if (*p != '/')
-                       goto next;
-
-               /* skip repeating / */
-               if ((p + 1 < e) && (p[1] == '/')) {
-                       p++;
-                       continue;
-               }
-
-               /* /./ or /../ */
-               if ((p + 1 < e) && (p[1] == '.')) {
-                       /* skip /./ */
-                       if ((p + 2 >= e) || (p[2] == '/')) {
-                               p += 2;
-                               continue;
-                       }
-
-                       /* collapse /x/../ */
-                       if ((p + 2 < e) && (p[2] == '.') && ((p + 3 >= e) || (p[3] == '/'))) {
-                               while ((cp > canonpath) && (*--cp != '/'))
-                                       ;
-
-                               p += 3;
-                               continue;
-                       }
-               }
-
-next:
-               *cp++ = *p++;
-       }
-
-       /* remove trailing slash if not root / */
-       if ((cp > canonpath + 1) && (cp[-1] == '/'))
-               cp--;
-       else if (cp == canonpath)
-               *cp++ = '/';
-
-       *cp = '\0';
-
-       return canonpath;
-}
-
-static int
-response(bool success, const char *message)
-{
-       char *chksum;
-       struct stat s;
-
-       printf("Status: 200 OK\r\n");
-       printf("Content-Type: text/plain\r\n\r\n{\n");
-
-       if (success)
-       {
-               if (!stat(st.filename, &s))
-                       printf("\t\"size\": %u,\n", (unsigned int)s.st_size);
-               else
-                       printf("\t\"size\": null,\n");
-
-               chksum = checksum("md5sum", 32, st.filename);
-               printf("\t\"checksum\": %s%s%s,\n",
-                       chksum ? "\"" : "",
-                       chksum ? chksum : "null",
-                       chksum ? "\"" : "");
-
-               chksum = checksum("sha256sum", 64, st.filename);
-               printf("\t\"sha256sum\": %s%s%s\n",
-                       chksum ? "\"" : "",
-                       chksum ? chksum : "null",
-                       chksum ? "\"" : "");
-       }
-       else
-       {
-               if (message)
-                       printf("\t\"message\": \"%s\",\n", message);
-
-               printf("\t\"failure\": [ %u, \"%s\" ]\n", errno, strerror(errno));
-
-               if (st.filefd > -1)
-                       unlink(st.filename);
-       }
-
-       printf("}\n");
-
-       return -1;
-}
-
-static int
-failure(int code, int e, const char *message)
-{
-       printf("Status: %d %s\r\n", code, message);
-       printf("Content-Type: text/plain\r\n\r\n");
-       printf("%s", message);
-
-       if (e)
-               printf(": %s", strerror(e));
-
-       printf("\n");
-
-       return -1;
-}
-
-static int
-filecopy(void)
-{
-       int len;
-       char buf[READ_BLOCK];
-
-       if (!st.filedata)
-       {
-               close(st.tempfd);
-               errno = EINVAL;
-               return response(false, "No file data received");
-       }
-
-       snprintf(buf, sizeof(buf), "/proc/self/fd/%d", st.tempfd);
-
-       if (unlink(st.filename) < 0 && errno != ENOENT)
-       {
-               close(st.tempfd);
-               return response(false, "Failed to unlink existing file");
-       }
-
-       if (linkat(AT_FDCWD, buf, AT_FDCWD, st.filename, AT_SYMLINK_FOLLOW) < 0)
-       {
-               if (lseek(st.tempfd, 0, SEEK_SET) < 0)
-               {
-                       close(st.tempfd);
-                       return response(false, "Failed to rewind temp file");
-               }
-
-               st.filefd = open(st.filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
-
-               if (st.filefd < 0)
-               {
-                       close(st.tempfd);
-                       return response(false, "Failed to open target file");
-               }
-
-               while ((len = read(st.tempfd, buf, sizeof(buf))) > 0)
-               {
-                       if (write(st.filefd, buf, len) != len)
-                       {
-                               close(st.tempfd);
-                               close(st.filefd);
-                               return response(false, "I/O failure while writing target file");
-                       }
-               }
-
-               close(st.filefd);
-       }
-
-       close(st.tempfd);
-
-       if (chmod(st.filename, st.filemode))
-               return response(false, "Failed to chmod target file");
-
-       return 0;
-}
-
-static int
-header_field(multipart_parser *p, const char *data, size_t len)
-{
-       st.is_content_disposition = !strncasecmp(data, "Content-Disposition", len);
-       return 0;
-}
-
-static int
-header_value(multipart_parser *p, const char *data, size_t len)
-{
-       size_t i, j;
-
-       if (!st.is_content_disposition)
-               return 0;
-
-       if (len < 10 || strncasecmp(data, "form-data", 9))
-               return 0;
-
-       for (data += 9, len -= 9; *data == ' ' || *data == ';'; data++, len--);
-
-       if (len < 8 || strncasecmp(data, "name=\"", 6))
-               return 0;
-
-       for (data += 6, len -= 6, i = 0; i <= len; i++)
-       {
-               if (*(data + i) != '"')
-                       continue;
-
-               for (j = 1; j < sizeof(parts) / sizeof(parts[0]); j++)
-                       if (!strncmp(data, parts[j], i))
-                               st.parttype = j;
-
-               break;
-       }
-
-       return 0;
-}
-
-static int
-data_begin_cb(multipart_parser *p)
-{
-       if (st.parttype == PART_FILEDATA)
-       {
-               if (!st.sessionid)
-                       return response(false, "File data without session");
-
-               if (!st.filename)
-                       return response(false, "File data without name");
-
-               if (!session_access(st.sessionid, "file", st.filename, "write"))
-                       return response(false, "Access to path denied by ACL");
-
-               st.tempfd = open("/tmp", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
-
-               if (st.tempfd < 0)
-                       return response(false, "Failed to create temporary file");
-       }
-
-       return 0;
-}
-
-static int
-data_cb(multipart_parser *p, const char *data, size_t len)
-{
-       int wlen = len;
-
-       switch (st.parttype)
-       {
-       case PART_SESSIONID:
-               st.sessionid = datadup(data, len);
-               break;
-
-       case PART_FILENAME:
-               st.filename = canonicalize_path(data, len);
-               break;
-
-       case PART_FILEMODE:
-               st.filemode = strtoul(data, NULL, 8);
-               break;
-
-       case PART_FILEDATA:
-               if (write(st.tempfd, data, len) != wlen)
-               {
-                       close(st.tempfd);
-                       return response(false, "I/O failure while writing temporary file");
-               }
-
-               if (!st.filedata)
-                       st.filedata = !!wlen;
-
-               break;
-
-       default:
-               break;
-       }
-
-       return 0;
-}
-
-static int
-data_end_cb(multipart_parser *p)
-{
-       if (st.parttype == PART_SESSIONID)
-       {
-               if (!session_access(st.sessionid, "cgi-io", "upload", "write"))
-               {
-                       errno = EPERM;
-                       return response(false, "Upload permission denied");
-               }
-       }
-       else if (st.parttype == PART_FILEDATA)
-       {
-               if (st.tempfd < 0)
-                       return response(false, "Internal program failure");
-
-#if 0
-               /* prepare directory */
-               for (ptr = st.filename; *ptr; ptr++)
-               {
-                       if (*ptr == '/')
-                       {
-                               *ptr = 0;
-
-                               if (mkdir(st.filename, 0755))
-                               {
-                                       unlink(st.tmpname);
-                                       return response(false, "Failed to create destination directory");
-                               }
-
-                               *ptr = '/';
-                       }
-               }
-#endif
-
-               if (filecopy())
-                       return -1;
-
-               return response(true, NULL);
-       }
-
-       st.parttype = PART_UNKNOWN;
-       return 0;
-}
-
-static multipart_parser *
-init_parser(void)
-{
-       char *boundary;
-       const char *var;
-
-       multipart_parser *p;
-       static multipart_parser_settings s = {
-               .on_part_data        = data_cb,
-               .on_headers_complete = data_begin_cb,
-               .on_part_data_end    = data_end_cb,
-               .on_header_field     = header_field,
-               .on_header_value     = header_value
-       };
-
-       var = getenv("CONTENT_TYPE");
-
-       if (!var || strncmp(var, "multipart/form-data;", 20))
-               return NULL;
-
-       for (var += 20; *var && *var != '='; var++);
-
-       if (*var++ != '=')
-               return NULL;
-
-       boundary = malloc(strlen(var) + 3);
-
-       if (!boundary)
-               return NULL;
-
-       strcpy(boundary, "--");
-       strcpy(boundary + 2, var);
-
-       st.tempfd = -1;
-       st.filefd = -1;
-       st.filemode = 0600;
-
-       p = multipart_parser_init(boundary, &s);
-
-       free(boundary);
-
-       return p;
-}
-
-static int
-main_upload(int argc, char *argv[])
-{
-       int rem, len;
-       bool done = false;
-       char buf[READ_BLOCK];
-       multipart_parser *p;
-
-       p = init_parser();
-
-       if (!p)
-       {
-               errno = EINVAL;
-               return response(false, "Invalid request");
-       }
-
-       while ((len = read(0, buf, sizeof(buf))) > 0)
-       {
-               if (!done) {
-                       rem = multipart_parser_execute(p, buf, len);
-                       done = (rem < len);
-               }
-       }
-
-       multipart_parser_free(p);
-
-       return 0;
-}
-
-static void
-free_charp(char **ptr)
-{
-       free(*ptr);
-}
-
-#define autochar __attribute__((__cleanup__(free_charp))) char
-
-static int
-main_download(int argc, char **argv)
-{
-       char *fields[] = { "sessionid", NULL, "path", NULL, "filename", NULL, "mimetype", NULL };
-       unsigned long long size = 0;
-       char *p, buf[READ_BLOCK];
-       ssize_t len = 0;
-       struct stat s;
-       int rfd;
-
-       autochar *post = postdecode(fields, 4);
-
-       if (!fields[1] || !session_access(fields[1], "cgi-io", "download", "read"))
-               return failure(403, 0, "Download permission denied");
-
-       if (!fields[3] || !session_access(fields[1], "file", fields[3], "read"))
-               return failure(403, 0, "Access to path denied by ACL");
-
-       if (stat(fields[3], &s))
-               return failure(404, errno, "Failed to stat requested path");
-
-       if (!S_ISREG(s.st_mode) && !S_ISBLK(s.st_mode))
-               return failure(403, 0, "Requested path is not a regular file or block device");
-
-       for (p = fields[5]; p && *p; p++)
-               if (!isalnum(*p) && !strchr(" ()<>@,;:[]?.=%-", *p))
-                       return failure(400, 0, "Invalid characters in filename");
-
-       for (p = fields[7]; p && *p; p++)
-               if (!isalnum(*p) && !strchr(" .;=/-", *p))
-                       return failure(400, 0, "Invalid characters in mimetype");
-
-       rfd = open(fields[3], O_RDONLY);
-
-       if (rfd < 0)
-               return failure(500, errno, "Failed to open requested path");
-
-       if (S_ISBLK(s.st_mode))
-               ioctl(rfd, BLKGETSIZE64, &size);
-       else
-               size = (unsigned long long)s.st_size;
-
-       printf("Status: 200 OK\r\n");
-       printf("Content-Type: %s\r\n", fields[7] ? fields[7] : "application/octet-stream");
-
-       if (fields[5])
-               printf("Content-Disposition: attachment; filename=\"%s\"\r\n", fields[5]);
-
-       if (size > 0) {
-               printf("Content-Length: %llu\r\n\r\n", size);
-               fflush(stdout);
-
-               while (size > 0) {
-                       len = sendfile(1, rfd, NULL, size);
-
-                       if (len == -1) {
-                               if (errno == ENOSYS || errno == EINVAL) {
-                                       while ((len = read(rfd, buf, sizeof(buf))) > 0)
-                                               fwrite(buf, len, 1, stdout);
-
-                                       fflush(stdout);
-                                       break;
-                               }
-
-                               if (errno == EINTR || errno == EAGAIN)
-                                       continue;
-                       }
-
-                       if (len <= 0)
-                               break;
-
-                       size -= len;
-               }
-       }
-       else {
-               printf("\r\n");
-
-               while ((len = read(rfd, buf, sizeof(buf))) > 0)
-                       fwrite(buf, len, 1, stdout);
-
-               fflush(stdout);
-       }
-
-       close(rfd);
-
-       return 0;
-}
-
-static int
-main_backup(int argc, char **argv)
-{
-       pid_t pid;
-       time_t now;
-       int r;
-       int len;
-       int status;
-       int fds[2];
-       char datestr[16] = { 0 };
-       char hostname[64] = { 0 };
-       char *fields[] = { "sessionid", NULL };
-
-       autochar *post = postdecode(fields, 1);
-
-       if (!fields[1] || !session_access(fields[1], "cgi-io", "backup", "read"))
-               return failure(403, 0, "Backup permission denied");
-
-       if (pipe(fds))
-               return failure(500, errno, "Failed to spawn pipe");
-
-       switch ((pid = fork()))
-       {
-       case -1:
-               return failure(500, errno, "Failed to fork process");
-
-       case 0:
-               dup2(fds[1], 1);
-
-               close(0);
-               close(2);
-               close(fds[0]);
-               close(fds[1]);
-
-               r = chdir("/");
-               if (r < 0)
-                       return failure(500, errno, "Failed chdir('/')");
-
-               execl("/sbin/sysupgrade", "/sbin/sysupgrade",
-                     "--create-backup", "-", NULL);
-
-               return -1;
-
-       default:
-               close(fds[1]);
-
-               now = time(NULL);
-               strftime(datestr, sizeof(datestr) - 1, "%Y-%m-%d", localtime(&now));
-
-               if (gethostname(hostname, sizeof(hostname) - 1))
-                       sprintf(hostname, "OpenWrt");
-
-               printf("Status: 200 OK\r\n");
-               printf("Content-Type: application/x-targz\r\n");
-               printf("Content-Disposition: attachment; "
-                      "filename=\"backup-%s-%s.tar.gz\"\r\n\r\n", hostname, datestr);
-
-               fflush(stdout);
-
-               do {
-                       len = splice(fds[0], NULL, 1, NULL, READ_BLOCK, SPLICE_F_MORE);
-               } while (len > 0);
-
-               waitpid(pid, &status, 0);
-
-               close(fds[0]);
-
-               return 0;
-       }
-}
-
-
-static const char *
-lookup_executable(const char *cmd)
-{
-       size_t plen = 0, clen = strlen(cmd) + 1;
-       static char path[PATH_MAX];
-       char *search, *p;
-       struct stat s;
-
-       if (!stat(cmd, &s) && S_ISREG(s.st_mode))
-               return cmd;
-
-       search = getenv("PATH");
-
-       if (!search)
-               search = "/bin:/usr/bin:/sbin:/usr/sbin";
-
-       p = search;
-
-       do {
-               if (*p != ':' && *p != '\0')
-                       continue;
-
-               plen = p - search;
-
-               if ((plen + clen) >= sizeof(path))
-                       continue;
-
-               strncpy(path, search, plen);
-               sprintf(path + plen, "/%s", cmd);
-
-               if (!stat(path, &s) && S_ISREG(s.st_mode))
-                       return path;
-
-               search = p + 1;
-       } while (*p++);
-
-       return NULL;
-}
-
-static char **
-parse_command(const char *cmdline)
-{
-       const char *p = cmdline, *s;
-       char **argv = NULL, *out;
-       size_t arglen = 0;
-       int argnum = 0;
-       bool esc;
-
-       while (isspace(*cmdline))
-               cmdline++;
-
-       for (p = cmdline, s = p, esc = false; p; p++) {
-               if (esc) {
-                       esc = false;
-               }
-               else if (*p == '\\' && p[1] != 0) {
-                       esc = true;
-               }
-               else if (isspace(*p) || *p == 0) {
-                       if (p > s) {
-                               argnum += 1;
-                               arglen += sizeof(char *) + (p - s) + 1;
-                       }
-
-                       s = p + 1;
-               }
-
-               if (*p == 0)
-                       break;
-       }
-
-       if (arglen == 0)
-               return NULL;
-
-       argv = calloc(1, arglen + sizeof(char *));
-
-       if (!argv)
-               return NULL;
-
-       out = (char *)argv + sizeof(char *) * (argnum + 1);
-       argv[0] = out;
-
-       for (p = cmdline, s = p, esc = false, argnum = 0; p; p++) {
-               if (esc) {
-                       esc = false;
-                       *out++ = *p;
-               }
-               else if (*p == '\\' && p[1] != 0) {
-                       esc = true;
-               }
-               else if (isspace(*p) || *p == 0) {
-                       if (p > s) {
-                               *out++ = ' ';
-                               argv[++argnum] = out;
-                       }
-
-                       s = p + 1;
-               }
-               else {
-                       *out++ = *p;
-               }
-
-               if (*p == 0)
-                       break;
-       }
-
-       argv[argnum] = NULL;
-       out[-1] = 0;
-
-       return argv;
-}
-
-static int
-main_exec(int argc, char **argv)
-{
-       char *fields[] = { "sessionid", NULL, "command", NULL, "filename", NULL, "mimetype", NULL };
-       int i, devnull, status, fds[2];
-       bool allowed = false;
-       ssize_t len = 0;
-       const char *exe;
-       char *p, **args;
-       pid_t pid;
-
-       autochar *post = postdecode(fields, 4);
-
-       if (!fields[1] || !session_access(fields[1], "cgi-io", "exec", "read"))
-               return failure(403, 0, "Exec permission denied");
-
-       for (p = fields[5]; p && *p; p++)
-               if (!isalnum(*p) && !strchr(" ()<>@,;:[]?.=%-", *p))
-                       return failure(400, 0, "Invalid characters in filename");
-
-       for (p = fields[7]; p && *p; p++)
-               if (!isalnum(*p) && !strchr(" .;=/-", *p))
-                       return failure(400, 0, "Invalid characters in mimetype");
-
-       args = fields[3] ? parse_command(fields[3]) : NULL;
-
-       if (!args)
-               return failure(400, 0, "Invalid command parameter");
-
-       /* First check if we find an ACL match for the whole cmdline ... */
-       allowed = session_access(fields[1], "file", args[0], "exec");
-
-       /* Now split the command vector... */
-       for (i = 1; args[i]; i++)
-               args[i][-1] = 0;
-
-       /* Find executable... */
-       exe = lookup_executable(args[0]);
-
-       if (!exe) {
-               free(args);
-               return failure(404, 0, "Executable not found");
-       }
-
-       /* If there was no ACL match, check for a match on the executable */
-       if (!allowed && !session_access(fields[1], "file", exe, "exec")) {
-               free(args);
-               return failure(403, 0, "Access to command denied by ACL");
-       }
-
-       if (pipe(fds)) {
-               free(args);
-               return failure(500, errno, "Failed to spawn pipe");
-       }
-
-       switch ((pid = fork()))
-       {
-       case -1:
-               free(args);
-               close(fds[0]);
-               close(fds[1]);
-               return failure(500, errno, "Failed to fork process");
-
-       case 0:
-               devnull = open("/dev/null", O_RDWR);
-
-               if (devnull > -1) {
-                       dup2(devnull, 0);
-                       dup2(devnull, 2);
-                       close(devnull);
-               }
-               else {
-                       close(0);
-                       close(2);
-               }
-
-               dup2(fds[1], 1);
-               close(fds[0]);
-               close(fds[1]);
-
-               if (chdir("/") < 0) {
-                       free(args);
-                       return failure(500, errno, "Failed chdir('/')");
-               }
-
-               if (execv(exe, args) < 0) {
-                       free(args);
-                       return failure(500, errno, "Failed execv(...)");
-               }
-
-               return -1;
-
-       default:
-               close(fds[1]);
-
-               printf("Status: 200 OK\r\n");
-               printf("Content-Type: %s\r\n",
-                      fields[7] ? fields[7] : "application/octet-stream");
-
-               if (fields[5])
-                       printf("Content-Disposition: attachment; filename=\"%s\"\r\n",
-                              fields[5]);
-
-               printf("\r\n");
-               fflush(stdout);
-
-               do {
-                       len = splice(fds[0], NULL, 1, NULL, READ_BLOCK, SPLICE_F_MORE);
-               } while (len > 0);
-
-               waitpid(pid, &status, 0);
-
-               close(fds[0]);
-               free(args);
-
-               return 0;
-       }
-}
-
-int main(int argc, char **argv)
-{
-       if (strstr(argv[0], "cgi-upload"))
-               return main_upload(argc, argv);
-       else if (strstr(argv[0], "cgi-download"))
-               return main_download(argc, argv);
-       else if (strstr(argv[0], "cgi-backup"))
-               return main_backup(argc, argv);
-       else if (strstr(argv[0], "cgi-exec"))
-               return main_exec(argc, argv);
-
-       return -1;
-}
diff --git a/src/multipart_parser.c b/src/multipart_parser.c
deleted file mode 100644 (file)
index ee82c82..0000000
+++ /dev/null
@@ -1,309 +0,0 @@
-/* Based on node-formidable by Felix Geisendörfer
- * Igor Afonov - afonov@gmail.com - 2012
- * MIT License - http://www.opensource.org/licenses/mit-license.php
- */
-
-#include "multipart_parser.h"
-
-#include <stdio.h>
-#include <stdarg.h>
-#include <string.h>
-
-static void multipart_log(const char * format, ...)
-{
-#ifdef DEBUG_MULTIPART
-    va_list args;
-    va_start(args, format);
-
-    fprintf(stderr, "[HTTP_MULTIPART_PARSER] %s:%d: ", __FILE__, __LINE__);
-    vfprintf(stderr, format, args);
-    fprintf(stderr, "\n");
-#endif
-}
-
-#define NOTIFY_CB(FOR)                                                 \
-do {                                                                   \
-  if (p->settings->on_##FOR) {                                         \
-    if (p->settings->on_##FOR(p) != 0) {                               \
-      return i;                                                        \
-    }                                                                  \
-  }                                                                    \
-} while (0)
-
-#define EMIT_DATA_CB(FOR, ptr, len)                                    \
-do {                                                                   \
-  if (p->settings->on_##FOR) {                                         \
-    if (p->settings->on_##FOR(p, ptr, len) != 0) {                     \
-      return i;                                                        \
-    }                                                                  \
-  }                                                                    \
-} while (0)
-
-
-#define LF 10
-#define CR 13
-
-struct multipart_parser {
-  void * data;
-
-  size_t index;
-  size_t boundary_length;
-
-  unsigned char state;
-
-  const multipart_parser_settings* settings;
-
-  char* lookbehind;
-  char multipart_boundary[1];
-};
-
-enum state {
-  s_uninitialized = 1,
-  s_start,
-  s_start_boundary,
-  s_header_field_start,
-  s_header_field,
-  s_headers_almost_done,
-  s_header_value_start,
-  s_header_value,
-  s_header_value_almost_done,
-  s_part_data_start,
-  s_part_data,
-  s_part_data_almost_boundary,
-  s_part_data_boundary,
-  s_part_data_almost_end,
-  s_part_data_end,
-  s_part_data_final_hyphen,
-  s_end
-};
-
-multipart_parser* multipart_parser_init
-    (const char *boundary, const multipart_parser_settings* settings) {
-
-  multipart_parser* p = malloc(sizeof(multipart_parser) +
-                               strlen(boundary) +
-                               strlen(boundary) + 9);
-
-  strcpy(p->multipart_boundary, boundary);
-  p->boundary_length = strlen(boundary);
-
-  p->lookbehind = (p->multipart_boundary + p->boundary_length + 1);
-
-  p->index = 0;
-  p->state = s_start;
-  p->settings = settings;
-
-  return p;
-}
-
-void multipart_parser_free(multipart_parser* p) {
-  free(p);
-}
-
-void multipart_parser_set_data(multipart_parser *p, void *data) {
-    p->data = data;
-}
-
-void *multipart_parser_get_data(multipart_parser *p) {
-    return p->data;
-}
-
-size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len) {
-  size_t i = 0;
-  size_t mark = 0;
-  char c, cl;
-  int is_last = 0;
-
-  while(i < len) {
-    c = buf[i];
-    is_last = (i == (len - 1));
-    switch (p->state) {
-      case s_start:
-        multipart_log("s_start");
-        p->index = 0;
-        p->state = s_start_boundary;
-
-      /* fallthrough */
-      case s_start_boundary:
-        multipart_log("s_start_boundary");
-        if (p->index == p->boundary_length) {
-          if (c != CR) {
-            return i;
-          }
-          p->index++;
-          break;
-        } else if (p->index == (p->boundary_length + 1)) {
-          if (c != LF) {
-            return i;
-          }
-          p->index = 0;
-          NOTIFY_CB(part_data_begin);
-          p->state = s_header_field_start;
-          break;
-        }
-        if (c != p->multipart_boundary[p->index]) {
-          return i;
-        }
-        p->index++;
-        break;
-
-      case s_header_field_start:
-        multipart_log("s_header_field_start");
-        mark = i;
-        p->state = s_header_field;
-
-      /* fallthrough */
-      case s_header_field:
-        multipart_log("s_header_field");
-        if (c == CR) {
-          p->state = s_headers_almost_done;
-          break;
-        }
-
-        if (c == '-') {
-          break;
-        }
-
-        if (c == ':') {
-          EMIT_DATA_CB(header_field, buf + mark, i - mark);
-          p->state = s_header_value_start;
-          break;
-        }
-
-        cl = tolower(c);
-        if (cl < 'a' || cl > 'z') {
-          multipart_log("invalid character in header name");
-          return i;
-        }
-        if (is_last)
-            EMIT_DATA_CB(header_field, buf + mark, (i - mark) + 1);
-        break;
-
-      case s_headers_almost_done:
-        multipart_log("s_headers_almost_done");
-        if (c != LF) {
-          return i;
-        }
-
-        p->state = s_part_data_start;
-        break;
-
-      case s_header_value_start:
-        multipart_log("s_header_value_start");
-        if (c == ' ') {
-          break;
-        }
-
-        mark = i;
-        p->state = s_header_value;
-
-      /* fallthrough */
-      case s_header_value:
-        multipart_log("s_header_value");
-        if (c == CR) {
-          EMIT_DATA_CB(header_value, buf + mark, i - mark);
-          p->state = s_header_value_almost_done;
-        }
-        if (is_last)
-            EMIT_DATA_CB(header_value, buf + mark, (i - mark) + 1);
-        break;
-
-      case s_header_value_almost_done:
-        multipart_log("s_header_value_almost_done");
-        if (c != LF) {
-          return i;
-        }
-        p->state = s_header_field_start;
-        break;
-
-      case s_part_data_start:
-        multipart_log("s_part_data_start");
-        NOTIFY_CB(headers_complete);
-        mark = i;
-        p->state = s_part_data;
-
-      /* fallthrough */
-      case s_part_data:
-        multipart_log("s_part_data");
-        if (c == CR) {
-            EMIT_DATA_CB(part_data, buf + mark, i - mark);
-            mark = i;
-            p->state = s_part_data_almost_boundary;
-            p->lookbehind[0] = CR;
-            break;
-        }
-        if (is_last)
-            EMIT_DATA_CB(part_data, buf + mark, (i - mark) + 1);
-        break;
-
-      case s_part_data_almost_boundary:
-        multipart_log("s_part_data_almost_boundary");
-        if (c == LF) {
-            p->state = s_part_data_boundary;
-            p->lookbehind[1] = LF;
-            p->index = 0;
-            break;
-        }
-        EMIT_DATA_CB(part_data, p->lookbehind, 1);
-        p->state = s_part_data;
-        mark = i --;
-        break;
-
-      case s_part_data_boundary:
-        multipart_log("s_part_data_boundary");
-        if (p->multipart_boundary[p->index] != c) {
-          EMIT_DATA_CB(part_data, p->lookbehind, 2 + p->index);
-          p->state = s_part_data;
-          mark = i --;
-          break;
-        }
-        p->lookbehind[2 + p->index] = c;
-        if ((++ p->index) == p->boundary_length) {
-            NOTIFY_CB(part_data_end);
-            p->state = s_part_data_almost_end;
-        }
-        break;
-
-      case s_part_data_almost_end:
-        multipart_log("s_part_data_almost_end");
-        if (c == '-') {
-            p->state = s_part_data_final_hyphen;
-            break;
-        }
-        if (c == CR) {
-            p->state = s_part_data_end;
-            break;
-        }
-        return i;
-
-      case s_part_data_final_hyphen:
-        multipart_log("s_part_data_final_hyphen");
-        if (c == '-') {
-            NOTIFY_CB(body_end);
-            p->state = s_end;
-            break;
-        }
-        return i;
-
-      case s_part_data_end:
-        multipart_log("s_part_data_end");
-        if (c == LF) {
-            p->state = s_header_field_start;
-            NOTIFY_CB(part_data_begin);
-            break;
-        }
-        return i;
-
-      case s_end:
-        multipart_log("s_end: %02X", (int) c);
-        break;
-
-      default:
-        multipart_log("Multipart parser unrecoverable error");
-        return 0;
-    }
-    ++ i;
-  }
-
-  return len;
-}
diff --git a/src/multipart_parser.h b/src/multipart_parser.h
deleted file mode 100644 (file)
index 87e67f4..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-/* Based on node-formidable by Felix Geisendörfer
- * Igor Afonov - afonov@gmail.com - 2012
- * MIT License - http://www.opensource.org/licenses/mit-license.php
- */
-#ifndef _multipart_parser_h
-#define _multipart_parser_h
-
-#ifdef __cplusplus
-extern "C"
-{
-#endif
-
-#include <stdlib.h>
-#include <ctype.h>
-
-typedef struct multipart_parser multipart_parser;
-typedef struct multipart_parser_settings multipart_parser_settings;
-typedef struct multipart_parser_state multipart_parser_state;
-
-typedef int (*multipart_data_cb) (multipart_parser*, const char *at, size_t length);
-typedef int (*multipart_notify_cb) (multipart_parser*);
-
-struct multipart_parser_settings {
-  multipart_data_cb on_header_field;
-  multipart_data_cb on_header_value;
-  multipart_data_cb on_part_data;
-
-  multipart_notify_cb on_part_data_begin;
-  multipart_notify_cb on_headers_complete;
-  multipart_notify_cb on_part_data_end;
-  multipart_notify_cb on_body_end;
-};
-
-multipart_parser* multipart_parser_init
-    (const char *boundary, const multipart_parser_settings* settings);
-
-void multipart_parser_free(multipart_parser* p);
-
-size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len);
-
-void multipart_parser_set_data(multipart_parser* p, void* data);
-void * multipart_parser_get_data(multipart_parser* p);
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
-#endif