.TH MAGI 3 2021-01-07 v0.0.1 "Magi Library Manual" .SH NAME .B magi \- Common Gateway Interface (CGI) library .SH SYNOPSIS .B #include .SH DESCRIPTION The .B magi library gives the user a way to interact with the web server through CGI as described in .IR "RFC 3875" . The library is free and open source software. It is written in ANSI C without any dependencies except C standard library. .SH GETTING STARTED Once you have obtained the sources (from .I http://git.veresov.xyz/aversey/magi or other way) run .P .RS .nf make .fi .RE .P in order to build the library. The results will be in the .I build directory. .P To build the examples run: .P .RS .nf make examples .fi .RE .P Add any single-file C program using .B magi into the .I examples directory in order to build it with the same command. .P To give you a .B magi try here is the minimal program: .P .RS .nf #include #include int main() { struct magi_request request; magi_request_init(&request); if (magi_parse(&request)) { magi_response_default(); puts("Hi!Hi!"); } else { magi_error_response(request.error); } magi_request_free(&request); return 0; } .fi .RE .P You can install .B magi system-wide once you have got root privileges by running: .P .RS .nf make install .fi .RE .P Once you have installed the library system-wide all you need to do to compile you programs is to specify .I -lmagi flag. So to compile .I some.c using this library you can run: .P .RS .nf gcc some.c -lmagi .fi .RE .P Default installation locations are .I /usr/include for the headers, .I /usr/lib for library binaries and .I /usr/share/man for this manual. .SH OVERVIEW Main task of the .B magi library is to analyze CGI request and represent it to the user in .B magi_request data structure. CGI request is passed in environment variables and standard input. You can access environment directly even then using magi, however reading from standard input will cause a mess. .P The library also provides response functionality with .B magi_response and its methods. Since body can be everything and only headers have specific CGI-related structure, .B magi provides response functionality only for them. Body can be outputted directly into standard output with any method you want after forming and sending CGI headers via .BR magi_response . .SH REQUEST Request structure initialization is done with .BR magi_request_init (). Don't forget to free everything in it then the work is done via .BR magi_request_free (). .P .RS .nf struct magi_request request; magi_request_init(&request); /* do your work with request */ magi_request_free(&request); .fi .RE .P Obtaining of request is done in two parts, i.e. head via .BR magi_parse_head () and body via .BR magi_parse_body (). Analysis of head will fill everything in .B magi_request structure, except .I body and .I files fields. Such separation has place since body processing can depend on data from head, e.g. in case of loading files, if file size is defined by user group. If analysis succeeded they will return 1, otherwise 0 as error mark. More distinct error code will be placed in .IR request.error . .P .RS .nf if (!magi_parse_head(&request)) { /* handle error and exit */ } /* configure body processing using head data */ if (magi_parse_body(&request)) { /* send response using request data */ } else { /* handle error */ } .fi .RE .P If your case is not as advanced, you can use shortcut .BR magi_parse () to analyze both head and body. .BR magi_parse (&request) is literally .BR magi_parse_head (&request) && .BR magi_parse_body (&request). .P .RS .nf if (magi_parse(&request)) { /* send response using request data */ } else { /* handle error */ } .fi .RE .SS General information in request Request method is case-sensitive ("GET", "POST", etc.) and stored in .I method field. .P Server's document root (e.g. /var/www/htdocs) is in .I document_root field. It is useful in case of processing documents on server, e.g. if you are building CMS. .P If request was done over HTTPS .I is_secure field will be set as 1, otherwise as zero (actually it checks port to be 443, which is standard HTTPS port). .P You can restore URL by following formula: .P .RS .nf http[s]://{host}:{port}{script}{path}[?...] .fi .RE .P there .I host (e.g. "example.com"), .I port (usually 80 or 443), .I script (e.g. "/cgi-bin/script") and .I path (e.g. "/login") are fields. All that values are lowercased, since they are case-insensitive. .SS Form fields values in request Parameters passed in URL after question mark (e.g. "?q=example&color=red") are stored in linked list of key-values .RB ( magi_params ) field .IR head , while HTML form fields values passed in body (except for files, described lower) are stored in .IR body . To get value of parameter by its key use .BR magi_request_param (). This function priorities body params, so if you need specifically parameter from URL use .B magi_request_urlparam (). If no parameter was passed with given name, these functions will return null. .P For example, if we build search engine and want to access query passed in form field with name .IR q , we must call: .P .RS .nf const char *query = magi_request_param(&request, "q"); if (query) { /* calculate and show result */ } else { /* show welcoming page */ } .fi .RE .P Note what values returned by .BR magi_request_urlparam () are decoded from URL encoding, so you don't need to deal with all that percents and pluses. Result of .BR magi_request_param () is also raw data. .SS Cookies in request Same situation with cookies, stored in .I cookies field, and accessible via .BR magi_request_cookie (). .P For example, we can check session ID of user, stored in cookie "sid": .P .RS .nf struct awesome_session session; const char *sid = magi_request_cookie(&request, "sid"); if (!sid) { /* render response for unauthorized user */ } else if (session_exists(sid)) { load_session(&session, sid); /* render response for this user */ } else { /* show error page of invalid session */ } .fi .RE .P While for cookies as described in .I RFC 6265 it's all you need, your server can use Cookie2, as described in .IR "RFC 2965" . In both cases .BR magi_request_cookie () will return the most accurate cookie for this request, however Cookie2 allows more complex behavior, setting and receiving different host and path for cookies. You able to receive all information about cookie using .BR magi_request_cookie_complex (), which returns pointer to .B magi_cookie structure. If no cookie is found, result is null. It has fields .IR name , .IR data , .I path (without '/' at the end), .I domain (with dot at the beginning) and .I max_age (the last one is used only in response). .SS Files in request While ordinary parameters are not very big and don't need special processing, files can be huge and need special processing. Due to their size, files shouldn't be loaded into memory in general. The .B magi library allows you to setup your own callback for file loading, or use predefined one, .BR magi_loadfiles . .P You can load file from field .I to_load into .I ./uploaded with limits on its size of 1MB as following: .P .RS .nf struct magi_loadfiles cb; magi_loadfiles_init(&cb); magi_loadfiles_add(&cb, "to_load", "./uploaded", 1024 * 1024); magi_loadfiles_set(&request, &cb); magi_parse_body(&request); magi_loadfiles_free(&rules); /* Use file ./uploaded */ .fi .RE .P There .I request is initialized .B magi_request with parsed head and not parsed body. .P To access information about file, use .B magi_request_file (). It will get your .B magi_file structure for file loaded from field with passed name. This structure have file name on user host in .I filename field. Other parameters (as Content-Type) are in .I params field, accessible with .BR magi_file_param (). .P For example, we can get user feedback, returning Content-Type of loaded file: .P .RS .nf struct magi_file *loaded = magi_request_file(&request, "to_load"); if (loaded) { char *type = magi_file_param(loaded, "Content-Type"); if (type_to_response) { /* Report the user that file of 'type' was loaded. */ } else { /* Report the user that file was loaded without specified Content-Type. */ } } else { /* Report the user that file wasn't loaded. */ } .SS File processing callback In some cases .B magi_loadfiles can be not enough. Then you can specify your own .BR magi_file_callback . The .I act field contains the callback function itself. The .I userdata field has type .I "void *" allowing you to exchange state across different calls of callback. The .I addon_max field specify how much bytes can be passed to your callback with one call. .P Callback function has type .BR magi_file_callback_act , which is function returning void, with .IR userdata , .I newfile flag, .I file .B magi_file structure, .I addon and .IR addon_len . .P Files are passed sequentially addon by addon. At the file end callback will be called with .I addon and .I addon_len as nulls. If current .I addon is first in current .I file .I newfile flag will be set up. .P You can load file from field 'file' into current directory with name, as specified by .I filename field as: .P .RS .nf static void cb(void *userdata, int newfile, const struct magi_file *file, const char *addon, int addon_len) { static FILE *f = 0; if (!strcmp("file", file->field)) { if (newfile) { f = fopen(file->filename, "wb"); } fwrite(addon, 1, addon_len, f); if (!addon) { fclose(f); } } } .fi .RE .P Set it as callback for processing files for initialized .I request with parsed head and not parsed body, and then parse the body: .P .RS .nf request.callback.act = cb; magi_parse_body(&request); /* Now file is loaded into filename */ .fi .RE .SH RESPONSE Response headers are formed with .B magi_response structure. It is initiated with .BR magi_response_init (), sent with .BR magi_response_send (), and freed with .BR magi_response_free (). The only defaults are .I text/html as Content-Type and .I 200 Ok as status. You can send them with .BR magi_response_default (). .P .BR magi_response_content_type (), .BR magi_reponse_content_length () and .BR magi_response_status () are used to change corresponding headers. Any other header can be only added, not changed with .BR magi_response_header (). Don't use it in cases above. .P For cookies use .BR magi_response_cookie () to set cookies and .BR magi_response_cookie_discard () to discard them. In case of Cookie2 use .BR magi_response_cookie_complex (). .P Lets send headers for XHTML body, setting cookie 'monster' as 'cookie': .P .RS .nf struct magi_response response; magi_response_init(&response); magi_response_content_type(&response, "application/xhtml+xml"); magi_response_cookie(&response, "monster", "cookie"); magi_response_send(&response); magi_response_free(&response); .fi .RE .SS URL encoding It is described in .IR "RFC 3986" . Briefly it is replacement of every space into plus sign and every not alpha-numerical or not one of "~-._" character into percent sign followed by hexadecimal representation of given character byte. .P The .B magi library provides functions to form url-encoded strings, which is very useful in forming response. Use .BR magi_urlenc_size () to find what the size of code will be and then call .BR magi_urlenc () for encoding itself. .P For example, lets form URL to search in DuckDuckGo for provided char .RI * query in char .RI * url . .P .RS .nf const char *prefix = "http://duckduckgo.com/?q="; const int prelen = strlen(prefix); const int urlencsize = magi_urlenc_size(query); url = malloc(prelen + urlencsize + 1); strcpy(url, prefix); magi_urlenc(query, url + prelen); url[prelen + urlencsize] = 0; .fi .RE .P Note that second argument of .BR magi_urlenc () which is encoding destination should be at least size of .BR magi_urlenc_size () of plain data, and that .BR magi_urlenc () doesn't write zero to form null-terminated string in its output. .SH ERRORS If function is returning pointer, error is only in case of null returned. If function is returning .I int as success mark it will be null only in case of error, and one otherwise. Exact .B magi_error code is in .I error field of .B magi_request structure. For other modules error codes seem to be overkill. .P You can access default error message with .BR magi_error_message () or send default error page with .BR magi_error_response (). .SH DEBUGGING To debug your CGI scripts with .I gdb you can include signal.h: .P .RS .nf #include .fi .RE .P and stop your script in the beginning with: .P .RS .nf raise(SIGSTOP); .fi .RE .P Then compile your script and place it in directory which is handled by your CGI server. Check timeout settings for CGI scripts on your server and make it big enough, since the server will kill your script by timeout. Now make request to your server and run the following in the shell as root: .P .RS .nf gdb path_to_your_executable `pgrep name_of_your_executable` .fi .RE .P This will connect .I gdb to your running CGI script. It will be paused by SIGSTOP, so you will be able to setup your breakpoints. In order to continue run: .P .RS .nf continue .fi .RE .SH COMPATIBILTY Since .B magi only uses C standard library and the C language itself it should be able to run on every environment with them, i.e. Linux, OpenBSD, FreeBSD, macOS, Windows, etc. .P The .B magi library should work well with any server supporting CGI, since it is compatible with .IR "RFC 3875" , i.e. Apache, nginx, Caddy, etc. .P The library is compatible with C++, since it avoid using typedefs for structs. However it doesn't have 'extern "C"' in the headers, so you need to wrap your includes with it manually or use .B #include which is a shortcut for include of .I magi.h wrapped in 'extern "C"' construct. .SH AUTHORS AND LICENSE Copyright 2019-2021 .B Aleksey Veresov .RI < aleksey@veresov.pro > .P This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. .P Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.