From 07657b8a9f5c2fd9047594ec8604b9c439a999e4 Mon Sep 17 00:00:00 2001 From: Aleksey Veresov Date: Sat, 4 Apr 2020 19:39:38 +0300 Subject: [magi] Finalization. --- Makefile | 6 +- README | 10 +- examples/append.c | 52 +++++---- examples/cookie.c | 40 ++++--- examples/echo.c | 96 ++++++++--------- examples/fastcgi.c | 33 ------ examples/upload.c | 62 ++++++----- include/magi.h | 3 +- include/magi/cgi.h | 18 ---- include/magi/error.h | 4 + include/magi/fastcgi.h | 14 --- include/magi/loadfiles.h | 2 - include/magi/parse.h | 18 ++++ include/magi/request.h | 20 ++-- include/magi/response.h | 63 ++++------- include/magi/session.h | 17 --- include/magi/urlenc.h | 2 - man/magi.3 | 7 -- src/cgi.c | 266 ----------------------------------------------- src/error.c | 19 +++- src/fastcgi.c | 23 ---- src/parse.c | 206 ++++++++++++++++++++++++++++++++++++ src/request.c | 11 -- src/response.c | 151 +++++++++++---------------- src/session.c | 58 ----------- 25 files changed, 479 insertions(+), 722 deletions(-) delete mode 100644 examples/fastcgi.c delete mode 100644 include/magi/cgi.h delete mode 100644 include/magi/fastcgi.h create mode 100644 include/magi/parse.h delete mode 100644 include/magi/session.h delete mode 100644 man/magi.3 delete mode 100644 src/cgi.c delete mode 100644 src/fastcgi.c create mode 100644 src/parse.c delete mode 100644 src/session.c diff --git a/Makefile b/Makefile index 18fd89b..6d2c8bc 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,6 @@ # Compilation Options # Debug mode [yes/no] (allowing to debug the library via gdb): DEBUG ?= no -# Optional modules (remove unwanted ones): -MODULES ?= cgi fastcgi loadfiles urlenc # Specify your favourite C compiler here: CC ?= gcc @@ -30,8 +28,8 @@ EXADIR = examples # Library itself: LIB = libmagi.a # Modules: -EXTERNAL = cookie error file param request response session $(MODULES) -INTERNAL = cookies multipart tools urlencoded +EXTERNAL = $(foreach x,$(notdir $(wildcard $(INCLUDE)/magi/*.h)),$(x:.h=)) +INTERNAL = $(foreach x,$(notdir $(wildcard $(SRCDIR)/*.h)),$(x:.h=)) # Default target is library: TARGET = $(BUILD)/$(LIB) diff --git a/README b/README index f363b11..1e05137 100644 --- a/README +++ b/README @@ -1,5 +1,5 @@ Description -Magi Library (libmagi) implements Gateway Interfaces, namely CGI and FastCGI. +Magi Library (libmagi) implements Common Gateway Interface. Overview Magi are free and open-source software: legal info is in the 'LICENSE' file. @@ -22,12 +22,12 @@ Running 'make examples' produces executable for each example in 'examples'. Descriptions and details are in corresponding source files. Usage -magi_request handles incoming request, general workflow for CGI is to: +magi_request handles incoming request, general workflow is to: 1. prepare request for analysis, setup defaults via magi_request_init; -2. analyse CGI request via magi_cgi; +2. analyse CGI request via magi_parse; 4. do your work, and response request: - 1. specify headers (e.g. magi_response_cookie), - 2. fill response body (e.g. magi_response); + 1. specify and send headers (magi_response_...), + 2. response body (through stdout); 5. finally, free memory via magi_request_free. Motivation diff --git a/examples/append.c b/examples/append.c index 8417796..fe85492 100644 --- a/examples/append.c +++ b/examples/append.c @@ -1,40 +1,46 @@ +/* This is the very basic example, showing work with form fields to append + * specified by user in form addon into some file (i.e. "file_to_append"). + */ #include #include -void response(magi_request *r) +void append(magi_request *r) { char *data = magi_request_param(r, "addon"); - magi_response(r, - "" - "" - "Append to File" - ""); - if (data) { + if (data) { /* If field "addon" was in request: */ FILE *file = fopen("file_to_append", "a"); - fputs(data, file); + fputs(data, file); /* Append addon into the file. */ fclose(file); - magi_response(r, "

Appended!

"); + printf("

Appended!

"); /* And display success message. */ } - magi_response(r, - "
" - "" - "" - "
" - "" - ""); +} + +void response(magi_request *r) +{ + magi_response_default(); /* Pass default headers and send body: */ + printf("" + "" + "Append to File" + ""); + append(r); + printf("
" + "" + "" + "
" + ""); } int main() { magi_request request; - magi_request_init(&request); - if (magi_cgi(&request)) { - response(&request); - } else { - magi_response_error(&request); + magi_request_init(&request); /* Setting defaults. */ + if (magi_parse(&request)) { /* If parsing was done successful */ + response(&request); /* we need to response the request. */ + } else { /* And display error overwise: */ + magi_error_response(request.error); } - magi_request_free(&request); + magi_request_free(&request); /* Don't forget to free everything after. */ return 0; } diff --git a/examples/cookie.c b/examples/cookie.c index e18c100..c64ca0c 100644 --- a/examples/cookie.c +++ b/examples/cookie.c @@ -1,38 +1,46 @@ +/* Basic example of cookie handling through response headers. + * It lists all recieved cookies and set one via magi_response, + * showing work with headers in magi. + */ #include void list_cookies(magi_request *r) { magi_cookies *current; - magi_response(r, "Cookies:"); + printf("Cookies:"); + /* Iterate through all cookies in request to show them in body: */ for (current = r->cookies; current; current = current->next) { magi_cookie *c = ¤t->item; - magi_response_format(r, "
[%s]=[%s]", c->name, c->data); + printf("
[%s]=[%s]", c->name, c->data); } } void response(magi_request *r) { - magi_response_cookie(r, "cookie", "monster"); - magi_response(r, - "" - "" - "Cookie Listing and Setting" - "

