.\" Copyright (c) 2010-2016 Hypertriton, Inc. .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR .\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED .\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, .\" INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES .\" (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR .\" SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, .\" STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING .\" IN ANY WAY OUT OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" .Dd October 9, 2004 .Dt PERCGI 3 .Os .ds vT PerCGI API Reference .ds oS PerCGI 2.0 .Sh NAME .Nm percgi .Nd Framework for web application servers .Sh SYNOPSIS .Bd -literal #include .Ed .Sh DESCRIPTION The .Nm library provides the basic building blocks for building high-performance web application servers in C and C-based languages. PerCGI implements: .Pp .Bl -bullet -compact .It CGI/FastCGI Interface .It Authentication .It Session Management .It Modular Query Processing .It Input/Output Content Filters .It Input Filter: Variable substitution .It HTTP Language Negotiation .It HTTP Content Negotiation .It HTTP Cookies .It Validation .El .Sh INITIALIZATION .nr nS 1 .Ft void .Fn CGI_Init "CGI_Application *cgi" .Pp .Ft void .Fn CGI_Exit "int exit_code" "const char *errmsg_fmt" "..." .nr nS 0 .Pp The .Fn CGI_Init function initializes the PerCGI library, and should be called prior to invoking any other .Nm functions. The .Ft CGI_Application argument should point to the following structure: .Bd -literal typedef struct cgi_application { const char *name; /* Description */ const char *copyright; /* Copyright notice */ const char *availLangs[CGI_LANGS_MAX]; /* Available languages */ const char *homeOp; /* Default operation */ Uint flags; #define CGI_PERSISTENT 0x02 /* Only run under FastCGI */ #define CGI_PRETTY_URL 0x04 /* Use "/op?args" instead of "/path/to/prog.fcgi?op=op&args" */ void (*destroyFn)(void); void (*logFn)(enum cgi_loglvl, const char *); } CGI_Application; .Ed .Pp The .Va availLangs list contains the set of languages (ISO 639) which are supported by the application, and which should be offered for HTTP language negotiation. .Pp The .Va homeOp field specifies the default operation to invoke after a user has successfully logged in (usually "home" or "index"). .Pp .Dv CGI_PERSISTENT indicates that the application must run under FastCGI, and will abort if FastCGI support was not compiled in. .Pp The .Dv CGI_PRETTY_URL option enables URLs of the form "/op?args", instead of the standard "/path/to/prog.fcgi?op=op&args" form. With Apache httpd 2.4, it is possible to start the .Nm application using .Xr fcgistarter 8 and simply match "/" with the .Dv ProxyPass directive, like so: .Bd -literal ProxyPass / fcgi://127.0.0.1:4123/ ProxyPass /static/ ! ProxyPass /images/ ! .Ed .Pp The .Fn destroyFn callback, if defined, will be invoked before the application terminates. .Pp The .Fn logFn method, if defined, overrides the default behavior of .Fn CGI_Log . .Pp The .Fn CGI_Exit function frees allocated resources and terminates the CGI application with the given .Fa exit_code and optional error message. .Sh ERROR HANDLING .nr nS 1 .Ft void .Fn CGI_SetError "const char *fmt" "..." .Pp .Ft "char *" .Fn CGI_GetError "void" .nr nS 0 .Pp The .Fn CGI_SetError function sets the current error message. .Fn CGI_GetError retrieves the last error message. .Sh HTTP QUERY HANDLING .nr nS 1 .Ft "void" .Fn CGI_QueryInit "CGI_Query *q" .Pp .Ft "void" .Fn CGI_QueryDestroy "CGI_Query *q" .Pp .Ft "int" .Fn CGI_ReadQueryHTTP "CGI_Query *q" .Pp .Ft "int" .Fn CGI_ExecQuery "CGI_Query *q" "CGI_SessionOps *sess" .Pp .Ft void .Fn CGI_QueryDestroy "CGI_Query *q" .Pp .Ft void .Fn CGI_WriteHeader "CGI_Query *q" "const char *contentType" "const char *charset" "int setCookies" .Pp .Ft void .Fn CGI_WriteHeaderHTML "CGI_Query *q" .Pp .Ft ssize_t .Fn CGI_WriteData "CGI_Query *q" "void *data" "size_t len" .nr nS 0 .Pp .Fn CGI_QueryInit initializes a query structure. .Fn CGI_QueryDestroy releases resources allocated by a query structure. .Pp The .Fn CGI_ReadQueryHTTP function accepts an HTTP request from the web server, parses any script arguments and initializes the .Ft CGI_Query structure argument according to the new request. The function returns 0 on success or -1 if an error occured. .Pp The .Fn CGI_ExecQuery routine handles a query in the standard way, by invoking the appropriate module call. It is assumed that standard .Nm session management is used (see .Dq SESSION MANAGEMENT section). It is not mandatory for queries to be processed by .Fn CGI_ExecQuery . .Pp The .Fn CGI_WriteHeader function selects the specified Content-Type / character set, and writes HTTP response headers to the client. If .Fa setCookie is 1, "Set-Cookie" headers are emitted (see .Fn CGI_SetCookie ) . .Pp The .Fn CGI_WriteHeaderHTML shorthand emits standard headers for text/html, and is equivalent to: .Bd -literal CGI_WriteHeader(q, "text/html", "utf-8", 1); .Ed .Pp The .Fn CGI_Write routine writes .Fa len bytes of .Fa data to the client. If registered output filters match the current "Content-Type", those filters are applied before writing the data. .Sh CGI ARGUMENTS .nr nS 1 .Ft "const char *" .Fn CGI_Get "CGI_Query *q" "const char *key" "size_t max" .Pp .Ft "const char *" .Fn CGI_GetTrim "CGI_Query *q" "const char *key" "size_t max" .Pp .Ft "int" .Fn CGI_GetBool "CGI_Query *q" "const char *key" .Pp .Ft "int" .Fn CGI_GetInt "CGI_Query *q" "const char *key" "int *rv" .Pp .Ft "int" .Fn CGI_GetIntR "CGI_Query *q" "const char *key" "int *rv" "int min" "int max" .Pp .Ft "int" .Fn CGI_GetIntRange "CGI_Query *q" "const char *key" "int *rvMin" "const char *sep" "int *rvMax" .Pp .Ft "int" .Fn CGI_GetEnum "CGI_Query *q" "const char *key" "Uint *rv" "Uint last" .Pp .Ft "int" .Fn CGI_GetFloat "CGI_Query *q" "const char *key" "float *rv" .Pp .Ft "int" .Fn CGI_GetDouble "CGI_Query *q" "const char *key" "double *rv" .Pp .Ft "char *" .Fn CGI_EscapeURL "CGI_Query *q" "const char *url" .Pp .Ft "char *" .Fn CGI_UnescapeURL "CGI_Query *q" "const char *url" .Pp .Ft "CGI_KeySet *" .Fn CGI_GetKeySet "CGI_Query *q" "const char *key_pattern" .Pp .Ft void .Fn CGI_FreeKeySet "CGI_KeySet *set" .nr nS 0 .Pp The .Fn CGI_Get function returns the value of the given CGI argument (either GET or POST). If the argument does not exist, or it exceeds .Fa len - 1 bytes in size, .Fn CGI_Get returns NULL and sets the error message accordingly. The .Fn CGI_GetTrim variant strips any leading and terminating whitespace from the token. Bounds checking is performed against the stripped token. .Pp The .Fn CGI_GetBool variant returns 1 if the given argument exists and has a value of "y". .Pp The .Fn CGI_GetInt , .Fn CGI_GetIntR , .Fn CGI_GetFloat and .Fn CGI_GetDouble functions parse and validate a numerical argument. If the number is valid, they return 0 and write the value to .Fa rv . If the number is malformed or out of range, they return -1 and set an error message. The .Fn CGI_GetIntR variant fails if the value lies outside of the specified range. .Pp The .Fn CGI_GetIntRange function parses an argument which may be a single integer or a range bounded by two numbers separated by any of the characters in .Fa sep (typically "-"). If the range is negative (pMin > pMax), the function will fail returning an error. .Pp The .Fn CGI_GetEnum routine is a shorthand for .Fn CGI_GetIntR with min=0, which also fails if the return value is greater than the .Fa last argument. .Pp The .Fn CGI_EscapeURL function substitutes illegal URL characters as described by RFC1738. .Fn CGI_UnescapeURL substitutes escape codes for the character they represent, except for NUL sequences which are replaced by underscores to avoid truncation. .Pp The .Fn CGI_GetKeySet function returns the set of arguments whose key start with the given pattern. It is useful for things like checkboxes in HTML forms. The .Ft CGI_KeySet structure is defined as: .Bd -literal typedef struct cgi_keyset { char **ents; int nents; } CGI_KeySet; .Ed .Pp The application should use .Fn CGI_FreeKeySet when done with the data. .Sh CONTENT FILTERING .nr nS 1 .Ft void .Fn CGI_AddFilter "CGI_Filter *filter" .Pp .Ft void .Fn CGI_DelFilter "CGI_Filter *filter" .nr nS 0 .Pp The .Fn CGI_AddFilter function registers a content filtering function which should process either data read by the script or data written to the output, which matches the given MIME type. Filters are useful for compression and certain types of substitutions. .Pp The argument of .Fn CGI_AddFilter is a pointer to the following structure: .Bd -literal typedef struct cgi_filter { enum cgi_filter_type { CGI_INPUT_FILTER, /* Filter data read by the script */ CGI_OUTPUT_FILTER /* Filter data written to stdout */ } type; char *content_type; /* Apply to data of this type */ size_t (*func)(CGI_Query *q, void **data, size_t len); } CGI_Filter; .Ed .Pp The .Fn CGI_DelFilter function removes the specified content filter. .Sh HTML OUTPUT The most common operation in a PerCGI query is the servicing of precompiled, performatted HTML data (with "$foo" style references in it). .Bd -literal .Ft void .Fn HTML_SetDirectory "CGI_Query *q" "const char *path" .Pp .Ft void .Fn HTML_Output "CGI_Query *q" "const char *document" .Pp .Ft void .Fn HTML_OutputError "CGI_Query *q" "const char *fmt" "..." .Pp .Ft void .Fn HTML_SetError "const char *fmt" "..." .Pp .Ft void .Fn HTML_SetSuccess "const char *fmt" "..." .Pp .Ed .Fn HTML_SetDirectory can be used to set the default directory to search for HTML documents and fragments (defaults to .Pa html/ ) . .Pp The .Fn HTML_Output function writes an HTML document to the standard output (assuming the HTTP headers have already been emitted). .Pp The .Fn HTML_OutputError function outputs an HTML document containing only the given error message. .Pp .Fn HTML_SetError sets the built-in variable $_error to the given error message. Whenever $_error is defined, the HTML template is expected to display a closable error dialog in a suitable location (usually on top of the document). .Fn HTML_SetSuccess works the same, except $_error is set to a success-type message. .Sh VARIABLE SUBSTITUTION PerCGI provides one standard content filter, which is an Input filter named .Va varsubst . This filter is used by .Fn HTML_Output to substitute instances of "$foo" in the HTML input, for the value of the corresponding .Nm variable (per the interface described below). .Va varsubst also provides a handy gettext-style internationalization operator, "$_()". Instances of "$_(Some text)" in the sources will be replaced by the corresponding gettext translation (per the user's language settings). .Pp Variables are managed by PerCGI and described as: .Bd -literal typedef struct var { char key[VAR_NAME_MAX]; /* Variable name */ char *value; /* Variable value */ size_t len; /* Length in characters */ size_t bufSize; /* Total buffer size */ int global; /* Persist across queries */ TAILQ_ENTRY(var) vars; } VAR; .Ed .Pp The API is as follows. Note that the VAR_ prefix can be omitted if compiling with .Dv _USE_PERCGI_VAR defined. .Pp .nr nS 1 .Ft "VAR *" .Fn VAR_New "const char *key" .Pp .Ft "VAR *" .Fn VAR_Set "const char *key" "const char *fmt" "..." .Pp .Ft "VAR *" .Fn VAR_SetS "const char *key" "const char *s" .Pp .Ft "VAR *" .Fn VAR_SetS_NODUP "const char *key" "char *s" .Pp .Ft "VAR *" .Fn VAR_SetGlobal "const char *key" "const char *fmt" "..." .Pp .Ft "VAR *" .Fn VAR_SetGlobalS "const char *key" "const char *s" .Pp .Ft void .Fn VAR_Cat "VAR *v" "const char *fmt" "..." .Pp .Ft void .Fn VAR_CatS "VAR *v" "const char *s" .Pp .Ft void .Fn VAR_CatS_NODUP "VAR *v" "char *s" .Pp .Ft void .Fn VAR_Wipe "const char *key" .Pp .Ft void .Fn VAR_Unset "const char *key" .Pp .Ft int .Fn VAR_Defined "const char *key" .Pp .Ft void .Fn VAR_Free "VAR *var" .Pp .nr nS 0 .Pp .Fn VAR_New creates a new variable. The variable is initially undefined. .Pp .Fn VAR_Set sets the value of a variable from a format string argument. .Fn VAR_SetS accepts a C string. The .Fn VAR_SetS_NODUP routine accepts a pointer to user memory (which should remain accessible as long as the variable is in use). .Pp The scope of variables set by .Fn VAR_Set is limited to the current .Ft CGI_Query . The .Fn VAR_SetGlobal and .Fn VAR_SetGlobalS variants set a "global" variable remains persistent across queries. This is useful for static or semi-static content which needs to be regenerated infrequently. It is customary to define globals for limits such as .Dv CGI_USERNAME_MAX , from the .Fn init method of a .Ft CGI_Module . .Pp The .Fn VAR_Cat and .Fn VAR_CatS functions append a string to an existing variable. The .Fn VAR_CatS_NODUP variant frees the provided string after it has been appended. .Pp For variable substitution to work, variables should be set prior to calling .Fn HTML_Output . For example, the following assumes that the login_form HTML document contains one or more instances of "$username" and "$password", to be substituted: .Bd -literal SetS("username", "nobody"); SetS("password", ""); HTML_Output("login_form"); VAR_Wipe("password"); .Ed .Pp The .Fn VAR_Wipe routine wipes the memory currently used by the given variable. It is recommended to call .Fn VAR_Wipe on variables that have been storing sensitive information such as passwords, once the variable is no longer needed. .Pp .Fn VAR_Unset searches for the named variable. If the variable is found, it is deleted and freed. .Pp The .Fn VAR_Defined function evaluates to 1 if the named variable exists, otherwise returns 0. .Pp The .Fn VAR_Free routine frees all resources allocated by a variable. Note that this is already done internally by PerCGI when .Fn VAR_Unset is called or whenever a query has been completed. .Fn VAR_Free should only be used to free anonymous variables (variables with key = NULL which are not managed by PerCGI). For example: .Bd -literal VAR *v; v = SetS(NULL, "Foo"); /* Anonymous variable */ VAR_CatS(v, " bar"); VAR_Free(v); .Ed .Sh OUTPUT AND LOGGING .nr nS 1 .Ft ssize_t .Fn CGI_Write "CGI_Query *q" "const void *data" "size_t len" .Pp .Ft void .Fn CGI_Printf "CGI_Query *q" "const char *fmt" "..." .Pp .Ft void .Fn CGI_PutS "CGI_Query *q" "const char *s" .Pp .Ft void .Fn CGI_PutC "CGI_Query *q" "char c" .nr nS 0 .Pp The .Fn CGI_Write function writes .Fa len bytes from .Fa data to the client. It returns 0 on success, or -1 if an I/O error or EOF prevented all .Fa len bytes from being written. .Pp .Fn CGI_Write applies any output filters matching the current "Content-Type". .Pp .Fn CGI_Printf writes a .Xr printf 3 formatted string to the client, .Fn CGI_PutS writes a C string, and .Fn CGI_PutC writes a character. .Pp .Sh LOGGING .nr nS 1 .Ft void .Fn CGI_Log "enum cgi_loglvl log_level" "const char *fmt" "..." .Pp .Ft void .Fn CGI_LogErr "const char *fmt" "..." .Pp .Ft void .Fn CGI_LogWarn "const char *fmt" "..." .Pp .Ft void .Fn CGI_LogInfo "const char *fmt" "..." .Pp .Ft void .Fn CGI_LogNotice "const char *fmt" "..." .Pp .Ft void .Fn CGI_LogDebug "const char *fmt" "..." .nr nS 0 .Pp The .Fn CGI_Log function appends an entry to the application's logfile. The .Fa log_level argument is one of: .Bd -literal enum cgi_loglvl { CGI_LOG_EMERG, CGI_LOG_ALERT, CGI_LOG_CRIT, CGI_LOG_ERR, CGI_LOG_WARNING, CGI_LOG_NOTICE, CGI_LOG_INFO, CGI_LOG_DEBUG }; .Ed .Pp The .Fn CGI_LogErr , .Fn CGI_LogWarn , .Fn CGI_LogInfo , .Fn CGI_LogNotice and .Fn CGI_LogDebug shorthands are also provided. .Sh SESSION MANAGEMENT .nr nS 1 .Ft void .Fn CGI_SessionInit "CGI_Session *sess" "CGI_SessionOps *ops" .Pp .Ft void .Fn CGI_SessionDestroy "CGI_Session *sess" .Pp .Ft void .Fn CGI_CloseSession "CGI_Session *sess" .Pp .Ft int .Fn CGI_SessionLoad "CGI_Session *sess" "int fd" .Pp .Ft int .Fn CGI_SessionSaveToFD "CGI_Session *sess" "int fd" .Pp .Ft int .Fn CGI_SessionSave "CGI_Session *sess" .Pp .Ft int .Fn CGI_SetSV "CGI_Session *sess" "const char *key" "const char *fmt" "..." .Pp .Ft int .Fn CGI_SetSV_S "CGI_Session *sess" "const char *key" "const char *s" .Pp .Ft int .Fn CGI_SetSV_ALL "CGI_SessionOps *ops" "const char *user" "const char *key" "const char *value" .Pp .nr nS 0 The .Fn CGI_SessionInit routine initializes the given .Ft CGI_Session structure. The .Fa ops argument should point to the following structure, which describes a session manager class. All operations are optional, but unused methods should be initialized to NULL. .Pp .Bd -literal typedef struct cgi_session_ops { const char *name; /* Description */ size_t size; /* Structure size */ Uint flags; #define CGI_SESSION_PREFORK_AUTH 0x01 /* Call auth() before fork() */ void (*init)(void *s); void (*destroy)(void *s); int (*load)(void *s, int fd); void (*save)(void *s, int fd); int (*auth)(void *s, const char *user, const char *pass); void (*authRegister)(CGI_Query *q); const char *authRegisterName; void (*authRegConfirm)(CGI_Query *q); const char *authRegConfirm; void (*authAssist)(CGI_Query *q); const char *authAssistName; void (*authRequest)(CGI_Query *q); const char *authRequestName; void (*authConfirm)(CGI_Query *q); const char *authConfirmName; void (*authCommit)(CGI_Query *q); const char *authCommitName; int (*sessOpen)(void *s); void (*sessClose)(void *s); void (*sessExpired)(void *s); void (*beginQueryUnauth)(CGI_Query *q, const char *op); void (*loginPage)(CGI_Query *q); void (*logout)(CGI_Query *q); void (*addSelectFDs)(void *s, fd_set *r, fd_set *w, int *maxfds); void (*procSelectFDs)(void *s, fd_set *r, fd_set *w); } CGI_SessionOps; .Ed .Pp The .Va name is an arbitrary string which identifies the session manager. .Va size should be set to the size of the .Ft CGI_SessionOps structure (or derivative thereof). .Pp The .Va flags options apply to all session manager instances. If the .Dv CGI_SESSION_PREFORK_AUTH flag is set, the .Fn auth call will be made prior to .Xr fork 2 . This is recommended when using local methods of authentication (i.e., password files), as opposed to methods that involve estalishing a network connection. .Pp The .Fn init callback is invoked at initialization time and is expected to initialize any extra members of structures derived from .Ft CGI_SessionOps . .Pp .Fn destroy should release all resources previously allocated by .Fn init . .Pp The serialization routines .Fn load and .Fn save are invoked whenever the session file is read from, or written to the disk file referenced by .Fa fd . They are useful for session managers which need to save other data (in addition to the session variables). The extra data will follow the list of session variables in the session file. .Pp Password authentication is handled by the .Fn auth operation, which should return 0 if .Fa user and .Fa pass are correct, or -1 otherwise. .Pp The .Fn authRegister operation is expected to process POSTDATA from a subscription form where new users can request an account. It should perform validation on the request data, generate a new authentication token and e-mail a link (a link to the .Fn authRegConfirm operation), to the user. .Pp The .Fn authRegConfirm operation is expected to validate the token and complete the registration request. .Pp The following methods implement password recovery, to assist users in recovering lost passwords. .Fn authAssist is expected to display a password recovery form (which submits to .Fn authRequest ) . The .Fn authRequest should generate a random token and e-mail it to the contact e-mail address (or perform validation using some alternate mechanism if desired). .Pp The verification e-mail should be a link to the .Fn authConfirm operation. Given a valid token, this operation is expected to generate a form prompting for the security question (if one has been set), in addition to the "New password" field. This form should submit the token, answer to the security question, and password to the .Fn authCommit operation. .Pp The .Fn sessOpen function is invoked after authentication and successful creation of a new session. If it returns a value other than 0, the new session will be aborted. .Pp .Fn sessClose is invoked after a session has been terminated. If a session has been terminated due to an inactivity time-out, .Fn sessExpired is also invoked. .Pp The .Fn beginQueryUnauth method may be used to override the default code used to initialize the standard globals such as $_error, $_user, $_lang and $_modules in an unauthenticated session. .Pp The .Fn loginPage method is expected to display the standard "Log in" form. When a user gracefully logs out using "Log out", the .Fn logout method is called and is expected to display a confirmation message. .Pp .Fn addSelectFDs and .Fn procSelectFDs are hooks into the standard event loop of .Nm (which is .Xr select 2 based). .Fn addSelectFDs can add additional file descriptors to .Fa rd or .Fa wr using .Xr FD_SET 2 (it should not modify the existing set). .Pp The .Fn procSelectFDs callback is invoked following a return from .Xr select 2 , and may test the returned sets using .Xr FD_ISSET 2 . .Pp .Fn CGI_SessionDestroy releases all resources allocated by a session. .Pp The .Fn CGI_CloseSession routine closes the given session. .Pp The serialization routines .Fn CGI_SessionLoad and .Fn CGI_SessionSave respectively read and write session data (including all session variables) from/to file. .Pp The .Fn CGI_SetSV routine sets the named session variable to the value (specified as a .Xr printf 3 format string). The .Fn CGI_SetSV_S variants accepts a C string argument. .Pp The .Fn CGI_SetSV_ALL routine sets the named session variable similarly to .Fn CGI_SetSV , except the change applies to all sessions currently opened by .Fa user . .Sh HISTORY The .Nm interface was first developed in 2003 (at csoft.net), under the name of csoft-cgi.