diff options
author | Aleksey Veresov <aleksey@veresov.pro> | 2019-09-13 18:50:34 +0300 |
---|---|---|
committer | Aleksey Veresov <aleksey@veresov.pro> | 2019-09-13 18:50:34 +0300 |
commit | ad6188f911af896c9c77e9215bea3c5c2a4e6cc3 (patch) | |
tree | 158b4015ff302f72fe2bb8a0ee3d5441ffb66719 /src | |
download | magi-ad6188f911af896c9c77e9215bea3c5c2a4e6cc3.tar magi-ad6188f911af896c9c77e9215bea3c5c2a4e6cc3.tar.xz magi-ad6188f911af896c9c77e9215bea3c5c2a4e6cc3.zip |
Project name and license are added. Minor changes.
Diffstat (limited to 'src')
-rw-r--r-- | src/cookie.c | 369 | ||||
-rw-r--r-- | src/cookie.h | 47 | ||||
-rw-r--r-- | src/field.c | 54 | ||||
-rw-r--r-- | src/field.h | 45 | ||||
-rw-r--r-- | src/log.c | 16 | ||||
-rw-r--r-- | src/log.h | 8 | ||||
-rw-r--r-- | src/multipart.c | 537 | ||||
-rw-r--r-- | src/multipart.h | 23 | ||||
-rw-r--r-- | src/param.c | 51 | ||||
-rw-r--r-- | src/param.h | 43 | ||||
-rw-r--r-- | src/request.c | 280 | ||||
-rw-r--r-- | src/request.h | 52 | ||||
-rw-r--r-- | src/urlencoded.c | 180 | ||||
-rw-r--r-- | src/urlencoded.h | 10 |
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, ¶m); + } 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 |