"); + magi_response head; + magi_response_init(&head); /* Setting defaults. */ + /* Set cookie "cookie" with value "monster" on clientside: */ + magi_response_cookie(&head, "cookie", "monster"); + magi_response_free(&head); /* Send headers and start sending body: */ + printf("" + "" + "Cookie Listing and Setting" + "

"); list_cookies(r); - magi_response(r, "

"); + printf("

"); } int main() { magi_request request; - magi_request_init(&request); - if (magi_cgi(&request)) { - response(&request); - } else { - magi_response_error(&request); + magi_request_init(&request); /* Setting defaults. */ + if (magi_parse(&request)) { /* If parsing was done successful */ + response(&request); /* we need to response the request. */ + } else { /* And display error overwise: */ + magi_error_response(request.error); } - magi_request_free(&request); + magi_request_free(&request); /* Don't forget to free everything after. */ return 0; } diff --git a/examples/echo.c b/examples/echo.c index 7cb4a3b..4f6975a 100644 --- a/examples/echo.c +++ b/examples/echo.c @@ -1,111 +1,111 @@ +/* This is useful example echoing request data in response. + */ #include void list_cookies(magi_request *r) { magi_cookies *current = r->cookies; - magi_response(r, "

"); + printf("

"); + /* Iterate through all cookies in request to show them in body: */ for (current = r->cookies; current; current = current->next) { magi_cookie *c = ¤t->item; - magi_response(r, "Cookie with name ["); - magi_response(r, c->name); + printf("Cookie with name [%s", c->name); if (c->data) { - magi_response(r, "] is ["); - magi_response(r, c->data); + printf("] is [%s", c->data); } if (c->domain) { - magi_response(r, "] for domain ["); - magi_response(r, c->domain); + printf("] for domain [%s", c->domain); } if (c->path) { - magi_response(r, "] for path ["); - magi_response(r, c->path); + printf("] for path [%s", c->path); } - magi_response(r, "]
"); + printf("]
"); } - magi_response(r, "

"); + printf("

"); } -void list_params(magi_request *r, magi_params *current) +void list_params(magi_params *current) { - magi_response(r, "

"); + printf("

"); + /* Iterate through specified params to show them in body: */ for (; current; current = current->next) { magi_param *p = ¤t->item; - magi_response_format(r, "[%s] is [%s]
", p->name, p->data); + printf("[%s] is [%s]
", p->name, p->data); } - magi_response(r, "

"); + printf("

"); } void list_files(magi_request *r) { magi_files *current; - magi_response(r, "

"); + printf("

"); + /* Iterate through all field files in request to show them in body: */ for (current = r->files; current; current = current->next) { magi_file *f = ¤t->item; - magi_response_format(r, "[%s] was [%s] on clientside
", - f->field, f->filename); + printf("[%s] was [%s] on clientside
", f->field, f->filename); } - magi_response(r, "

"); + printf("

"); } void show_meta(magi_request *r) { - magi_response(r, "

I was called "); + printf("

I was called "); if (r->is_secure) { - magi_response(r, "securely "); + printf("securely "); } - magi_response_format(r, "with method [%s", r->method); + printf("with method [%s", r->method); if (r->host) { - magi_response_format(r, "] on server [%s", r->host); + printf("] on server [%s", r->host); } if (r->script) { - magi_response_format(r, "] being script on [%s", r->script); + printf("] being script on [%s", r->script); } if (r->path) { - magi_response_format(r, "] with requested path [%s", r->path); + printf("] with requested path [%s", r->path); } - magi_response(r, "]

"); + printf("]

"); } void response(magi_request *r) { - magi_response(r, - "" - "" - "Echo" - ""); + magi_response_default(); /* Pass default headers and send body: */ + printf("" + "" + "Echo" + ""); - magi_response(r, "

Echo CGI Script

"); + printf("

Echo CGI Script

"); show_meta(r); - magi_response(r, "

Cookies:

"); + printf("

Cookies:

"); list_cookies(r); - magi_response(r, "

Parameters:

"); - list_params(r, r->meta); + printf("

Parameters:

"); + list_params(r->meta); - magi_response(r, "

URL Parameters:

"); - list_params(r, r->head); + printf("

URL Parameters:

"); + list_params(r->head); - magi_response(r, "

Body Parameters:

"); - list_params(r, r->body); + printf("

Body Parameters:

"); + list_params(r->body); - magi_response(r, "

Files:

"); + printf("

Files:

