aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cookie.c369
-rw-r--r--src/cookie.h47
-rw-r--r--src/field.c54
-rw-r--r--src/field.h45
-rw-r--r--src/log.c16
-rw-r--r--src/log.h8
-rw-r--r--src/multipart.c537
-rw-r--r--src/multipart.h23
-rw-r--r--src/param.c51
-rw-r--r--src/param.h43
-rw-r--r--src/request.c280
-rw-r--r--src/request.h52
-rw-r--r--src/urlencoded.c180
-rw-r--r--src/urlencoded.h10
14 files changed, 1715 insertions, 0 deletions
diff --git a/src/cookie.c b/src/cookie.c
new file mode 100644
index 0000000..4e757e6
--- /dev/null
+++ b/src/cookie.c
@@ -0,0 +1,369 @@
+#include "cookie.h"
+
+#include "log.h"
+#include <stdlib.h>
+#include <string.h>
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Cookie Parse
+ */
+enum st {
+ st_error = 0,
+ st_pre_name,
+ st_name,
+ st_post_name,
+ st_pre_data,
+ st_data,
+ st_post_data
+};
+
+enum data_type {
+ dt_plain = 0,
+ dt_version,
+ dt_path,
+ dt_domain,
+ dt_port
+};
+
+struct automata {
+ struct magi_cookie_list **list;
+ struct magi_cookie cookie;
+ char *buf;
+ int buf_len;
+ int buf_size;
+ int is_first;
+ int is_cookie2;
+ int is_quoted;
+ enum data_type data_t;
+};
+
+static void nulify_cookie(struct automata *a)
+{
+ a->cookie.name = 0;
+ a->cookie.data = 0;
+ a->cookie.path = 0;
+ a->cookie.domain = 0;
+ a->cookie.port = 0;
+}
+
+static void buf_new(struct automata *a)
+{
+ a->buf = 0;
+ a->buf_len = 0;
+ a->buf_size = 1;
+}
+
+static int buf_add(struct automata *a, char c)
+{
+ int ok = 1;
+ if (a->buf_len == a->buf_size - 1) {
+ a->buf_size *= 2;
+ a->buf = realloc(a->buf, a->buf_size);
+ }
+ if (a->buf) {
+ a->buf_len++;
+ a->buf[a->buf_len - 1] = c;
+ a->buf[a->buf_len] = 0;
+ } else {
+ ok = 0;
+ magi_log("[cookie] Cannot allocate automata buffer.");
+ }
+ return ok;
+}
+
+static enum data_type what_is_name(const struct automata *a)
+{
+ enum data_type data_t = dt_plain;
+ if (a->is_first && !strcmp(a->buf, "$Version")) {
+ data_t = dt_version;
+ } else if (a->is_cookie2) {
+ if (!strcmp(a->buf, "$Path")) {
+ data_t = dt_path;
+ } else if (!strcmp(a->buf, "$Domain")) {
+ data_t = dt_domain;
+ } else if (!strcmp(a->buf, "$Port")) {
+ data_t = dt_port;
+ }
+ }
+ return data_t;
+}
+
+static int end_name(struct automata *a)
+{
+ int ok = 1;
+ a->data_t = what_is_name(a);
+ if (a->data_t == dt_plain) {
+ if (a->cookie.name) {
+ ok = magi_cookie_list_add(a->list, &a->cookie);
+ }
+ nulify_cookie(a);
+ a->cookie.name = a->buf;
+ } else {
+ free(a->buf);
+ }
+ buf_new(a);
+ return ok;
+}
+
+static int end_data(struct automata *a)
+{
+ int ok = 1;
+ switch (a->data_t) {
+ case dt_plain: a->cookie.data = a->buf; break;
+ case dt_path: a->cookie.path = a->buf; break;
+ case dt_domain: a->cookie.domain = a->buf; break;
+ case dt_port: a->cookie.port = a->buf; break;
+ case dt_version:
+ if (strcmp(a->buf, "1")) {
+ ok = 0;
+ magi_log("[cookie] Version must be '1', readed: %s.", a->buf);
+ }
+ }
+ buf_new(a);
+ return ok;
+}
+
+static enum st parse_pre_name(struct automata *a, char c)
+{
+ enum st state;
+ if (c == ' ' || c == '\t') {
+ state = st_name;
+ } else if (32 <= c && c <= 126 && !strchr("()<>@,;:\\\"/[]?={}", c)) {
+ state = st_name;
+ if (!buf_add(a, c)) {
+ state = st_error;
+ }
+ } else {
+ state = st_error;
+ magi_log("[cookie] Pre-name, readed: \\%o (render: %c).", c, c);
+ }
+ return state;
+}
+
+static enum st parse_name(struct automata *a, char c)
+{
+ enum st state;
+ if (c == '=') {
+ state = st_pre_data;
+ if (!end_name(a)) {
+ state = st_error;
+ }
+ } else if (c == ' ' || c == '\t') {
+ state = st_post_name;
+ if (!end_name(a)) {
+ state = st_error;
+ }
+ } else if (32 <= c && c <= 126 && !strchr("()<>@,;:\\\"/[]?={}", c)) {
+ state = st_name;
+ if (!buf_add(a, c)) {
+ state = st_error;
+ }
+ } else {
+ state = st_error;
+ magi_log("[cookie] Reading name, readed: \\%o (render: %c).", c, c);
+ }
+ return state;
+}
+
+static enum st parse_post_name(struct automata *a, char c)
+{
+ enum st state;
+ if (c == '=') {
+ state = st_pre_data;
+ } else if (c == ' ' || c == '\t') {
+ state = st_post_name;
+ } else {
+ state = st_error;
+ magi_log(
+ "[cookie] Waiting for name-value separator, "
+ "readed: \\%o (render: %c).", c, c
+ );
+ }
+ return state;
+}
+
+static enum st parse_pre_data(struct automata *a, char c)
+{
+ enum st state;
+ if (c == ' ' || c == '\t') {
+ state = st_pre_data;
+ } else if (c == '"') {
+ state = st_data;
+ a->is_quoted = 1;
+ } else if (32 <= c && c <= 126 && !strchr("()<>@,;:\\\"/[]?={}", c)) {
+ state = st_data;
+ if (!buf_add(a, c)) {
+ state = st_error;
+ }
+ } else {
+ state = st_error;
+ magi_log("[cookie] Pre-value, readed: \\%o (render: %c).", c, c);
+ }
+ return state;
+}
+
+static enum st parse_not_quoted_data(struct automata *a, char c)
+{
+ enum st state;
+ if (c == ';' || (c == ',' && a->is_first)) {
+ state = st_pre_name;
+ a->is_first = 0;
+ if (!end_data(a)) {
+ state = st_error;
+ }
+ } else if (c == ' ' || c == '\t') {
+ state = st_post_data;
+ if (!end_data(a)) {
+ state = st_error;
+ }
+ } else if (32 <= c && c <= 126 && !strchr("()<>@,;:\\\"/[]?={}", c)) {
+ state = st_data;
+ if (!buf_add(a, c)) {
+ state = st_error;
+ }
+ } else {
+ state = st_error;
+ magi_log(
+ "[cookie] Reading not-quoted value, "
+ "readed: \\%o (render: %c).", c, c
+ );
+ }
+ return state;
+}
+
+static enum st parse_data(struct automata *a, char c)
+{
+ enum st state;
+ if (a->is_quoted) {
+ if (c == '"') {
+ state = st_post_data;
+ a->is_quoted = 0;
+ if (!end_data(a)) {
+ state = st_error;
+ }
+ } else {
+ state = st_data;
+ }
+ } else {
+ state = parse_not_quoted_data(a, c);
+ }
+ return state;
+}
+
+static enum st parse_post_data(struct automata *a, char c)
+{
+ enum st state;
+ if (c == ';' || (c == ',' && a->is_first)) {
+ state = st_pre_name;
+ } else if (c == ' ' || c == '\t') {
+ state = st_post_data;
+ } else {
+ state = st_error;
+ magi_log(
+ "[cookie] Waiting for separator between name-value pairs, "
+ "readed: \\%o (render: %c).", c, c
+ );
+ }
+ return state;
+}
+
+static int parse_end(struct automata *a, enum st s)
+{
+ int ok = 0;
+ if (s == st_post_data) {
+ ok = 1;
+ } else if (s == st_data) {
+ if (!a->is_quoted) {
+ if (a->cookie.name) {
+ if (end_data(a) && magi_cookie_list_add(a->list, &a->cookie)) {
+ ok = 1;
+ nulify_cookie(a);
+ buf_new(a);
+ }
+ } else {
+ magi_log("[cookie] No cookies set.");
+ }
+ } else {
+ magi_log("[cookie] In quotation when reached input end.");
+ }
+ } else if (s != st_error) {
+ magi_log("[cookie] Input ended in not correct state.");
+ }
+ free(a->cookie.name);
+ free(a->cookie.data);
+ free(a->cookie.path);
+ free(a->cookie.domain);
+ free(a->cookie.port);
+ free(a->buf);
+ return ok;
+}
+
+int magi_parse_cookie(struct magi_cookie_list **list, const char *input)
+{
+ struct automata a = { 0, { 0, 0, 0, 0, 0}, 0, 0, 1, 1, 0, 0, 0 };
+ enum st state = st_pre_name;
+ a.list = list;
+ while (*input && state) {
+ char c = *input++;
+ switch (state) {
+ case st_pre_name: state = parse_pre_name(&a, c); break;
+ case st_name: state = parse_name(&a, c); break;
+ case st_post_name: state = parse_post_name(&a, c); break;
+ case st_pre_data: state = parse_pre_data(&a, c); break;
+ case st_data: state = parse_data(&a, c); break;
+ case st_post_data: state = parse_post_data(&a, c);
+ default: break;
+ }
+ }
+ return parse_end(&a, state);
+}
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Cookie List
+ */
+int magi_cookie_list_add(
+ struct magi_cookie_list **list,
+ struct magi_cookie *item
+)
+{
+ struct magi_cookie_list *old = *list;
+ int ok = 1;
+ *list = malloc(sizeof(**list));
+ if (*list) {
+ (*list)->next = old;
+ (*list)->item = *item;
+ } else {
+ ok = 0;
+ magi_log("[cookie:list] Cannot allocate new list node.");
+ *list = old;
+ }
+ return ok;
+}
+
+char *magi_cookie_list_get(struct magi_cookie_list *list, const char *name)
+{
+ char *data = 0;
+ if (list && name) {
+ if (!strcmp(list->item.name, name)) {
+ data = list->item.data;
+ } else {
+ data = magi_cookie_list_get(list->next, name);
+ }
+ }
+ return data;
+}
+
+void magi_cookie_list_destroy(struct magi_cookie_list *list)
+{
+ if (list) {
+ magi_cookie_list_destroy(list->next);
+ free(list->next);
+ free(list->item.name);
+ free(list->item.data);
+ free(list->item.domain);
+ free(list->item.path);
+ free(list->item.port);
+ }
+}
diff --git a/src/cookie.h b/src/cookie.h
new file mode 100644
index 0000000..d65cbe5
--- /dev/null
+++ b/src/cookie.h
@@ -0,0 +1,47 @@
+#ifndef MAGI_INCLUDED_COOKIE_H
+#define MAGI_INCLUDED_COOKIE_H
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Cookie
+ */
+struct magi_cookie {
+ char *name; /* name: free(name) is valid. */
+ char *data; /* data: free(data) is valid. */
+ /* Following is used in Cookie2: */
+ char *path; /* path: free(path) is valid. */
+ char *domain; /* domain: free(domain) is valid. */
+ char *port; /* port: free(port) is valid. */
+};
+
+/* Null is valid "struct magi_cookie_list *" object. */
+struct magi_cookie_list {
+ struct magi_cookie_list *next;
+ struct magi_cookie item;
+};
+
+/* Returns null in case of error. */
+int magi_parse_cookie(struct magi_cookie_list **list, const char *input);
+
+/*
+ * Adds *item to the begining of *list, item and list are dereferencable;
+ * Returns null in case of error.
+ */
+int magi_cookie_list_add(
+ struct magi_cookie_list **list,
+ struct magi_cookie *item
+);
+
+/*
+ * Searchs for first node in list: node.name == name, name is C-string;
+ * Returns node.data if succeed, otherwise result is null.
+ */
+char *magi_cookie_list_get(struct magi_cookie_list *list, const char *name);
+
+/*
+ * Destroys list; list is not valid after destruction.
+ */
+void magi_cookie_list_destroy(struct magi_cookie_list *list);
+
+
+#endif
diff --git a/src/field.c b/src/field.c
new file mode 100644
index 0000000..0fa2664
--- /dev/null
+++ b/src/field.c
@@ -0,0 +1,54 @@
+#include "field.h"
+
+#include "log.h"
+#include "param.h"
+#include <stdlib.h>
+#include <string.h>
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Form Field
+ */
+int magi_field_list_add(struct magi_field_list **list, struct magi_field *item)
+{
+ struct magi_field_list *old = *list;
+ int ok = 1;
+ *list = malloc(sizeof(**list));
+ if (*list) {
+ (*list)->next = old;
+ (*list)->item = *item;
+ } else {
+ ok = 0;
+ magi_log("[field:list] Cannot allocate new list node.");
+ *list = old;
+ }
+ return ok;
+}
+
+struct magi_field *magi_field_list_get(
+ struct magi_field_list *list,
+ const char *name
+)
+{
+ struct magi_field *item = 0;
+ if (list && name) {
+ if (!strcmp(list->item.name, name)) {
+ item = &list->item;
+ } else {
+ item = magi_field_list_get(list->next, name);
+ }
+ }
+ return item;
+}
+
+void magi_field_list_destroy(struct magi_field_list *list)
+{
+ if (list) {
+ magi_field_list_destroy(list->next);
+ magi_param_list_destroy(list->item.params);
+ free(list->next);
+ free(list->item.name);
+ free(list->item.data);
+ free(list->item.params);
+ }
+}
diff --git a/src/field.h b/src/field.h
new file mode 100644
index 0000000..5d4cdb5
--- /dev/null
+++ b/src/field.h
@@ -0,0 +1,45 @@
+#ifndef MAGI_INCLUDED_FIELD_H
+#define MAGI_INCLUDED_FIELD_H
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Form Field
+ */
+struct magi_field {
+ char *name; /* name: free(name) is valid. */
+ char *data; /* data: free(data) is valid. */
+ int len; /* Length of data. */
+ struct magi_param_list *params; /* Only used if field is for file. */
+};
+
+/* Null is valid "struct magi_field_list *" object. */
+struct magi_field_list {
+ struct magi_field_list *next;
+ struct magi_field item;
+};
+
+/*
+ * Adds *item to the begining of *list, item and list are dereferencable;
+ * Returns null in case of error.
+ */
+int magi_field_list_add(
+ struct magi_field_list **list,
+ struct magi_field *item
+);
+
+/*
+ * Searchs for first node in list: node.name == name, name is C-string;
+ * Returns node itself if succeed, otherwise result is null.
+ */
+struct magi_field *magi_field_list_get(
+ struct magi_field_list *list,
+ const char *name
+);
+
+/*
+ * Destroys list; list is not valid after destruction.
+ */
+void magi_field_list_destroy(struct magi_field_list *list);
+
+
+#endif
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000..2dd6452
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,16 @@
+#include "log.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+
+void magi_log(const char *format, ...)
+{
+ #ifdef ERRLOG
+ va_list args;
+ va_start(args, format);
+ fputs("MAGI ERROR: ", stderr);
+ vfprintf(stderr, format, args);
+ fputc('\n', stderr);
+ va_end(args);
+ #endif
+}
diff --git a/src/log.h b/src/log.h
new file mode 100644
index 0000000..153969b
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,8 @@
+#ifndef MAGI_INCLUDED_ERROR_H
+#define MAGI_INCLUDED_ERROR_H
+
+
+void magi_log(const char *format, ...);
+
+
+#endif
diff --git a/src/multipart.c b/src/multipart.c
new file mode 100644
index 0000000..94a37cb
--- /dev/null
+++ b/src/multipart.c
@@ -0,0 +1,537 @@
+/* Support for multifile controls are not provided. */
+#include "multipart.h"
+
+#include "log.h"
+#include "param.h"
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Local Shortcuts
+ */
+static int add(char **dest, int *len, int *size, char c)
+{
+ int ok = 1;
+ if (*len + 1 == *size) {
+ *size *= 2;
+ *dest = realloc(*dest, *size);
+ }
+ if (*dest == 0) {
+ ok = 0;
+ magi_log("[multipart] Cannot allocate string.");
+ } else {
+ (*dest)[*len] = c;
+ ++*len;
+ (*dest)[*len] = 0;
+ }
+ return ok;
+}
+
+static void lowercase(char *str)
+{
+ if (str) {
+ while (*str) {
+ *str = tolower(*str);
+ ++str;
+ }
+ }
+}
+
+static char *create_str(char *begin, char *end)
+{
+ char *res;
+ res = malloc(end - begin + 1);
+ if (res) {
+ memcpy(res, begin, end - begin);
+ res[end - begin] = 0;
+ } else {
+ magi_log("[multipart] Cannot allocate string.");
+ }
+ return res;
+}
+
+static int is_token(char c)
+{
+ return 32 <= c && c <= 126 && !strchr("()<>@,;:\\\"/[]?={} \t", c);
+}
+
+static int is_str_token(char *str)
+{
+ int is = str && *str; /* Empty string is not valid. */
+ while (is && *str) {
+ is = is_token(*str);
+ ++str;
+ }
+ return is;
+}
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Automata for multipart/form-data
+ */
+enum st {
+ st_error = 0,
+ st_begin,
+ st_pname_pre,
+ st_pname,
+ st_pname_end,
+ st_pdata,
+ st_data,
+ st_end
+};
+
+struct automata {
+ struct magi_field_list **list;
+ struct magi_field field;
+ struct magi_param param;
+ char *buf;
+ int buf_size;
+ int size;
+ int len;
+ char *boundary;
+ int boundary_pos;
+ int boundary_len;
+ int is_end_suspected;
+ int is_CR_readed;
+ int is_quoted;
+ void (*callback)(struct magi_field *field, char *buffer, int size);
+};
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Automata Shortcuts
+ */
+static int content_disposition(struct automata *a)
+{
+ int ok = 1;
+ char *name = strchr(a->param.data, '=');
+ if (name) {
+ name += strspn(name, " \t") + 1;
+ if (*name == '"') {
+ ++name;
+ a->field.name = create_str(name, strchr(name, '"'));
+ if (a->field.name == 0) {
+ ok = 0;
+ } else if (a->field.name[0] == 0) {
+ ok = 0;
+ magi_log("[multipart] Wrong content-disposition quotation.");
+ }
+ } else {
+ a->field.name = create_str(name, name + strcspn(name, " \t"));
+ if (a->field.name == 0) {
+ ok = 0;
+ } else if (!is_str_token(a->field.name)) {
+ ok = 0;
+ magi_log(
+ "[multipart] Content-disposition value is not valid, "
+ "readed: %s.", a->field.name
+ );
+ }
+ }
+ if (ok) {
+ free(a->param.name);
+ free(a->param.data);
+ a->param.name = 0;
+ a->param.data = 0;
+ }
+ } else {
+ ok = 0;
+ magi_log("[multipart] Content-disposition has no '=' symbol.");
+ }
+ return ok;
+}
+
+static int param_end(struct automata *a)
+{
+ int ok = 1;
+ lowercase(a->param.name);
+ if (!strcmp(a->param.name, "content-disposition")) {
+ ok = content_disposition(a);
+ } else {
+ ok = magi_param_list_add(&a->field.params, &a->param);
+ a->param.name = 0;
+ a->param.data = 0;
+ }
+ a->size = 1;
+ a->len = 0;
+ return ok;
+}
+
+static int field_end(struct automata *a)
+{
+ int ok;
+ if (a->field.name == 0) {
+ ok = 0;
+ magi_log("[multipart] Field name is empty or not specified.");
+ } else {
+ if (a->callback) {
+ a->callback(&a->field, a->buf, a->buf_size);
+ a->buf_size = 0;
+ }
+ a->field.len = a->len;
+ ok = magi_field_list_add(a->list, &a->field);
+ if (!ok) {
+ free(a->field.name);
+ free(a->field.data);
+ magi_param_list_destroy(a->field.params);
+ free(a->field.params);
+ }
+ a->field.name = 0;
+ a->field.data = 0;
+ a->field.params = 0;
+ a->size = 1;
+ a->len = 0;
+ }
+ return ok;
+}
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Boundary interfaces
+ */
+static char sepget(const struct automata *a)
+{
+ char c;
+ const int pos_after = a->boundary_pos - 4 - a->boundary_len;
+ if (a->boundary_pos == 0) {
+ c = '\r';
+ } else if (a->boundary_pos == 1) {
+ c = '\n';
+ } else if (a->boundary_pos == 2 || a->boundary_pos == 3) {
+ c = '-';
+ } else if (a->boundary_pos - 4 < a->boundary_len) {
+ c = a->boundary[a->boundary_pos - 4];
+ } else if (pos_after == 0) {
+ c = '\r';
+ } else if (pos_after == 1) {
+ c = '\n';
+ } else {
+ c = 0;
+ }
+ return c;
+}
+
+static int seplen(const struct automata *a)
+{
+ return a->boundary_len + 6;
+}
+
+static char endget(const struct automata *a)
+{
+ char c;
+ const int pos_after = a->boundary_pos - 4 - a->boundary_len;
+ if (a->boundary_pos == 0) {
+ c = '\r';
+ } else if (a->boundary_pos == 1) {
+ c = '\n';
+ } else if (a->boundary_pos == 2 || a->boundary_pos == 3) {
+ c = '-';
+ } else if (a->boundary_pos - 4 < a->boundary_len) {
+ c = a->boundary[a->boundary_pos - 4];
+ } else if (pos_after == 0 || pos_after == 1) {
+ c = '-';
+ } else if (pos_after == 2) {
+ c = '\r';
+ } else if (pos_after == 3) {
+ c = '\n';
+ } else {
+ c = 0;
+ }
+ return c;
+}
+
+static int endlen(const struct automata *a)
+{
+ return a->boundary_len + 8;
+}
+
+static int is_semiend(const struct automata *a)
+{ /* Is end readed, expect last two chars, which are CR LF? */
+ return a->is_end_suspected && (a->boundary_pos == endlen(a) - 2);
+}
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * State analysers
+ */
+static enum st parse_begin(struct automata *a, char c)
+{
+ enum st state;
+ if (sepget(a) == c) {
+ a->boundary_pos++;
+ if (a->boundary_pos == seplen(a)) {
+ state = st_pname_pre;
+ } else {
+ state = st_begin;
+ }
+ } else {
+ state = st_begin;
+ a->boundary_pos = 0;
+ }
+ return state;
+}
+
+static enum st parse_pname_pre(struct automata *a, char c)
+{
+ enum st state;
+ if (a->is_CR_readed) {
+ a->is_CR_readed = 0;
+ if (c == '\n') {
+ state = st_data;
+ a->boundary_pos = 0;
+ } else {
+ state = st_error;
+ magi_log("[multipart] Waiting for name, CR is readed alone.");
+ }
+ } else if (c == '\r') {
+ state = st_pname_pre;
+ a->is_CR_readed = 1;
+ } else if (is_token(c)) {
+ if (add(&a->param.name, &a->len, &a->size, c)) {
+ state = st_pname;
+ } else {
+ state = st_error;
+ }
+ } else {
+ state = st_error;
+ magi_log(
+ "[multipart] Waiting for name, readed: \\%o (render: %c).", c, c
+ );
+ }
+ return state;
+}
+
+static enum st parse_pname(struct automata *a, char c)
+{
+ enum st state;
+ if (c == ':') {
+ state = st_pdata;
+ a->len = 0;
+ a->size = 1;
+ } else if (c == ' ' || c == '\t') {
+ state = st_pname_end;
+ } else if (is_token(c)) {
+ if (add(&a->param.name, &a->len, &a->size, c)) {
+ state = st_pname;
+ } else {
+ state = st_error;
+ }
+ } else {
+ state = st_error;
+ magi_log(
+ "[multipart] Reading name, readed: \\%o (render: %c).", c, c
+ );
+ }
+ return state;
+}
+
+static enum st parse_pname_end(struct automata *a, char c)
+{
+ enum st state;
+ if (c == ':') {
+ state = st_pdata;
+ a->len = 0;
+ a->size = 1;
+ } else if (c == ' ' || c == '\t') {
+ state = st_pname_end;
+ } else {
+ state = st_error;
+ magi_log(
+ "[multipart] Waiting for name-value separator, "
+ "readed: \\%o (render: %c).", c, c
+ );
+ }
+ return state;
+}
+
+static enum st parse_pdata(struct automata *a, char c)
+{
+ enum st state;
+ if (a->is_CR_readed) {
+ a->is_CR_readed = 0;
+ if (c == '\n') {
+ if (param_end(a)) {
+ state = st_pname_pre;
+ } else {
+ state = st_error;
+ }
+ } else if (add(&a->param.data, &a->len, &a->size, '\r')) {
+ if (add(&a->param.data, &a->len, &a->size, c)) {
+ state = st_pdata;
+ } else {
+ state = st_error;
+ }
+ } else {
+ state = st_error;
+ }
+ } else if (c == '\r') {
+ state = st_pdata;
+ a->is_CR_readed = 1;
+ } else {
+ if (add(&a->param.data, &a->len, &a->size, c)) {
+ state = st_pdata;
+ } else {
+ state = st_error;
+ }
+ }
+ return state;
+}
+
+static void apply_callback(struct automata *a)
+{
+ if (a->callback && a->buf_size == magi_parse_multipart_callback_size) {
+ a->callback(&a->field, a->buf, a->buf_size);
+ a->buf_size = 0;
+ }
+}
+
+static enum st data_add(struct automata *a, char c)
+{
+ static int max_buf_size = magi_parse_multipart_callback_size + 1;
+ enum st state;
+ char **dest;
+ int *len;
+ int *size;
+ int pos = a->boundary_pos;
+ state = st_data;
+ a->boundary_pos = 0;
+ if (a->callback) {
+ dest = &a->buf;
+ len = &a->buf_size;
+ size = &max_buf_size;
+ } else {
+ dest = &a->field.data;
+ len = &a->len;
+ size = &a->size;
+ }
+ while (a->boundary_pos < pos) {
+ if (a->is_end_suspected) {
+ add(dest, len, size, endget(a));
+ } else {
+ add(dest, len, size, sepget(a));
+ }
+ apply_callback(a);
+ a->boundary_pos++;
+ }
+ a->boundary_pos = 0;
+ a->is_end_suspected = 0;
+ if (sepget(a) == c) {
+ a->boundary_pos++;
+ if (a->boundary_pos == seplen(a)) {
+ state = st_pname_pre;
+ field_end(a);
+ } else {
+ state = st_data;
+ }
+ } else {
+ add(dest, len, size, c);
+ apply_callback(a);
+ }
+ return state;
+}
+
+static enum st parse_data(struct automata *a, char c)
+{
+ enum st state;
+ if (a->is_end_suspected) {
+ if (endget(a) == c) {
+ a->boundary_pos++;
+ if (a->boundary_pos == endlen(a)) {
+ state = st_end;
+ field_end(a);
+ } else {
+ state = st_data;
+ }
+ } else {
+ state = data_add(a, c);
+ }
+ } else if (sepget(a) == c) {
+ a->boundary_pos++;
+ if (a->boundary_pos == seplen(a)) {
+ state = st_pname_pre;
+ field_end(a);
+ } else {
+ state = st_data;
+ }
+ } else if ((a->boundary_pos == seplen(a) - 2) && endget(a) == c) {
+ state = st_data;
+ a->is_end_suspected = 1;
+ a->boundary_pos++;
+ } else {
+ state = data_add(a, c);
+ }
+ return state;
+}
+
+static enum st parse_end(struct automata *a, char c)
+{
+ return st_end;
+}
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Automata Runner
+ */
+static int
+run_automata(struct automata *a, int (*next)(void *thing), void *thing)
+{
+ int ok = 1;
+ enum st state = st_begin;
+ int c;
+ for (c = next(thing); state && c != EOF; c = next(thing)) {
+ switch (state) {
+ case st_begin: state = parse_begin(a, c); break;
+ case st_pname_pre: state = parse_pname_pre(a, c); break;
+ case st_pname: state = parse_pname(a, c); break;
+ case st_pname_end: state = parse_pname_end(a, c); break;
+ case st_pdata: state = parse_pdata(a, c); break;
+ case st_data: state = parse_data(a, c); break;
+ case st_end: state = parse_end(a, c);
+ default: break;
+ }
+ }
+ if (state == st_data && is_semiend(a)) {
+ state = st_end;
+ field_end(a);
+ }
+ if (state != st_end) {
+ ok = 0;
+ if (state != st_error) {
+ magi_log("[multipart] Input ended unexpectedly.");
+ }
+ free(a->field.name);
+ free(a->field.data);
+ }
+ return ok;
+}
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Automata Interfaces
+ */
+int magi_parse_multipart(
+ struct magi_field_list **list,
+ int (*get_next)(void *),
+ void *get_next_arg,
+ char *boundary,
+ void (*callback)(struct magi_field *field, char *buffer, int len)
+)
+{
+ struct automata a = {
+ 0, { 0, 0, 0 }, { 0, 0 }, 0, 0, 1, 0, 0, 2, 0, 0, 0
+ };
+ int ok = 0;
+ a.list = list;
+ a.boundary = boundary;
+ a.boundary_len = strlen(boundary);
+ a.callback = callback;
+ a.buf = malloc(magi_parse_multipart_callback_size + 1);
+ if (a.buf) {
+ ok = run_automata(&a, get_next, get_next_arg);
+ free(a.buf);
+ }
+ return ok;
+}
diff --git a/src/multipart.h b/src/multipart.h
new file mode 100644
index 0000000..3e77391
--- /dev/null
+++ b/src/multipart.h
@@ -0,0 +1,23 @@
+#ifndef MAGI_INCLUDED_MULTIPART_H
+#define MAGI_INCLUDED_MULTIPART_H
+
+#include "field.h"
+
+
+enum {
+ magi_parse_multipart_callback_size = 64
+};
+
+
+int magi_parse_multipart(
+ struct magi_field_list **list,
+ int (*get_next)(void *),
+ void *get_next_arg,
+ char *boundary,
+ /* End if size < magi_parse_multipart_callback_size. */
+ /* Null callback means filling list. */
+ void (*callback)(struct magi_field *field, char *buffer, int size)
+);
+
+
+#endif
diff --git a/src/param.c b/src/param.c
new file mode 100644
index 0000000..ebd2216
--- /dev/null
+++ b/src/param.c
@@ -0,0 +1,51 @@
+#include "param.h"
+
+#include "log.h"
+#include <stdlib.h>
+#include <string.h>
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Parameter
+ */
+int magi_param_list_add(struct magi_param_list **list, struct magi_param *item)
+{
+ struct magi_param_list *old = *list;
+ int ok = 1;
+ *list = malloc(sizeof(**list));
+ if (*list) {
+ (*list)->next = old;
+ (*list)->item = *item;
+ } else {
+ ok = 0;
+ magi_log("[param:list] Cannot allocate new list node.");
+ *list = old;
+ }
+ return ok;
+}
+
+struct magi_param *magi_param_list_get(
+ struct magi_param_list *list,
+ const char *name
+)
+{
+ struct magi_param *item = 0;
+ if (list && name) {
+ if (!strcmp(list->item.name, name)) {
+ item = &list->item;
+ } else {
+ item = magi_param_list_get(list->next, name);
+ }
+ }
+ return item;
+}
+
+void magi_param_list_destroy(struct magi_param_list *list)
+{
+ if (list) {
+ magi_param_list_destroy(list->next);
+ free(list->next);
+ free(list->item.name);
+ free(list->item.data);
+ }
+}
diff --git a/src/param.h b/src/param.h
new file mode 100644
index 0000000..31b787a
--- /dev/null
+++ b/src/param.h
@@ -0,0 +1,43 @@
+#ifndef MAGI_INCLUDED_PARAM_H
+#define MAGI_INCLUDED_PARAM_H
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Parameter
+ */
+struct magi_param {
+ char *name; /* name: free(name) is valid. */
+ char *data; /* data: free(data) is valid. */
+};
+
+/* Null is valid "struct magi_param_list *" object. */
+struct magi_param_list {
+ struct magi_param_list *next;
+ struct magi_param item;
+};
+
+/*
+ * Adds *item to the begining of *list, item and list are dereferencable;
+ * Returns null in case of error.
+ */
+int magi_param_list_add(
+ struct magi_param_list **list,
+ struct magi_param *item
+);
+
+/*
+ * Searchs for first node in list: node.name == name, name is C-string;
+ * Returns node itself if succeed, otherwise result is null.
+ */
+struct magi_param *magi_param_list_get(
+ struct magi_param_list *list,
+ const char *name
+);
+
+/*
+ * Destroys list; list is not valid after destruction.
+ */
+void magi_param_list_destroy(struct magi_param_list *list);
+
+
+#endif
diff --git a/src/request.c b/src/request.c
new file mode 100644
index 0000000..79388f3
--- /dev/null
+++ b/src/request.c
@@ -0,0 +1,280 @@
+#include "request.h"
+
+#include "cookie.h"
+#include "log.h"
+#include "multipart.h"
+#include "param.h"
+#include "urlencoded.h"
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+extern char **environ;
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Local Shortcuts
+ */
+static void lowercase(char *str)
+{
+ if (str) {
+ while (*str) {
+ *str = tolower(*str);
+ ++str;
+ }
+ }
+}
+
+static char *create_str(const char *begin, const char *end)
+{
+ char *res;
+ res = malloc(end - begin + 1);
+ if (res) {
+ memcpy(res, begin, end - begin);
+ res[end - begin] = 0;
+ }
+ return res;
+}
+
+static char *str_alloc(int len)
+{
+ char *str = malloc(len + 1);
+ if (str) {
+ str[len] = 0;
+ } else {
+ magi_log("[request] Cannot allocate string.");
+ }
+ return str;
+}
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Common Request Handling
+ */
+void magi_request_destroy(struct magi_request *request)
+{
+ if (request) {
+ magi_field_list_destroy(request->fields);
+ magi_cookie_list_destroy(request->cookies);
+ free(request->fields);
+ free(request->cookies);
+ free(request->method);
+ free(request->uri);
+ free(request->document_root);
+ free(request->document_uri);
+ free(request->script_name);
+ free(request->script_filename);
+ free(request->remote_addr);
+ free(request->remote_port);
+ free(request->server_addr);
+ free(request->server_name);
+ free(request->server_port);
+ free(request->server_protocol);
+ free(request->server_software);
+ magi_param_list_destroy(request->http_params);
+ free(request->http_params);
+ }
+}
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * CGI Request Handling
+ */
+/* Helpers for CGI Request Handling */
+static int plain_env(char **dest, char *env_name)
+{
+ int ok = 1;
+ const char *env = getenv(env_name);
+ if (env) {
+ *dest = str_alloc(strlen(env));
+ if (*dest) {
+ strcpy(*dest, env);
+ } else {
+ ok = 0;
+ }
+ } else {
+ *dest = 0;
+ }
+ return ok;
+}
+
+static int lower_env(char **dest, char *env_name)
+{
+ int ok = plain_env(dest, env_name);
+ lowercase(*dest);
+ return ok;
+}
+
+static int cgi_http_env(struct magi_request *r)
+{
+ int ok = 1;
+ char **env = environ;
+ r->http_params = 0;
+ while (*env) {
+ if (!strncmp(*env, "HTTP_", 5) && strncmp(*env, "HTTP_COOKIE=", 12)) {
+ struct magi_param param;
+ /* At least one '=' must be in *env, according to format. */
+ char *name_end = strchr(*env, '=');
+ param.name = str_alloc(name_end - *env);
+ if (param.name) {
+ memcpy(param.name, *env, name_end - *env);
+ param.data = str_alloc(strlen(name_end + 1));
+ if (param.data) {
+ strcpy(param.data, name_end + 1);
+ } else {
+ free(param.name);
+ }
+ }
+ if (param.name && param.data) {
+ ok = magi_param_list_add(&r->http_params, &param);
+ } else {
+ ok = 0;
+ }
+ }
+ ++env;
+ }
+ return ok;
+}
+
+static int cgi_env(struct magi_request *r)
+{
+ int ok = cgi_http_env(r);
+ ok = ok && lower_env(&r->method, "REQUEST_METHOD");
+ ok = ok && plain_env(&r->uri, "REQUEST_URI");
+ ok = ok && plain_env(&r->document_root, "DOCUMENT_ROOT");
+ ok = ok && plain_env(&r->document_uri, "DOCUMENT_URI");
+ ok = ok && plain_env(&r->script_name, "SCRIPT_NAME");
+ ok = ok && plain_env(&r->script_filename, "SCRIPT_FILENAME");
+ ok = ok && plain_env(&r->remote_addr, "REMOTE_ADDR");
+ ok = ok && plain_env(&r->remote_port, "REMOTE_PORT");
+ ok = ok && plain_env(&r->server_addr, "SERVER_ADDR");
+ ok = ok && lower_env(&r->server_name, "SERVER_NAME");
+ ok = ok && plain_env(&r->server_port, "SERVER_PORT");
+ ok = ok && lower_env(&r->server_protocol, "SERVER_PROTOCOL");
+ ok = ok && plain_env(&r->server_software, "SERVER_SOFTWARE");
+ return ok;
+}
+
+static int cgi_cookies(struct magi_cookie_list **list)
+{
+ int ok = 1;
+ const char *env = getenv("HTTP_COOKIE");
+ *list = 0;
+ if (env && *env) {
+ ok = magi_parse_cookie(list, env);
+ } else {
+ *list = 0;
+ }
+ return ok;
+}
+
+static int cgi_input_get(char **input)
+{
+ int ok = 1;
+ const char *env_input = getenv("QUERY_STRING");
+ if (env_input) {
+ *input = str_alloc(strlen(env_input));
+ if (*input) {
+ strcpy(*input, env_input);
+ } else {
+ ok = 0;
+ }
+ }
+ return ok;
+}
+
+static int cgi_input_post(char **input, int max)
+{
+ int ok = 1;
+ int input_len = strtoul(getenv("CONTENT_LENGTH"), 0, 10);
+ if (input_len && (input_len < max || !max)) {
+ *input = str_alloc(input_len);
+ if (*input) {
+ if (fread(*input, 1, input_len, stdin) != input_len) {
+ ok = 0;
+ magi_log("[request:cgi] Content-length is not correct.");
+ }
+ } else {
+ ok = 0;
+ }
+ }
+ return ok;
+}
+
+static char *bound(const char *type)
+{
+ char *res = 0;
+ type = strchr(type, '=');
+ if (type) {
+ type += strspn(type, " \t") + 1;
+ if (*type == '"') {
+ ++type;
+ res = create_str(type, strchr(type, '"'));
+ } else {
+ res = create_str(type, type + strcspn(type, " \t"));
+ }
+ }
+ return res;
+}
+
+static int intput_getter(void *any)
+{
+ return getchar();
+}
+
+
+/* Interfacial CGI Request Handling */
+int magi_request_build_cgi(
+ struct magi_request *request,
+ void (*callback)(struct magi_field *field, char *buffer, int len),
+ int max_post
+)
+{
+ int ok = cgi_env(request) && cgi_cookies(&request->cookies);
+ request->fields = 0;
+ if (request->method) {
+ if (!strcmp(request->method, "post")) {
+ const char *t = getenv("CONTENT_TYPE");
+ if (t) {
+ if (!strncmp(t, "multipart/form-data", 19)) {
+ char *boundary = bound(t);
+ if (boundary && *boundary) {
+ ok = magi_parse_multipart(
+ &request->fields,
+ intput_getter,
+ 0,
+ boundary,
+ callback
+ );
+ } else {
+ ok = 0;
+ magi_log("[request:cgi] Multipart bound is not set.");
+ }
+ free(boundary);
+ } else if (!strcmp(t, "application/x-www-form-urlencoded")) {
+ char *in = 0;
+ ok = cgi_input_post(&in, max_post);
+ ok = ok && magi_parse_urlencoded(&request->fields, in);
+ free(in);
+ } else {
+ ok = 0;
+ magi_log("[request:cgi] Unknown content type.");
+ }
+ } else {
+ ok = 0;
+ magi_log("[request:cgi] Content-type is not set.");
+ }
+ } else if (!strcmp(request->method, "get")) {
+ char *in = 0;
+ ok = cgi_input_get(&in);
+ ok = ok && magi_parse_urlencoded(&request->fields, in);
+ free(in);
+ }
+ }
+ if (!ok) {
+ magi_request_destroy(request);
+ }
+ return ok;
+}
diff --git a/src/request.h b/src/request.h
new file mode 100644
index 0000000..4957099
--- /dev/null
+++ b/src/request.h
@@ -0,0 +1,52 @@
+#ifndef MAGI_INCLUDED_REQUEST_H
+#define MAGI_INCLUDED_REQUEST_H
+
+#include "field.h"
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Request
+ */
+struct magi_request {
+ struct magi_field_list *fields;
+ struct magi_cookie_list *cookies;
+ char *method;
+ char *uri;
+ char *document_root;
+ char *document_uri;
+ char *script_name;
+ char *script_filename;
+ char *remote_addr;
+ char *remote_port;
+ char *server_addr;
+ char *server_name;
+ char *server_port;
+ char *server_protocol;
+ char *server_software;
+ struct magi_param_list *http_params;
+}; /* http[s]://{server_name}[:{server_port}]{script_name}[?...] */
+
+
+/* Common Request Handling */
+/*
+ * Destroys request; request is not valid after destruction.
+ */
+void magi_request_destroy(struct magi_request *request);
+
+/* CGI Request Handling */
+/*
+ * Constructs request using environment variables and standart I/O;
+ * Returns null if succeed, otherwise error code.
+ */
+int magi_request_build_cgi(
+ struct magi_request *request,
+ /* Callback will be used only for fields loaded via multipart. */
+ /* Null callback disables callback system. */
+ void (*callback)(struct magi_field *field, char *buffer, int len),
+ int max_post
+);
+
+/* TODO: FastCGI Request Handling */
+
+
+#endif
diff --git a/src/urlencoded.c b/src/urlencoded.c
new file mode 100644
index 0000000..e3d4e56
--- /dev/null
+++ b/src/urlencoded.c
@@ -0,0 +1,180 @@
+#include "urlencoded.h"
+
+#include "field.h"
+#include "log.h"
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Shortcuts
+ */
+/* Shouldn't be called with 'c' as not hex digit. */
+static char from_hex(char c)
+{
+ char num;
+ if (isdigit(c)) {
+ num = c - '0';
+ } else {
+ num = toupper(c) - 'A' + 10;
+ }
+ return num;
+}
+
+static int is_hex(char c)
+{
+ return isdigit(c) || strchr("ABCDEF", toupper(c));
+}
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * URL Decoding
+ */
+static int deurl(char *data)
+{
+ int ok = 1;
+ int ti = 0;
+ int ci;
+ for (ci = 0; ok && data[ci]; ++ti, ++ci) {
+ if (data[ci] == '%') {
+ if (is_hex(data[ci + 1]) && is_hex(data[ci + 2])) {
+ /* Since chars can be signed, arithmetics are not safe. */
+ data[ti] = from_hex(data[ci + 2]); /* 00xx */
+ data[ti] |= from_hex(data[ci + 1]) << 4; /* XXxx */
+ ci += 2;
+ } else {
+ ok = 0;
+ magi_log(
+ "[urlencoded] Waiting for two hex digits after '%%', "
+ "readed: \\%o\\%o (render: %c%c)",
+ data[ci + 1], data[ci + 2], data[ci + 1], data[ci + 2]
+ );
+ }
+ } else if (data[ci] == '+') {
+ data[ti] = ' ';
+ } else {
+ data[ti] = data[ci];
+ }
+ }
+ data[ti++] = 0;
+ return ok;
+}
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Urlencoded Automata
+ */
+enum st {
+ st_error = 0,
+ st_name,
+ st_data
+};
+
+struct automata {
+ struct magi_field_list **list;
+ struct magi_field field;
+ int size;
+ int len;
+};
+
+
+static enum st parse_name(struct automata *a, char c)
+{
+ enum st state;
+ if (c != '&' && c != ';') {
+ if (c == '=') {
+ state = st_data;
+ a->size = 1;
+ a->len = 0;
+ } else {
+ if (a->len == a->size - 1) {
+ a->size *= 2;
+ a->field.name = realloc(a->field.name, a->size);
+ }
+ if (!a->field.name) {
+ state = st_error;
+ magi_log("[urlencoded] Cannot allocate field name.");
+ } else {
+ state = st_name;
+ a->len++;
+ a->field.name[a->len - 1] = c;
+ a->field.name[a->len] = 0;
+ }
+ }
+ } else {
+ state = st_error;
+ magi_log("[urlencoded] Reading name, readed: \\%o (render: %c).", c, c);
+ }
+ return state;
+}
+
+static enum st end_data(struct automata *a)
+{
+ enum st state = st_error;
+ if (deurl(a->field.name) && deurl(a->field.data)) {
+ a->field.len = strlen(a->field.data);
+ if (magi_field_list_add(a->list, &a->field)) {
+ state = st_name;
+ a->size = 1;
+ a->len = 0;
+ a->field.name = 0;
+ a->field.data = 0;
+ }
+ }
+ return state;
+}
+
+static enum st parse_data(struct automata *a, char c)
+{
+ enum st state;
+ if (c != '=') {
+ if (c == '&' || c == ';') {
+ state = end_data(a);
+ } else {
+ if (a->len == a->size - 1) {
+ a->size *= 2;
+ a->field.data = realloc(a->field.data, a->size);
+ }
+ if (!a->field.data) {
+ state = st_error;
+ magi_log("[urlencoded] Cannot allocate field data.");
+ } else {
+ state = st_data;
+ a->len++;
+ a->field.data[a->len - 1] = c;
+ a->field.data[a->len] = 0;
+ }
+ }
+ } else {
+ state = st_error;
+ magi_log("[urlencoded] Reading data, readed: \\%o (render: %c).", c, c);
+ }
+ return state;
+}
+
+int magi_parse_urlencoded(struct magi_field_list **list, const char *input)
+{
+ enum st state = st_name;
+ struct automata a = { 0, { 0, 0, 0 }, 1, 0 };
+ if (input && *input) {
+ a.list = list;
+ while (state && *input) {
+ switch (state) {
+ case st_name: state = parse_name(&a, *input); break;
+ case st_data: state = parse_data(&a, *input);
+ default: break;
+ }
+ ++input;
+ }
+ if (state == st_data) {
+ state = end_data(&a);
+ } else if (state == st_name) {
+ state = st_error;
+ magi_log("[urlencoded] Input ended while reading name.");
+ }
+ free(a.field.name);
+ free(a.field.data);
+ }
+ return state != st_error;
+}
diff --git a/src/urlencoded.h b/src/urlencoded.h
new file mode 100644
index 0000000..aa3023e
--- /dev/null
+++ b/src/urlencoded.h
@@ -0,0 +1,10 @@
+#ifndef MAGI_INCLUDED_URLENCODED_H
+#define MAGI_INCLUDED_URLENCODED_H
+
+#include "field.h"
+
+
+int magi_parse_urlencoded(struct magi_field_list **list, const char *input);
+
+
+#endif