Browse Source

cgi-io: add a small helper cgi that can be used by RPCD based UIs

Signed-off-by: John Crispin <blogic@openwrt.org>
John Crispin 9 years ago
parent
commit
fe31939c0e

+ 44
- 0
net/cgi-io/Makefile View File

@@ -0,0 +1,44 @@
1
+#
2
+# Copyright (C) 2015 OpenWrt.org
3
+#
4
+# This is free software, licensed under the GNU General Public License v2.
5
+# See /LICENSE for more information.
6
+#
7
+
8
+include $(TOPDIR)/rules.mk
9
+
10
+PKG_NAME:=cgi-io
11
+PKG_RELEASE:=1
12
+
13
+PKG_LICENSE:=GPL-2.0+
14
+
15
+PKG_MAINTAINER:=John Crispin <blogic@openwrt.org>
16
+
17
+include $(INCLUDE_DIR)/package.mk
18
+include $(INCLUDE_DIR)/cmake.mk
19
+
20
+define Package/cgi-io
21
+  SECTION:=net
22
+  CATEGORY:=Network
23
+  SUBMENU:=Web Servers/Proxies
24
+  DEPENDS:=+libubox +libubus
25
+  TITLE:=CGI utility for handling up/downloading of files
26
+endef
27
+
28
+define Package/cgi-io/description
29
+  This package contains an cgi utility that is useful for up/downloading files
30
+endef
31
+
32
+define Build/Prepare
33
+	mkdir -p $(PKG_BUILD_DIR)
34
+	$(CP) ./src/* $(PKG_BUILD_DIR)/
35
+endef
36
+
37
+define Package/cgi-io/install
38
+	$(INSTALL_DIR) $(1)/usr/libexec $(1)/www/cgi-bin/
39
+	$(INSTALL_BIN) $(PKG_BUILD_DIR)/cgi-io $(1)/usr/libexec
40
+	$(LN) ../../usr/libexec/cgi-io $(1)/www/cgi-bin/cgi-upload 
41
+	$(LN) ../../usr/libexec/cgi-io $(1)/www/cgi-bin/cgi-download 
42
+endef
43
+
44
+$(eval $(call BuildPackage,cgi-io))

+ 19
- 0
net/cgi-io/src/CMakeLists.txt View File

@@ -0,0 +1,19 @@
1
+cmake_minimum_required(VERSION 2.6)
2
+
3
+PROJECT(cgi-io C)
4
+
5
+INCLUDE(CheckFunctionExists)
6
+
7
+ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3 -Wmissing-declarations)
8
+
9
+SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
10
+
11
+IF(APPLE)
12
+  INCLUDE_DIRECTORIES(/opt/local/include)
13
+  LINK_DIRECTORIES(/opt/local/lib)
14
+ENDIF()
15
+
16
+ADD_EXECUTABLE(cgi-io main.c multipart_parser.c)
17
+TARGET_LINK_LIBRARIES(cgi-io ubox ubus)
18
+
19
+INSTALL(TARGETS cgi-io RUNTIME DESTINATION sbin)

+ 644
- 0
net/cgi-io/src/main.c View File

@@ -0,0 +1,644 @@
1
+/*
2
+ * cgi-io - LuCI non-RPC helper
3
+ *
4
+ *   Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
5
+ *
6
+ * Permission to use, copy, modify, and/or distribute this software for any
7
+ * purpose with or without fee is hereby granted, provided that the above
8
+ * copyright notice and this permission notice appear in all copies.
9
+ *
10
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
+ */
18
+
19
+#include <stdio.h>
20
+#include <stdlib.h>
21
+#include <stdbool.h>
22
+#include <unistd.h>
23
+#include <string.h>
24
+#include <errno.h>
25
+#include <fcntl.h>
26
+#include <ctype.h>
27
+#include <sys/stat.h>
28
+#include <sys/wait.h>
29
+
30
+#include <libubus.h>
31
+#include <libubox/blobmsg.h>
32
+
33
+#include "multipart_parser.h"
34
+
35
+
36
+enum part {
37
+	PART_UNKNOWN,
38
+	PART_SESSIONID,
39
+	PART_FILENAME,
40
+	PART_FILEMODE,
41
+	PART_FILEDATA
42
+};
43
+
44
+const char *parts[] = {
45
+	"(bug)",
46
+	"sessionid",
47
+	"filename",
48
+	"filemode",
49
+	"filedata",
50
+};
51
+
52
+struct state
53
+{
54
+	bool is_content_disposition;
55
+	enum part parttype;
56
+	char *sessionid;
57
+	char *filename;
58
+	bool filedata;
59
+	int filemode;
60
+	int filefd;
61
+	int tempfd;
62
+};
63
+
64
+enum {
65
+	SES_ACCESS,
66
+	__SES_MAX,
67
+};
68
+
69
+static const struct blobmsg_policy ses_policy[__SES_MAX] = {
70
+	[SES_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_BOOL },
71
+};
72
+
73
+
74
+static struct state st;
75
+
76
+static void
77
+session_access_cb(struct ubus_request *req, int type, struct blob_attr *msg)
78
+{
79
+	struct blob_attr *tb[__SES_MAX];
80
+	bool *allow = (bool *)req->priv;
81
+
82
+	if (!msg)
83
+		return;
84
+
85
+	blobmsg_parse(ses_policy, __SES_MAX, tb, blob_data(msg), blob_len(msg));
86
+
87
+	if (tb[SES_ACCESS])
88
+		*allow = blobmsg_get_bool(tb[SES_ACCESS]);
89
+}
90
+
91
+static bool
92
+session_access(const char *sid, const char *obj, const char *func)
93
+{
94
+	uint32_t id;
95
+	bool allow = false;
96
+	struct ubus_context *ctx;
97
+	static struct blob_buf req;
98
+
99
+	ctx = ubus_connect(NULL);
100
+
101
+	if (!ctx || ubus_lookup_id(ctx, "session", &id))
102
+		goto out;
103
+
104
+	blob_buf_init(&req, 0);
105
+	blobmsg_add_string(&req, "ubus_rpc_session", sid);
106
+	blobmsg_add_string(&req, "scope", "cgi-io");
107
+	blobmsg_add_string(&req, "object", obj);
108
+	blobmsg_add_string(&req, "function", func);
109
+
110
+	ubus_invoke(ctx, id, "access", req.head, session_access_cb, &allow, 500);
111
+
112
+out:
113
+	if (ctx)
114
+		ubus_free(ctx);
115
+
116
+	return allow;
117
+}
118
+
119
+static char *
120
+md5sum(const char *file)
121
+{
122
+	pid_t pid;
123
+	int fds[2];
124
+	static char md5[33];
125
+
126
+	if (pipe(fds))
127
+		return NULL;
128
+
129
+	switch ((pid = fork()))
130
+	{
131
+	case -1:
132
+		return NULL;
133
+
134
+	case 0:
135
+		uloop_done();
136
+
137
+		dup2(fds[1], 1);
138
+
139
+		close(0);
140
+		close(2);
141
+		close(fds[0]);
142
+		close(fds[1]);
143
+
144
+		if (execl("/bin/busybox", "/bin/busybox", "md5sum", file, NULL));
145
+			return NULL;
146
+
147
+		break;
148
+
149
+	default:
150
+		memset(md5, 0, sizeof(md5));
151
+		read(fds[0], md5, 32);
152
+		waitpid(pid, NULL, 0);
153
+		close(fds[0]);
154
+		close(fds[1]);
155
+	}
156
+
157
+	return md5;
158
+}
159
+
160
+static char *
161
+datadup(const void *in, size_t len)
162
+{
163
+	char *out = malloc(len + 1);
164
+
165
+	if (!out)
166
+		return NULL;
167
+
168
+	memcpy(out, in, len);
169
+
170
+	*(out + len) = 0;
171
+
172
+	return out;
173
+}
174
+
175
+static bool
176
+urldecode(char *buf)
177
+{
178
+	char *c, *p;
179
+
180
+	if (!buf || !*buf)
181
+		return true;
182
+
183
+#define hex(x) \
184
+	(((x) <= '9') ? ((x) - '0') : \
185
+		(((x) <= 'F') ? ((x) - 'A' + 10) : \
186
+			((x) - 'a' + 10)))
187
+
188
+	for (c = p = buf; *p; c++)
189
+	{
190
+		if (*p == '%')
191
+		{
192
+			if (!isxdigit(*(p + 1)) || !isxdigit(*(p + 2)))
193
+				return false;
194
+
195
+			*c = (char)(16 * hex(*(p + 1)) + hex(*(p + 2)));
196
+
197
+			p += 3;
198
+		}
199
+		else if (*p == '+')
200
+		{
201
+			*c = ' ';
202
+			p++;
203
+		}
204
+		else
205
+		{
206
+			*c = *p++;
207
+		}
208
+	}
209
+
210
+	*c = 0;
211
+
212
+	return true;
213
+}
214
+
215
+static bool
216
+postdecode(char **fields, int n_fields)
217
+{
218
+	char *p;
219
+	const char *var;
220
+	static char buf[1024];
221
+	int i, len, field, found = 0;
222
+
223
+	var = getenv("CONTENT_TYPE");
224
+
225
+	if (!var || strncmp(var, "application/x-www-form-urlencoded", 33))
226
+		return false;
227
+
228
+	memset(buf, 0, sizeof(buf));
229
+
230
+	if ((len = read(0, buf, sizeof(buf) - 1)) > 0)
231
+	{
232
+		for (p = buf, i = 0; i <= len; i++)
233
+		{
234
+			if (buf[i] == '=')
235
+			{
236
+				buf[i] = 0;
237
+
238
+				for (field = 0; field < (n_fields * 2); field += 2)
239
+				{
240
+					if (!strcmp(p, fields[field]))
241
+					{
242
+						fields[field + 1] = buf + i + 1;
243
+						found++;
244
+					}
245
+				}
246
+			}
247
+			else if (buf[i] == '&' || buf[i] == '\0')
248
+			{
249
+				buf[i] = 0;
250
+
251
+				if (found >= n_fields)
252
+					break;
253
+
254
+				p = buf + i + 1;
255
+			}
256
+		}
257
+	}
258
+
259
+	for (field = 0; field < (n_fields * 2); field += 2)
260
+		if (!urldecode(fields[field + 1]))
261
+			return false;
262
+
263
+	return (found >= n_fields);
264
+}
265
+
266
+static int
267
+response(bool success, const char *message)
268
+{
269
+	char *md5;
270
+	struct stat s;
271
+
272
+	printf("Status: 200 OK\r\n");
273
+	printf("Content-Type: text/plain\r\n\r\n{\n");
274
+
275
+	if (success)
276
+	{
277
+		if (!stat(st.filename, &s) && (md5 = md5sum(st.filename)) != NULL)
278
+			printf("\t\"size\": %u,\n\t\"checksum\": \"%s\"\n",
279
+				   (unsigned int)s.st_size, md5);
280
+	}
281
+	else
282
+	{
283
+		if (message)
284
+			printf("\t\"message\": \"%s\",\n", message);
285
+
286
+		printf("\t\"failure\": [ %u, \"%s\" ]\n", errno, strerror(errno));
287
+
288
+		if (st.filefd > -1)
289
+			unlink(st.filename);
290
+	}
291
+
292
+	printf("}\n");
293
+
294
+	return -1;
295
+}
296
+
297
+static int
298
+failure(int e, const char *message)
299
+{
300
+	printf("Status: 500 Internal Server failure\r\n");
301
+	printf("Content-Type: text/plain\r\n\r\n");
302
+	printf("%s", message);
303
+
304
+	if (e)
305
+		printf(": %s", strerror(e));
306
+
307
+	return -1;
308
+}
309
+
310
+static int
311
+filecopy(void)
312
+{
313
+	int len;
314
+	char buf[4096];
315
+
316
+	if (!st.filedata)
317
+	{
318
+		close(st.tempfd);
319
+		errno = EINVAL;
320
+		return response(false, "No file data received");
321
+	}
322
+
323
+	if (lseek(st.tempfd, 0, SEEK_SET) < 0)
324
+	{
325
+		close(st.tempfd);
326
+		return response(false, "Failed to rewind temp file");
327
+	}
328
+
329
+	st.filefd = open(st.filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
330
+
331
+	if (st.filefd < 0)
332
+	{
333
+		close(st.tempfd);
334
+		return response(false, "Failed to open target file");
335
+	}
336
+
337
+	while ((len = read(st.tempfd, buf, sizeof(buf))) > 0)
338
+	{
339
+		if (write(st.filefd, buf, len) != len)
340
+		{
341
+			close(st.tempfd);
342
+			close(st.filefd);
343
+			return response(false, "I/O failure while writing target file");
344
+		}
345
+	}
346
+
347
+	close(st.tempfd);
348
+	close(st.filefd);
349
+
350
+	if (chmod(st.filename, st.filemode))
351
+		return response(false, "Failed to chmod target file");
352
+
353
+	return 0;
354
+}
355
+
356
+static int
357
+header_field(multipart_parser *p, const char *data, size_t len)
358
+{
359
+	st.is_content_disposition = !strncasecmp(data, "Content-Disposition", len);
360
+	return 0;
361
+}
362
+
363
+static int
364
+header_value(multipart_parser *p, const char *data, size_t len)
365
+{
366
+	int i, j;
367
+
368
+	if (!st.is_content_disposition)
369
+		return 0;
370
+
371
+	if (len < 10 || strncasecmp(data, "form-data", 9))
372
+		return 0;
373
+
374
+	for (data += 9, len -= 9; *data == ' ' || *data == ';'; data++, len--);
375
+
376
+	if (len < 8 || strncasecmp(data, "name=\"", 6))
377
+		return 0;
378
+
379
+	for (data += 6, len -= 6, i = 0; i <= len; i++)
380
+	{
381
+		if (*(data + i) != '"')
382
+			continue;
383
+
384
+		for (j = 1; j < sizeof(parts) / sizeof(parts[0]); j++)
385
+			if (!strncmp(data, parts[j], i))
386
+				st.parttype = j;
387
+
388
+		break;
389
+	}
390
+
391
+	return 0;
392
+}
393
+
394
+static int
395
+data_begin_cb(multipart_parser *p)
396
+{
397
+	char tmpname[24] = "/tmp/luci-upload.XXXXXX";
398
+
399
+	if (st.parttype == PART_FILEDATA)
400
+	{
401
+		if (!st.sessionid)
402
+			return response(false, "File data without session");
403
+
404
+		if (!st.filename)
405
+			return response(false, "File data without name");
406
+
407
+		st.tempfd = mkstemp(tmpname);
408
+
409
+		if (st.tempfd < 0)
410
+			return response(false, "Failed to create temporary file");
411
+
412
+		unlink(tmpname);
413
+	}
414
+
415
+	return 0;
416
+}
417
+
418
+static int
419
+data_cb(multipart_parser *p, const char *data, size_t len)
420
+{
421
+	switch (st.parttype)
422
+	{
423
+	case PART_SESSIONID:
424
+		st.sessionid = datadup(data, len);
425
+		break;
426
+
427
+	case PART_FILENAME:
428
+		st.filename = datadup(data, len);
429
+		break;
430
+
431
+	case PART_FILEMODE:
432
+		st.filemode = strtoul(data, NULL, 8);
433
+		break;
434
+
435
+	case PART_FILEDATA:
436
+		if (write(st.tempfd, data, len) != len)
437
+		{
438
+			close(st.tempfd);
439
+			return response(false, "I/O failure while writing temporary file");
440
+		}
441
+
442
+		if (!st.filedata)
443
+			st.filedata = !!len;
444
+
445
+		break;
446
+
447
+	default:
448
+		break;
449
+	}
450
+
451
+	return 0;
452
+}
453
+
454
+static int
455
+data_end_cb(multipart_parser *p)
456
+{
457
+	if (st.parttype == PART_SESSIONID)
458
+	{
459
+		if (!session_access(st.sessionid, "upload", "write"))
460
+		{
461
+			errno = EPERM;
462
+			return response(false, "Upload permission denied");
463
+		}
464
+	}
465
+	else if (st.parttype == PART_FILEDATA)
466
+	{
467
+		if (st.tempfd < 0)
468
+			return response(false, "Internal program failure");
469
+
470
+#if 0
471
+		/* prepare directory */
472
+		for (ptr = st.filename; *ptr; ptr++)
473
+		{
474
+			if (*ptr == '/')
475
+			{
476
+				*ptr = 0;
477
+
478
+				if (mkdir(st.filename, 0755))
479
+				{
480
+					unlink(st.tmpname);
481
+					return response(false, "Failed to create destination directory");
482
+				}
483
+
484
+				*ptr = '/';
485
+			}
486
+		}
487
+#endif
488
+
489
+		if (filecopy())
490
+			return -1;
491
+
492
+		return response(true, NULL);
493
+	}
494
+
495
+	st.parttype = PART_UNKNOWN;
496
+	return 0;
497
+}
498
+
499
+static multipart_parser *
500
+init_parser(void)
501
+{
502
+	char *boundary;
503
+	const char *var;
504
+
505
+	multipart_parser *p;
506
+	static multipart_parser_settings s = {
507
+		.on_part_data        = data_cb,
508
+		.on_headers_complete = data_begin_cb,
509
+		.on_part_data_end    = data_end_cb,
510
+		.on_header_field     = header_field,
511
+		.on_header_value     = header_value
512
+	};
513
+
514
+	var = getenv("CONTENT_TYPE");
515
+
516
+	if (!var || strncmp(var, "multipart/form-data;", 20))
517
+		return NULL;
518
+
519
+	for (var += 20; *var && *var != '='; var++);
520
+
521
+	if (*var++ != '=')
522
+		return NULL;
523
+
524
+	boundary = malloc(strlen(var) + 3);
525
+
526
+	if (!boundary)
527
+		return NULL;
528
+
529
+	strcpy(boundary, "--");
530
+	strcpy(boundary + 2, var);
531
+
532
+	st.tempfd = -1;
533
+	st.filefd = -1;
534
+	st.filemode = 0600;
535
+
536
+	p = multipart_parser_init(boundary, &s);
537
+
538
+	free(boundary);
539
+
540
+	return p;
541
+}
542
+
543
+static int
544
+main_upload(int argc, char *argv[])
545
+{
546
+	int rem, len;
547
+	char buf[4096];
548
+	multipart_parser *p;
549
+
550
+	p = init_parser();
551
+
552
+	if (!p)
553
+	{
554
+		errno = EINVAL;
555
+		return response(false, "Invalid request");
556
+	}
557
+
558
+	while ((len = read(0, buf, sizeof(buf))) > 0)
559
+	{
560
+		rem = multipart_parser_execute(p, buf, len);
561
+
562
+		if (rem < len)
563
+			break;
564
+	}
565
+
566
+	multipart_parser_free(p);
567
+
568
+	/* read remaining post data */
569
+	while ((len = read(0, buf, sizeof(buf))) > 0);
570
+
571
+	return 0;
572
+}
573
+
574
+static int
575
+main_backup(int argc, char **argv)
576
+{
577
+	pid_t pid;
578
+	time_t now;
579
+	int len;
580
+	int fds[2];
581
+	char buf[4096];
582
+	char datestr[16] = { 0 };
583
+	char hostname[64] = { 0 };
584
+	char *fields[] = { "sessionid", NULL };
585
+
586
+	if (!postdecode(fields, 1) || !session_access(fields[1], "backup", "read"))
587
+		return failure(0, "Backup permission denied");
588
+
589
+	if (pipe(fds))
590
+		return failure(errno, "Failed to spawn pipe");
591
+
592
+	switch ((pid = fork()))
593
+	{
594
+	case -1:
595
+		return failure(errno, "Failed to fork process");
596
+
597
+	case 0:
598
+		dup2(fds[1], 1);
599
+
600
+		close(0);
601
+		close(2);
602
+		close(fds[0]);
603
+		close(fds[1]);
604
+
605
+		chdir("/");
606
+
607
+		execl("/sbin/sysupgrade", "/sbin/sysupgrade",
608
+		      "--create-backup", "-", NULL);
609
+
610
+		return -1;
611
+
612
+	default:
613
+		now = time(NULL);
614
+		strftime(datestr, sizeof(datestr) - 1, "%Y-%m-%d", localtime(&now));
615
+
616
+		if (gethostname(hostname, sizeof(hostname) - 1))
617
+			sprintf(hostname, "OpenWrt");
618
+
619
+		printf("Status: 200 OK\r\n");
620
+		printf("Content-Type: application/x-targz\r\n");
621
+		printf("Content-Disposition: attachment; "
622
+		       "filename=\"backup-%s-%s.tar.gz\"\r\n\r\n", hostname, datestr);
623
+
624
+		while ((len = read(fds[0], buf, sizeof(buf))) > 0)
625
+			fwrite(buf, len, 1, stdout);
626
+
627
+		waitpid(pid, NULL, 0);
628
+
629
+		close(fds[0]);
630
+		close(fds[1]);
631
+
632
+		return 0;
633
+	}
634
+}
635
+
636
+int main(int argc, char **argv)
637
+{
638
+	if (strstr(argv[0], "cgi-upload"))
639
+		return main_upload(argc, argv);
640
+	else if (strstr(argv[0], "cgi-backup"))
641
+		return main_backup(argc, argv);
642
+
643
+	return -1;
644
+}

+ 309
- 0
net/cgi-io/src/multipart_parser.c View File

@@ -0,0 +1,309 @@
1
+/* Based on node-formidable by Felix Geisendörfer
2
+ * Igor Afonov - afonov@gmail.com - 2012
3
+ * MIT License - http://www.opensource.org/licenses/mit-license.php
4
+ */
5
+
6
+#include "multipart_parser.h"
7
+
8
+#include <stdio.h>
9
+#include <stdarg.h>
10
+#include <string.h>
11
+
12
+static void multipart_log(const char * format, ...)
13
+{
14
+#ifdef DEBUG_MULTIPART
15
+    va_list args;
16
+    va_start(args, format);
17
+
18
+    fprintf(stderr, "[HTTP_MULTIPART_PARSER] %s:%d: ", __FILE__, __LINE__);
19
+    vfprintf(stderr, format, args);
20
+    fprintf(stderr, "\n");
21
+#endif
22
+}
23
+
24
+#define NOTIFY_CB(FOR)                                                 \
25
+do {                                                                   \
26
+  if (p->settings->on_##FOR) {                                         \
27
+    if (p->settings->on_##FOR(p) != 0) {                               \
28
+      return i;                                                        \
29
+    }                                                                  \
30
+  }                                                                    \
31
+} while (0)
32
+
33
+#define EMIT_DATA_CB(FOR, ptr, len)                                    \
34
+do {                                                                   \
35
+  if (p->settings->on_##FOR) {                                         \
36
+    if (p->settings->on_##FOR(p, ptr, len) != 0) {                     \
37
+      return i;                                                        \
38
+    }                                                                  \
39
+  }                                                                    \
40
+} while (0)
41
+
42
+
43
+#define LF 10
44
+#define CR 13
45
+
46
+struct multipart_parser {
47
+  void * data;
48
+
49
+  size_t index;
50
+  size_t boundary_length;
51
+
52
+  unsigned char state;
53
+
54
+  const multipart_parser_settings* settings;
55
+
56
+  char* lookbehind;
57
+  char multipart_boundary[1];
58
+};
59
+
60
+enum state {
61
+  s_uninitialized = 1,
62
+  s_start,
63
+  s_start_boundary,
64
+  s_header_field_start,
65
+  s_header_field,
66
+  s_headers_almost_done,
67
+  s_header_value_start,
68
+  s_header_value,
69
+  s_header_value_almost_done,
70
+  s_part_data_start,
71
+  s_part_data,
72
+  s_part_data_almost_boundary,
73
+  s_part_data_boundary,
74
+  s_part_data_almost_end,
75
+  s_part_data_end,
76
+  s_part_data_final_hyphen,
77
+  s_end
78
+};
79
+
80
+multipart_parser* multipart_parser_init
81
+    (const char *boundary, const multipart_parser_settings* settings) {
82
+
83
+  multipart_parser* p = malloc(sizeof(multipart_parser) +
84
+                               strlen(boundary) +
85
+                               strlen(boundary) + 9);
86
+
87
+  strcpy(p->multipart_boundary, boundary);
88
+  p->boundary_length = strlen(boundary);
89
+
90
+  p->lookbehind = (p->multipart_boundary + p->boundary_length + 1);
91
+
92
+  p->index = 0;
93
+  p->state = s_start;
94
+  p->settings = settings;
95
+
96
+  return p;
97
+}
98
+
99
+void multipart_parser_free(multipart_parser* p) {
100
+  free(p);
101
+}
102
+
103
+void multipart_parser_set_data(multipart_parser *p, void *data) {
104
+    p->data = data;
105
+}
106
+
107
+void *multipart_parser_get_data(multipart_parser *p) {
108
+    return p->data;
109
+}
110
+
111
+size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len) {
112
+  size_t i = 0;
113
+  size_t mark = 0;
114
+  char c, cl;
115
+  int is_last = 0;
116
+
117
+  while(i < len) {
118
+    c = buf[i];
119
+    is_last = (i == (len - 1));
120
+    switch (p->state) {
121
+      case s_start:
122
+        multipart_log("s_start");
123
+        p->index = 0;
124
+        p->state = s_start_boundary;
125
+
126
+      /* fallthrough */
127
+      case s_start_boundary:
128
+        multipart_log("s_start_boundary");
129
+        if (p->index == p->boundary_length) {
130
+          if (c != CR) {
131
+            return i;
132
+          }
133
+          p->index++;
134
+          break;
135
+        } else if (p->index == (p->boundary_length + 1)) {
136
+          if (c != LF) {
137
+            return i;
138
+          }
139
+          p->index = 0;
140
+          NOTIFY_CB(part_data_begin);
141
+          p->state = s_header_field_start;
142
+          break;
143
+        }
144
+        if (c != p->multipart_boundary[p->index]) {
145
+          return i;
146
+        }
147
+        p->index++;
148
+        break;
149
+
150
+      case s_header_field_start:
151
+        multipart_log("s_header_field_start");
152
+        mark = i;
153
+        p->state = s_header_field;
154
+
155
+      /* fallthrough */
156
+      case s_header_field:
157
+        multipart_log("s_header_field");
158
+        if (c == CR) {
159
+          p->state = s_headers_almost_done;
160
+          break;
161
+        }
162
+
163
+        if (c == '-') {
164
+          break;
165
+        }
166
+
167
+        if (c == ':') {
168
+          EMIT_DATA_CB(header_field, buf + mark, i - mark);
169
+          p->state = s_header_value_start;
170
+          break;
171
+        }
172
+
173
+        cl = tolower(c);
174
+        if (cl < 'a' || cl > 'z') {
175
+          multipart_log("invalid character in header name");
176
+          return i;
177
+        }
178
+        if (is_last)
179
+            EMIT_DATA_CB(header_field, buf + mark, (i - mark) + 1);
180
+        break;
181
+
182
+      case s_headers_almost_done:
183
+        multipart_log("s_headers_almost_done");
184
+        if (c != LF) {
185
+          return i;
186
+        }
187
+
188
+        p->state = s_part_data_start;
189
+        break;
190
+
191
+      case s_header_value_start:
192
+        multipart_log("s_header_value_start");
193
+        if (c == ' ') {
194
+          break;
195
+        }
196
+
197
+        mark = i;
198
+        p->state = s_header_value;
199
+
200
+      /* fallthrough */
201
+      case s_header_value:
202
+        multipart_log("s_header_value");
203
+        if (c == CR) {
204
+          EMIT_DATA_CB(header_value, buf + mark, i - mark);
205
+          p->state = s_header_value_almost_done;
206
+        }
207
+        if (is_last)
208
+            EMIT_DATA_CB(header_value, buf + mark, (i - mark) + 1);
209
+        break;
210
+
211
+      case s_header_value_almost_done:
212
+        multipart_log("s_header_value_almost_done");
213
+        if (c != LF) {
214
+          return i;
215
+        }
216
+        p->state = s_header_field_start;
217
+        break;
218
+
219
+      case s_part_data_start:
220
+        multipart_log("s_part_data_start");
221
+        NOTIFY_CB(headers_complete);
222
+        mark = i;
223
+        p->state = s_part_data;
224
+
225
+      /* fallthrough */
226
+      case s_part_data:
227
+        multipart_log("s_part_data");
228
+        if (c == CR) {
229
+            EMIT_DATA_CB(part_data, buf + mark, i - mark);
230
+            mark = i;
231
+            p->state = s_part_data_almost_boundary;
232
+            p->lookbehind[0] = CR;
233
+            break;
234
+        }
235
+        if (is_last)
236
+            EMIT_DATA_CB(part_data, buf + mark, (i - mark) + 1);
237
+        break;
238
+
239
+      case s_part_data_almost_boundary:
240
+        multipart_log("s_part_data_almost_boundary");
241
+        if (c == LF) {
242
+            p->state = s_part_data_boundary;
243
+            p->lookbehind[1] = LF;
244
+            p->index = 0;
245
+            break;
246
+        }
247
+        EMIT_DATA_CB(part_data, p->lookbehind, 1);
248
+        p->state = s_part_data;
249
+        mark = i --;
250
+        break;
251
+
252
+      case s_part_data_boundary:
253
+        multipart_log("s_part_data_boundary");
254
+        if (p->multipart_boundary[p->index] != c) {
255
+          EMIT_DATA_CB(part_data, p->lookbehind, 2 + p->index);
256
+          p->state = s_part_data;
257
+          mark = i --;
258
+          break;
259
+        }
260
+        p->lookbehind[2 + p->index] = c;
261
+        if ((++ p->index) == p->boundary_length) {
262
+            NOTIFY_CB(part_data_end);
263
+            p->state = s_part_data_almost_end;
264
+        }
265
+        break;
266
+
267
+      case s_part_data_almost_end:
268
+        multipart_log("s_part_data_almost_end");
269
+        if (c == '-') {
270
+            p->state = s_part_data_final_hyphen;
271
+            break;
272
+        }
273
+        if (c == CR) {
274
+            p->state = s_part_data_end;
275
+            break;
276
+        }
277
+        return i;
278
+
279
+      case s_part_data_final_hyphen:
280
+        multipart_log("s_part_data_final_hyphen");
281
+        if (c == '-') {
282
+            NOTIFY_CB(body_end);
283
+            p->state = s_end;
284
+            break;
285
+        }
286
+        return i;
287
+
288
+      case s_part_data_end:
289
+        multipart_log("s_part_data_end");
290
+        if (c == LF) {
291
+            p->state = s_header_field_start;
292
+            NOTIFY_CB(part_data_begin);
293
+            break;
294
+        }
295
+        return i;
296
+
297
+      case s_end:
298
+        multipart_log("s_end: %02X", (int) c);
299
+        break;
300
+
301
+      default:
302
+        multipart_log("Multipart parser unrecoverable error");
303
+        return 0;
304
+    }
305
+    ++ i;
306
+  }
307
+
308
+  return len;
309
+}