"); list_files(r); - magi_response(r, ""); + printf(""); } int main() { magi_request request; - magi_request_init(&request); - if (magi_cgi(&request)) { - response(&request); - } else { - magi_response_error(&request); + magi_request_init(&request); /* Setting defaults. */ + if (magi_parse(&request)) { /* If parsing was done successful */ + response(&request); /* we need to response the request. */ + } else { /* And display error overwise: */ + magi_error_response(request.error); } - magi_request_free(&request); + magi_request_free(&request); /* Don't forget to free everything after. */ return 0; } diff --git a/examples/fastcgi.c b/examples/fastcgi.c deleted file mode 100644 index 16668f3..0000000 --- a/examples/fastcgi.c +++ /dev/null @@ -1,33 +0,0 @@ -#include - - -void response(magi_request *r) -{ - magi_response(r, - "" - "" - "Fast CGI" - "Hi!" - ""); -} - -int main() -{ - magi_session session; - magi_request request; - magi_request_init(&request); - magi_session_init(&session); - magi_session_unix(&session, "unix.sock"); - while (magi_fastcgi(&session, &request)) { - if (request.error) { - magi_response_error(&request); - } else { - response(&request); - } - magi_request_free(&request); - } - magi_request_free(&request); - magi_session_free(&session); - return 0; -} diff --git a/examples/upload.c b/examples/upload.c index bf8eed1..d07db16 100644 --- a/examples/upload.c +++ b/examples/upload.c @@ -1,51 +1,59 @@ +/* This is basic example of handling file uploading. + * It is using magi_loadfiles callback to load file temporarily and change its + * name into the one specified by user. + */ #include #include -void response(magi_request *r) +void upload(magi_request *r) { char *name = magi_request_param(r, "name"); const magi_file *data = magi_request_file(r, "data"); - magi_response(r, - "" - "" - "Upload File" - ""); - if (name && data) { - rename("data", name); - magi_response(r, "

Uploaded!

"); + if (name && data) { /* If file to load and its name are in the request: */ + rename("data", name); /* Rename loaded file to designated name. */ + printf("

Uploaded!

"); /* And display success message. */ } - magi_response(r, - "
" - "" - "" - "" - "
" - "" - ""); +} + +void response(magi_request *r) +{ + magi_response_default(); /* Pass default headers and send body: */ + printf("" + "" + "Upload File" + ""); + upload(r); + printf("
" + "" + "" + "" + "
" + ""); } void get(magi_request *r) { magi_loadfiles rules = { 0, 0 }; + /* Setup callback to load file from "data" field into file "data": */ magi_loadfiles_add(&rules, "data", "data", 0); - magi_loadfiles_set(r, &rules); - magi_cgi(r); - magi_loadfiles_free(&rules); + magi_loadfiles_set(r, &rules); /* Setup request to use the callback. */ + magi_parse(r); + magi_loadfiles_free(&rules); /* Free data of callback. */ } int main() { magi_request request; magi_request_init(&request); - get(&request); - if (request.error) { - magi_response_error(&request); + get(&request); /* Parse request. */ + if (request.error) { /* If error occurred display error message: */ + magi_error_response(request.error); } else { - response(&request); + response(&request); /* Overwise response request. */ } - magi_request_free(&request); + magi_request_free(&request); /* Don't forget to free everything after. */ return 0; } diff --git a/include/magi.h b/include/magi.h index d8e1245..e89a322 100644 --- a/include/magi.h +++ b/include/magi.h @@ -3,13 +3,12 @@ /* All headers are included here. * You can include this, if you don't want to think about headers. */ -#include "magi/cgi.h" #include "magi/cookie.h" #include "magi/error.h" -#include "magi/fastcgi.h" #include "magi/file.h" #include "magi/loadfiles.h" #include "magi/param.h" +#include "magi/parse.h" #include "magi/request.h" #include "magi/response.h" #include "magi/urlenc.h" diff --git a/include/magi/cgi.h b/include/magi/cgi.h deleted file mode 100644 index 0de637e..0000000 --- a/include/magi/cgi.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef MAGI_INCLUDED_CGI -#define MAGI_INCLUDED_CGI -/* Common Gateway Interface implementation - */ -#include "request.h" - - -/* Analyses non-post part of request from environment. True if ok. */ -int magi_cgi_head(magi_request *request); - -/* Complete request with post body from standard input. True if ok. */ -int magi_cgi_body(magi_request *request); - -/* Shortcut for analysing both head and body of request. True if ok. */ -int magi_cgi(magi_request *request); - - -#endif diff --git a/include/magi/error.h b/include/magi/error.h index e928ed2..19538ae 100644 --- a/include/magi/error.h +++ b/include/magi/error.h @@ -1,6 +1,7 @@ #ifndef MAGI_INCLUDED_ERROR #define MAGI_INCLUDED_ERROR /* Error codes and messages + * You can output default error message via magi_error_response. */ @@ -20,4 +21,7 @@ typedef enum magi_error { const char *magi_error_message(magi_error error); +void magi_error_response(magi_error error); + + #endif diff --git a/include/magi/fastcgi.h b/include/magi/fastcgi.h deleted file mode 100644 index 3d91b7b..0000000 --- a/include/magi/fastcgi.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef MAGI_INCLUDED_FASTCGI -#define MAGI_INCLUDED_FASTCGI -/* Fast CGI implementation - */ -#include "session.h" -#include "request.h" - - -int magi_fastcgi_head(magi_session *s, magi_request *r); -int magi_fastcgi_body(magi_session *s, magi_request *r); -int magi_fastcgi(magi_session *s, magi_request *r); - - -#endif diff --git a/include/magi/loadfiles.h b/include/magi/loadfiles.h index bf3421c..d6cdaeb 100644 --- a/include/magi/loadfiles.h +++ b/include/magi/loadfiles.h @@ -6,8 +6,6 @@ * via magi_loadfiles_add, specifying which file-parameter to load into which * path, and what are size limitations for it. When table is complete, setup * your request to use this callback with magi_loadfiles_set. - * - * This module is optional. */ #include "request.h" diff --git a/include/magi/parse.h b/include/magi/parse.h new file mode 100644 index 0000000..a71018a --- /dev/null +++ b/include/magi/parse.h @@ -0,0 +1,18 @@ +#ifndef MAGI_INCLUDED_PARSE +#define MAGI_INCLUDED_PARSE +/* Common Gateway Interface request parsing implementation + */ +#include "request.h" + + +/* Analyses non-post part of request from environment. True if ok. */ +int magi_parse_head(magi_request *request); + +/* Complete request with post body from standard input. True if ok. */ +int magi_parse_body(magi_request *request); + +/* Shortcut for analysing both head and body of request. True if ok. */ +int magi_parse(magi_request *request); + + +#endif diff --git a/include/magi/request.h b/include/magi/request.h index 7245feb..32d31b5 100644 --- a/include/magi/request.h +++ b/include/magi/request.h @@ -2,13 +2,13 @@ #define MAGI_INCLUDED_REQUEST /* Request handler * - * Can be created via magi_{gateway interface name}_head, but will have - * nullified POST-related fields (params & files). Reason is unlimited - * POST body size, with possible dependence of wanted limits from data of - * headers (e.g. session id from cookies, enabling some users to load more). - * To proceed POST use magi_{gateway interface name}_body, specifying - * settings if necessary. - * Or just use shortcut magi_{gateway interface name} to do both parts. + * Can be created via magi_parse_head, but will have nullified POST-related + * fields (params & files). Reason is unlimited POST body size, + * with possible dependence of wanted limits from data of headers + * (e.g. session id from cookies, enabling some users to load more); and also + * dependence of place to load files by callback from head data. + * To proceed POST use magi_parse_body, specifying settings if necessary. + * Or just use shortcut magi_parse to do both parts. */ #include "cookie.h" #include "error.h" @@ -44,12 +44,6 @@ typedef struct magi_request { magi_file_callback callback; /* Callback to actually load files. */ magi_request_limits limits; - - /* Implementation of response for this request. - * Especially helpful in case of FastCGI, - * when you can handle many requests simultaniously, - * and need to know how to response each of them. */ - struct magi_response_implementation *response; } magi_request; diff --git a/include/magi/response.h b/include/magi/response.h index 11acf75..e67b279 100644 --- a/include/magi/response.h +++ b/include/magi/response.h @@ -1,66 +1,41 @@ #ifndef MAGI_INCLUDED_RESPONSE #define MAGI_INCLUDED_RESPONSE -/* General response functionality for magi_request. +/* General CGI headers response functionality. * - * There are two parts of response, namely header and body. - * You can directly dive into filling the body, since default headers are set. - * Defult content-type is HTML, status is 200 (OK). - * - * Use body related functions only after dealing with headers. - * (Since storing possibly large body in memory is a bad idea, - * all headers should be sent before anything from the body.) + * There are two parts of response, namely head and body. + * You need to output head first, it is done with magi_response_free or with + * magi_response_default. Default content-type is HTML, status is 200 (Ok). + * Body can be outputed after head into stdout. */ -#include "request.h" +#include "cookie.h" +#include "param.h" #include #include -/* * * TODO: Comments * * */ -typedef void (*magi_response_method_head)(void *ud, magi_param *header); -typedef void (*magi_response_method_start_body)(void *ud); -typedef void (*magi_response_method_body)(void *ud, const char *data, int len); -typedef void (*magi_response_method_fmt)(void *ud, const char *f, va_list a); -typedef void (*magi_response_method_file)(void *ud, FILE *file); -typedef void (*magi_response_method_close)(void *ud); - -typedef struct magi_response_methods { - magi_response_method_head head; - magi_response_method_start_body start_body; - magi_response_method_body body; - magi_response_method_fmt format; - magi_response_method_file file; - magi_response_method_close close; -} magi_response_methods; - -typedef struct magi_response_implementation { - const magi_response_methods *methods; - void *userdata; +typedef struct magi_response { magi_params *head_response; magi_params *head_general; magi_params *head_entity; - int head_done; -} magi_response_implementation; - +} magi_response; -void magi_response_status(magi_request *r, int code, const char *description); -void magi_response_cookie(magi_request *r, const char *name, const char *data); -void magi_response_cookie_complex(magi_request *r, magi_cookie *c); -void magi_response_cookie_discard(magi_request *r, const char *name); +void magi_response_init(magi_response *r); +void magi_response_free(magi_response *r); -void magi_response_header(magi_request *r, const char *name, const char *data); +void magi_response_default(); -void magi_response_content_length(magi_request *r, int length); -void magi_response_content_type(magi_request *r, const char *type); -void magi_response_head(magi_request *r); +void magi_response_status(magi_response *r, int code, const char *description); -void magi_response(magi_request *r, const char *addon); -void magi_response_format(magi_request *r, const char *format, ...); -void magi_response_file(magi_request *r, FILE *file); +void magi_response_cookie(magi_response *r, const char *n, const char *d); +void magi_response_cookie_complex(magi_response *r, magi_cookie *c); +void magi_response_cookie_discard(magi_response *r, const char *name); +void magi_response_header(magi_response *r, const char *n, const char *d); -void magi_response_error(magi_request *r); +void magi_response_content_length(magi_response *r, int length); +void magi_response_content_type(magi_response *r, const char *type); #endif diff --git a/include/magi/session.h b/include/magi/session.h deleted file mode 100644 index c1c5903..0000000 --- a/include/magi/session.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef MAGI_INCLUDED_SESSION -#define MAGI_INCLUDED_SESSION - - -typedef struct magi_session { - int socket; -} magi_session; - - -void magi_session_init(magi_session *s); -void magi_session_free(magi_session *s); - -int magi_session_inet(magi_session *s, const char *address, int port); -int magi_session_unix(magi_session *s, const char *path); - - -#endif diff --git a/include/magi/urlenc.h b/include/magi/urlenc.h index dadebe7..1b629f9 100644 --- a/include/magi/urlenc.h +++ b/include/magi/urlenc.h @@ -9,8 +9,6 @@ * RFC 3986 describes URL-encoding. Briefly it is changing every space into * plus sign and every not alpha-numerical and not @c "~-._" character into * percent sign followed by hexadecimal representation of given character. - * - * This module is optional. */ diff --git a/man/magi.3 b/man/magi.3 deleted file mode 100644 index 0c7ed1a..0000000 --- a/man/magi.3 +++ /dev/null @@ -1,7 +0,0 @@ -.TH MAGI 3 "2020-03-18" "0.1" "Magi Library" -.SH NAME -magi \- ANSI C dependency-free CGI and FastCGI library -.SH LIBRARY -library "libmagi" -.SH SYNOPSIS -.In #include diff --git a/src/cgi.c b/src/cgi.c deleted file mode 100644 index 7b0551d..0000000 --- a/src/cgi.c +++ /dev/null @@ -1,266 +0,0 @@ -#include "cgi.h" - -#include "cookie.h" -#include "cookies.h" -#include "error.h" -#include "file.h" -#include "multipart.h" -#include "param.h" -#include "request.h" -#include "response.h" -#include "tools.h" -#include "urlencoded.h" -#include -#include -#include -#include - -extern char **const environ; - - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * CGI Request - */ -static char *plain_env(char *env_name) -{ - const char *env = getenv(env_name); - if (!env) { - return 0; - } - return magi_str_create_copy(env, strlen(env)); -} - -static char *lower_env(char *env_name) -{ - char *env = plain_env(env_name); - magi_str_lowercase(env); - return env; -} - -static void cgi_http_env(magi_request *r) -{ - char **env; - int len = 0; - r->meta = 0; - for (env = environ; *env; ++env) { - magi_param meta; - char *name_end = strchr(*env, '='); - int nlen = name_end - *env; - int dlen = strlen(name_end + 1); - len += nlen + dlen; - if (len > r->limits.params_meta && r->limits.params_meta) { - r->error = magi_error_limit; - return; - } - meta.name = magi_str_create_copy(*env, nlen); - meta.data = magi_str_create_copy(name_end + 1, dlen); - magi_params_add(&r->meta, &meta); - } -} - -static void cgi_env(magi_request *r) -{ - cgi_http_env(r); - r->method = plain_env("REQUEST_METHOD"); - r->document_root = plain_env("DOCUMENT_ROOT"); - r->script = plain_env("SCRIPT_NAME"); - r->host = lower_env("HTTP_HOST"); - if (!r->host) { - r->host = lower_env("SERVER_NAME"); - } - if (getenv("SERVER_PORT")) { - r->port = atoi(getenv("SERVER_PORT")); - r->is_secure = r->port == 443; - } else { - r->port = 0; - r->is_secure = 0; - } - r->path = plain_env("PATH_INFO"); -} - -static void cgi_cookies(magi_request *r) -{ - const char *env = getenv("HTTP_COOKIE"); - if (!env || !*env) { - r->cookies = 0; - return; - } - if ((int)strlen(env) > r->limits.cookies && r->limits.cookies) { - r->error = magi_error_limit; - return; - } - magi_parse_cookies(r, env); -} - -static void cgi_input_get(magi_error *e, char **input, int max) -{ - const char *env_input = getenv("QUERY_STRING"); - if (env_input) { - int len = strlen(env_input); - if (len >= max && max) { - *e = magi_error_limit; - return; - } - *input = magi_str_create_copy(env_input, len); - } -} - -static void cgi_url(magi_request *request) -{ - char *in = 0; - cgi_input_get(&request->error, &in, request->limits.params_head); - if (!request->error) { - magi_parse_urlencoded(&request->head, in); - free(in); - } -} - -static void cgi_input_post(magi_error *e, char **input, int max) -{ - int input_len = strtoul(getenv("CONTENT_LENGTH"), 0, 10); - if (!input_len) { - *e = magi_error_length; - return; - } - if (input_len > max && max) { - *e = magi_error_limit; - return; - } - *input = magi_str_create(input_len); - if ((int)fread(*input, 1, input_len, stdin) != input_len) { - *e = magi_error_length; - return; - } -} - -static char *bound(const char *type) -{ - type = strchr(type, '='); - if (!type) { - return 0; - } - type += strspn(type, " \t") + 1; - if (*type == '"') { - ++type; - return magi_str_create_copy(type, type - strchr(type, '"')); - } - return magi_str_create_copy(type, strcspn(type, " \t")); -} - -static int next() -{ - return getchar(); -} - -static void mhead(void *any, magi_param *header) -{ - (void)any; - fputs(header->name, stdout); - fputs(": ", stdout); - fputs(header->data, stdout); - fputs("\r\n", stdout); -} - -static void mstart_body() -{ - fputs("\r\n", stdout); -} - -static void mbody(void *any, const char *data, int len) -{ - (void)any; - fwrite(data, 1, len, stdout); -} - -static void mformat(void *any, const char *format, va_list args) -{ - (void)any; - vprintf(format, args); -} - -static void mfile(void *any, FILE *file) -{ - (void)any; - while (!feof(file)) { - char buf[64]; - int len = fread(buf, 1, 64, file); - fwrite(buf, 1, len, stdout); - } -} - -static void mclose() {} - -static void setup_response(magi_request *r) -{ - static const magi_response_methods m = { - mhead, - mstart_body, - mbody, - mformat, - mfile, - mclose - }; - r->response = malloc(sizeof(*r->response)); - r->response->methods = &m; - r->response->userdata = 0; - r->response->head_response = 0; - r->response->head_general = 0; - r->response->head_entity = 0; - r->response->head_done = 0; - magi_response_content_type(r, "text/html"); - magi_response_status(r, 200, "OK"); -} - -/* Interfacial CGI Request Handling */ -int magi_cgi_head(magi_request *request) -{ - request->cookies = 0; - request->files = 0; - request->meta = 0; - request->head = 0; - request->body = 0; - request->error = 0; - cgi_env(request); - cgi_cookies(request); - cgi_url(request); - setup_response(request); - return !request->error; -} - -int magi_cgi_body(magi_request *request) -{ - magi_error *e = &request->error; - request->error = magi_error_none; - if (request->method && !strcmp(request->method, "POST")) { - const char *t = getenv("CONTENT_TYPE"); - if (!t) { - *e = magi_error_notype; - return 0; - } - if (!strncmp(t, "multipart/form-data", 19)) { - char *boundary = bound(t); - if (boundary && *boundary) { - magi_parse_multipart(request, boundary, next, 0); - } else { - *e = magi_error_nobound; - } - free(boundary); - } else if (!strcmp(t, "application/x-www-form-urlencoded")) { - char *in = 0; - cgi_input_post(e, &in, request->limits.params_body); - if (!*e) { - magi_parse_urlencoded(&request->body, in); - } - free(in); - } else { - *e = magi_error_unknown; - return 0; - } - } - return !request->error; -} - -int magi_cgi(magi_request *request) -{ - return magi_cgi_head(request) && magi_cgi_body(request); -} diff --git a/src/error.c b/src/error.c index a1c3153..eb16eb6 100644 --- a/src/error.c +++ b/src/error.c @@ -1,9 +1,11 @@ #include "error.h" +#include + const char *magi_error_message(magi_error error) { - const char *const messages[] = { + static const char *const messages[] = { 0, /* magi_error_none */ "No boundary for multipart.", /* magi_error_nobound */ "Content-Type is unknown.", /* magi_error_unknown */ @@ -17,3 +19,18 @@ const char *magi_error_message(magi_error error) }; return messages[error]; } + +void magi_error_response(magi_error error) +{ + fputs("Status: 400 Bad Request\r\n" + "Content-Type: text/html\r\n\r\n" + "" + "" + "400 Bad Request" + "" + "