+ 48
- 0
net/cgi-io/src/multipart_parser.h View File

@@ -0,0 +1,48 @@
1
+/* Based on node-formidable by Felix Geisendörfer
2
+ * Igor Afonov - afonov@gmail.com - 2012
3
+ * MIT License - http://www.opensource.org/licenses/mit-license.php
4
+ */
5
+#ifndef _multipart_parser_h
6
+#define _multipart_parser_h
7
+
8
+#ifdef __cplusplus
9
+extern "C"
10
+{
11
+#endif
12
+
13
+#include <stdlib.h>
14
+#include <ctype.h>
15
+
16
+typedef struct multipart_parser multipart_parser;
17
+typedef struct multipart_parser_settings multipart_parser_settings;
18
+typedef struct multipart_parser_state multipart_parser_state;
19
+
20
+typedef int (*multipart_data_cb) (multipart_parser*, const char *at, size_t length);
21
+typedef int (*multipart_notify_cb) (multipart_parser*);
22
+
23
+struct multipart_parser_settings {
24
+  multipart_data_cb on_header_field;
25
+  multipart_data_cb on_header_value;
26
+  multipart_data_cb on_part_data;
27
+
28
+  multipart_notify_cb on_part_data_begin;
29
+  multipart_notify_cb on_headers_complete;
30
+  multipart_notify_cb on_part_data_end;
31
+  multipart_notify_cb on_body_end;
32
+};
33
+
34
+multipart_parser* multipart_parser_init
35
+    (const char *boundary, const multipart_parser_settings* settings);
36
+
37
+void multipart_parser_free(multipart_parser* p);
38
+
39
+size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len);
40
+
41
+void multipart_parser_set_data(multipart_parser* p, void* data);
42
+void * multipart_parser_get_data(multipart_parser* p);
43
+
44
+#ifdef __cplusplus
45
+} /* extern "C" */
46
+#endif
47
+
48
+#endif