400 Bad Request

" + "

", stdout); + fputs(magi_error_message(error), stdout); + fputs("

", stdout); +} diff --git a/src/fastcgi.c b/src/fastcgi.c deleted file mode 100644 index 8959c14..0000000 --- a/src/fastcgi.c +++ /dev/null @@ -1,23 +0,0 @@ -#include "fastcgi.h" - - -int magi_fastcgi_head(magi_session *s, magi_request *r) -{ - (void)s; - (void)r; - return 1; -} - - -int magi_fastcgi_body(magi_session *s, magi_request *r) -{ - (void)s; - (void)r; - return 1; -} - - -int magi_fastcgi(magi_session *s, magi_request *r) -{ - return magi_fastcgi_head(s, r) && magi_fastcgi_body(s, r); -} diff --git a/src/parse.c b/src/parse.c new file mode 100644 index 0000000..58ae001 --- /dev/null +++ b/src/parse.c @@ -0,0 +1,206 @@ +#include "parse.h" + +#include "cookie.h" +#include "cookies.h" +#include "error.h" +#include "file.h" +#include "multipart.h" +#include "param.h" +#include "request.h" +#include "response.h" +#include "tools.h" +#include "urlencoded.h" +#include +#include +#include +#include + +extern char **const environ; + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * CGI Request + */ +static char *plain_env(char *env_name) +{ + const char *env = getenv(env_name); + if (!env) { + return 0; + } + return magi_str_create_copy(env, strlen(env)); +} + +static char *lower_env(char *env_name) +{ + char *env = plain_env(env_name); + magi_str_lowercase(env); + return env; +} + +static void cgi_http_env(magi_request *r) +{ + char **env; + int len = 0; + r->meta = 0; + for (env = environ; *env; ++env) { + magi_param meta; + char *name_end = strchr(*env, '='); + int nlen = name_end - *env; + int dlen = strlen(name_end + 1); + len += nlen + dlen; + if (len > r->limits.params_meta && r->limits.params_meta) { + r->error = magi_error_limit; + return; + } + meta.name = magi_str_create_copy(*env, nlen); + meta.data = magi_str_create_copy(name_end + 1, dlen); + magi_params_add(&r->meta, &meta); + } +} + +static void cgi_env(magi_request *r) +{ + cgi_http_env(r); + r->method = plain_env("REQUEST_METHOD"); + r->document_root = plain_env("DOCUMENT_ROOT"); + r->script = plain_env("SCRIPT_NAME"); + r->host = lower_env("HTTP_HOST"); + if (!r->host) { + r->host = lower_env("SERVER_NAME"); + } + if (getenv("SERVER_PORT")) { + r->port = atoi(getenv("SERVER_PORT")); + r->is_secure = r->port == 443; + } else { + r->port = 0; + r->is_secure = 0; + } + r->path = plain_env("PATH_INFO"); +} + +static void cgi_cookies(magi_request *r) +{ + const char *env = getenv("HTTP_COOKIE"); + if (!env || !*env) { + r->cookies = 0; + return; + } + if ((int)strlen(env) > r->limits.cookies && r->limits.cookies) { + r->error = magi_error_limit; + return; + } + magi_parse_cookies(r, env); +} + +static void cgi_input_get(magi_error *e, char **input, int max) +{ + const char *env_input = getenv("QUERY_STRING"); + if (env_input) { + int len = strlen(env_input); + if (len >= max && max) { + *e = magi_error_limit; + return; + } + *input = magi_str_create_copy(env_input, len); + } +} + +static void cgi_url(magi_request *request) +{ + char *in = 0; + cgi_input_get(&request->error, &in, request->limits.params_head); + if (!request->error) { + magi_parse_urlencoded(&request->head, in); + free(in); + } +} + +static void cgi_input_post(magi_error *e, char **input, int max) +{ + int input_len = strtoul(getenv("CONTENT_LENGTH"), 0, 10); + if (!input_len) { + *e = magi_error_length; + return; + } + if (input_len > max && max) { + *e = magi_error_limit; + return; + } + *input = magi_str_create(input_len); + if ((int)fread(*input, 1, input_len, stdin) != input_len) { + *e = magi_error_length; + return; + } +} + +static char *bound(const char *type) +{ + type = strchr(type, '='); + if (!type) { + return 0; + } + type += strspn(type, " \t") + 1; + if (*type == '"') { + ++type; + return magi_str_create_copy(type, type - strchr(type, '"')); + } + return magi_str_create_copy(type, strcspn(type, " \t")); +} + +static int next() +{ + return getchar(); +} + +/* Interfacial CGI Request Handling */ +int magi_parse_head(magi_request *request) +{ + request->cookies = 0; + request->files = 0; + request->meta = 0; + request->head = 0; + request->body = 0; + request->error = 0; + cgi_env(request); + cgi_cookies(request); + cgi_url(request); + return !request->error; +} + +int magi_parse_body(magi_request *request) +{ + magi_error *e = &request->error; + request->error = magi_error_none; + if (request->method && !strcmp(request->method, "POST")) { + const char *t = getenv("CONTENT_TYPE"); + if (!t) { + *e = magi_error_notype; + return 0; + } + if (!strncmp(t, "multipart/form-data", 19)) { + char *boundary = bound(t); + if (boundary && *boundary) { + magi_parse_multipart(request, boundary, next, 0); + } else { + *e = magi_error_nobound; + } + free(boundary); + } else if (!strcmp(t, "application/x-www-form-urlencoded")) { + char *in = 0; + cgi_input_post(e, &in, request->limits.params_body); + if (!*e) { + magi_parse_urlencoded(&request->body, in); + } + free(in); + } else { + *e = magi_error_unknown; + return 0; + } + } + return !request->error; +} + +int magi_parse(magi_request *request) +{ + return magi_parse_head(request) && magi_parse_body(request); +} diff --git a/src/request.c b/src/request.c index d80bf20..bf8fc1d 100644 --- a/src/request.c +++ b/src/request.c @@ -1,6 +1,5 @@ #include "request.h" -#include "response.h" #include @@ -30,7 +29,6 @@ static void request_free(magi_request *request) free(request->host); free(request->script); free(request->path); - free(request->response); } static void request_annul(magi_request *request) @@ -44,7 +42,6 @@ static void request_annul(magi_request *request) request->host = 0; request->script = 0; request->path = 0; - request->response = 0; } void magi_request_free(magi_request *request) @@ -55,14 +52,6 @@ void magi_request_free(magi_request *request) magi_params_free(request->head); magi_params_free(request->body); magi_files_free(request->files); - request->response->methods->close(request->response->userdata); - free(request->response->userdata); - magi_params_free(request->response->head_response); - magi_params_free(request->response->head_general); - magi_params_free(request->response->head_entity); - free(request->response->head_response); - free(request->response->head_general); - free(request->response->head_entity); request_free(request); request_annul(request); } diff --git a/src/response.c b/src/response.c index dc36bb0..c5b7575 100644 --- a/src/response.c +++ b/src/response.c @@ -6,11 +6,55 @@ #include -void magi_response_status(magi_request *r, int code, const char *description) +void magi_response_init(magi_response *r) +{ + r->head_response = 0; + r->head_general = 0; + r->head_entity = 0; + magi_response_status(r, 200, "OK"); + magi_response_content_type(r, "text/html"); +} + +static void response_headers(magi_params *p) +{ + for (; p; p = p->next) { + fputs(p->item.name, stdout); + fputs(": ", stdout); + fputs(p->item.data, stdout); + fputs("\r\n", stdout); + } +} + +void magi_response_free(magi_response *r) +{ + response_headers(r->head_response); + response_headers(r->head_general); + response_headers(r->head_entity); + fputs("\r\n", stdout); + magi_params_free(r->head_response); + magi_params_free(r->head_general); + magi_params_free(r->head_entity); + free(r->head_response); + free(r->head_general); + free(r->head_entity); + r->head_response = 0; + r->head_general = 0; + r->head_entity = 0; +} + + +void magi_response_default() +{ + fputs("Status: 200 Ok\r\n" + "Content-Type: text/html\r\n\r\n", stdout); +} + + +void magi_response_status(magi_response *r, int code, const char *description) { int dlen; magi_param addon; - if (r->response->head_done || code <= 99 || 600 <= code) { + if (code <= 99 || 600 <= code) { return; } dlen = strlen(description); @@ -21,15 +65,15 @@ void magi_response_status(magi_request *r, int code, const char *description) addon.data[2] = '0' + code % 10; addon.data[3] = ' '; memcpy(addon.data + 4, description, dlen + 1); - magi_params_set(&r->response->head_response, &addon); + magi_params_set(&r->head_response, &addon); } -void magi_response_cookie(magi_request *r, const char *name, const char *data) +void magi_response_cookie(magi_response *r, const char *name, const char *data) { magi_param addon; int nlen; int dlen; - if (r->response->head_done || !name || !data) { + if (!name || !data) { return; } nlen = strlen(name); @@ -39,10 +83,10 @@ void magi_response_cookie(magi_request *r, const char *name, const char *data) memcpy(addon.data, name, nlen); addon.data[nlen] = '='; memcpy(addon.data + nlen + 1, data, dlen + 1); - magi_params_add(&r->response->head_general, &addon); + magi_params_add(&r->head_general, &addon); } -void magi_response_cookie_complex(magi_request *r, magi_cookie *c) +void magi_response_cookie_complex(magi_response *r, magi_cookie *c) { magi_param addon; char *pointer; @@ -50,7 +94,7 @@ void magi_response_cookie_complex(magi_request *r, magi_cookie *c) const int cdsize = 9; const int cpsize = 7; const int cmsize = 10; - if (r->response->head_done || !c->name) { + if (!c->name) { return; } nlen = strlen(c->name); @@ -83,14 +127,14 @@ void magi_response_cookie_complex(magi_request *r, magi_cookie *c) memcpy(pointer, "; Max-Age=", cmsize); memcpy(pointer + cmsize, c->max_age, msize - cmsize); } - magi_params_add(&r->response->head_general, &addon); + magi_params_add(&r->head_general, &addon); } -void magi_response_cookie_discard(magi_request *r, const char *name) +void magi_response_cookie_discard(magi_response *r, const char *name) { magi_param addon; int len; - if (r->response->head_done || !name) { + if (!name) { return; } len = strlen(name); @@ -98,29 +142,22 @@ void magi_response_cookie_discard(magi_request *r, const char *name) addon.data = malloc(len + 13); memcpy(addon.data, name, len); memcpy(addon.data + len, "=; Max-Age=1", 13); - magi_params_add(&r->response->head_general, &addon); + magi_params_add(&r->head_general, &addon); } -void magi_response_header(magi_request *r, const char *name, const char *data) +void magi_response_header(magi_response *r, const char *name, const char *data) { magi_param addon; - if (r->response->head_done) { - return; - } addon.name = magi_str_create_copy(name, strlen(name)); addon.data = magi_str_create_copy(data, strlen(data)); - magi_params_add(&r->response->head_general, &addon); + magi_params_add(&r->head_general, &addon); } -void magi_response_content_length(magi_request *r, int length) +void magi_response_content_length(magi_response *r, int length) { magi_param addon; - int len; - if (r->response->head_done) { - return; - } + int len = 1; addon.name = magi_str_create_copy("Content-Length", 14); - len = 1; addon.data = malloc(len + 1); *addon.data = '0' + length % 10; while (length /= 10) { @@ -129,75 +166,13 @@ void magi_response_content_length(magi_request *r, int length) ++len; } addon.data[len] = 0; - magi_params_set(&r->response->head_entity, &addon); + magi_params_set(&r->head_entity, &addon); } -void magi_response_content_type(magi_request *r, const char *type) +void magi_response_content_type(magi_response *r, const char *type) { magi_param addon; - if (r->response->head_done) { - return; - } addon.name = magi_str_create_copy("Content-Type", 12); addon.data = magi_str_create_copy(type, strlen(type)); - magi_params_set(&r->response->head_entity, &addon); -} - -static void response_headers(magi_response_implementation *r, magi_params *p) -{ - for (; p; p = p->next) { - r->methods->head(r->userdata, &p->item); - } -} - -void magi_response_head(magi_request *r) -{ - if (r->response->head_done) { - return; - } - response_headers(r->response, r->response->head_response); - response_headers(r->response, r->response->head_general); - response_headers(r->response, r->response->head_entity); - r->response->methods->start_body(r->response->userdata); - r->response->head_done = 1; -} - -void magi_response(magi_request *r, const char *addon) -{ - magi_response_head(r); - if (!addon) { - return; - } - r->response->methods->body(r->response->userdata, addon, strlen(addon)); -} - -void magi_response_format(magi_request *r, const char *format, ...) -{ - va_list args; - magi_response_head(r); - va_start(args, format); - r->response->methods->format(r->response->userdata, format, args); - va_end(args); -} - -void magi_response_file(magi_request *r, FILE *file) -{ - magi_response_head(r); - r->response->methods->file(r->response->userdata, file); -} - - -void magi_response_error(magi_request *r) -{ - magi_response_status(r, 400, "Bad Request"); - magi_response(r, - "" - "" - "400 Bad Request" - "" - "

400 Bad Request

" - "

"); - magi_response(r, magi_error_message(r->error)); - magi_response(r, "

"); + magi_params_set(&r->head_entity, &addon); } diff --git a/src/session.c b/src/session.c deleted file mode 100644 index aec0e6d..0000000 --- a/src/session.c +++ /dev/null @@ -1,58 +0,0 @@ -#include "session.h" - -#include -#include -#include -#include - - -enum { listen_backlog = 64 }; - -void magi_session_init(magi_session *s) -{ - s->socket = 0; -} - -void magi_session_free(magi_session *s) -{ - if (s->socket) { - shutdown(s->socket, SHUT_RDWR); - s->socket = 0; - } -} - - -int magi_session_inet(magi_session *s, const char *address, int port) -{ - struct sockaddr_in addr; - if (s->socket) { - return 0; - } - s->socket = socket(AF_INET, SOCK_STREAM, 0); - if (s->socket == -1) { - s->socket = 0; - return 0; - } - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - addr.sin_addr.s_addr = inet_addr(address); - return !bind(s->socket, (struct sockaddr *)&addr, sizeof(addr)) && - !listen(s->socket, listen_backlog); -} - -int magi_session_unix(magi_session *s, const char *path) -{ - struct sockaddr_un addr; - if (s->socket) { - return 0; - } - s->socket = socket(AF_UNIX, SOCK_STREAM, 0); - if (s->socket == -1) { - s->socket = 0; - return 0; - } - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); - return !bind(s->socket, (struct sockaddr *)&addr, sizeof(addr)) && - !listen(s->socket, listen_backlog); -} -- cgit v1.2.3