/* Public domain */ #ifndef _AGAR_NET_WEB_H_ #define _AGAR_NET_WEB_H_ #include #ifdef AG_WEB #include #include #include #include #include #define WEB_COMPAT_APACHE /* Behind Apache 2.4 mod_proxy */ /* #define WEB_COMPAT_NGINX */ /* Behind nginx */ /* #define WEB_CHUNKED_EVENTS */ /* Chunked event streams */ #define HAVE_SETPROCTITLE #define WEB_FRONTEND_RDBUFSIZE 16384 /* Frontend I/O buffer (must fit header) */ #define WEB_DATA_BUFSIZE 65536 /* Data buffer size */ #define WEB_DATA_COMPRESS_MIN 8192 /* Compression threshold */ #define WEB_DATA_COMPRESS_LVL 6 /* Default compression level */ #define WEB_FORMDATA_MAX (8*1024*1024) /* Accepted multipart/form-data size */ #define WEB_HTTP_REQ_TIMEOUT 30 /* HTTP request (and keepalive) timeout) */ #define WEB_WORKER_RESP_TIMEOUT 15 /* Worker response timeout */ #define WEB_EVENT_READ_TIMEOUT 10 /* Event source read timeout */ #define WEB_EVENT_INACT_TIMEOUT 3600 /* Event source inactivity timeout */ #define WEB_EVENT_MAXRETRY 10 /* Max Redirect/Retry attempts */ #define WEB_EVENT_PING_IVAL 15 /* Event source ping interval */ #define WEB_HTTP_HEADER_MIN 14 /* Min HTTP header size */ #define WEB_HTTP_PER_HEADER_MAX 256 /* Max HTTP header size (per header) */ #define WEB_HTTP_HEADER_MAX 1024 /* HTTP header size (total) */ #define WEB_HTTP_MAXHEADERS 32 /* Max HTTP response headers */ #define WEB_QUERY_MAX 4096 /* Max serialized WEB_Query size */ #define WEB_MAXHTTPSOCKETS 5 /* Max listening sockets */ #define WEB_MAXWORKERSOCKETS 30 /* Max Worker->Frontend sockets */ #define WEB_MAX_ARGS 256 /* URL-encoded argument count */ #define WEB_MAX_COOKIES 32 /* Number of cookies */ #define WEB_ARG_KEY_MAX 60 /* Argument key */ #if AG_MODEL == AG_MEDIUM #define WEB_ARG_TYPE_MAX 28 #else #define WEB_ARG_TYPE_MAX 32 #endif #define WEB_ARG_LENGTH_MAX 100000000 /* Argument data (100MB) */ #define WEB_LANGS_MAX 64 /* Accept-Language entries */ #define WEB_LANG_CODE_MAX 6 /* Language code */ #define WEB_URL_MAX 768 /* URL length */ #define WEB_ERROR_MAX 1024 /* Error message length */ #define WEB_USERAGENT_MAX 512 /* HTTP User-Agent length */ #define WEB_EVENT_MAX 4096 /* size of event packets */ #define WEB_RANGE_STRING_MAX 32 /* Byte range specifier */ #define WEB_RANGE_MAXRANGES 8 /* Maximum ranges in a Range request */ #define WEB_OPNAME_MAX 32 /* Operation name length */ #define WEB_USERNAME_MAX 64 /* Username length */ #define WEB_PASSWORD_MAX 128 /* Password length */ #define WEB_EMAIL_MAX 128 /* E-mail address length */ #define WEB_INT_RANGE_MAX 21 /* Maximum integer range length */ #define WEB_SESSID_MAX 11 /* Session ID length */ #define WEB_SESSION_VAR_KEY_MAX 32 /* Session variable key */ #define WEB_SESSION_VAR_VALUE_MAX 1024 /* Session variable value */ #define WEB_SESSION_VARIABLES_MAX 100 /* Session variables total */ #define WEB_SESSION_DATA_MAX 4096 /* Session data total (bytes) */ #define WEB_SESSION_DATA_MAGIC 0x50657243 #define WEB_COOKIE_NAME_MAX 48 /* Cookie name */ #define WEB_COOKIE_VALUE_MAX 3807 /* Cookie value */ #define WEB_COOKIE_EXPIRE_MAX 64 /* Cookie expiration field */ #define WEB_COOKIE_DOMAIN_MAX 48 /* Cookie domain field */ #define WEB_COOKIE_PATH_MAX 101 /* Cookie path argument */ #define WEB_COOKIE_SET_MAX 128 /* Max generated Set-Cookie length */ #define WEB_VAR_NAME_MAX 32 /* Web variable name */ #define WEB_VAR_BUF_INIT 128 /* Variable buffer size */ #define WEB_VAR_BUF_GROW 1024 #ifndef WEB_GLYPHICON #define WEB_GLYPHICON(x) "" #endif #ifndef WEB_PATH_HTML #define WEB_PATH_HTML "html" #endif #ifndef WEB_PATH_SESSIONS #define WEB_PATH_SESSIONS "sessions/" #endif #ifndef WEB_PATH_SOCKETS #define WEB_PATH_SOCKETS "sockets/" #endif #ifndef WEB_PATH_EVENTS #define WEB_PATH_EVENTS "events/" #endif typedef enum web_method { WEB_METHOD_GET, WEB_METHOD_HEAD, WEB_METHOD_POST, WEB_METHOD_OPTIONS, WEB_METHOD_PUT, WEB_METHOD_DELETE, WEB_METHOD_TRACE, WEB_METHOD_CONNECT, WEB_METHOD_LAST } WEB_Method; /* Web variable */ typedef struct web_variable { char key[WEB_VAR_NAME_MAX]; /* Name */ char *_Nullable value; /* Value */ AG_Size len; /* Content length (characters) */ AG_Size bufSize; /* Buffer size */ int global; /* 1 = Persist across queries */ Uint32 _pad; AG_TAILQ_ENTRY(web_variable) vars; } WEB_Variable; AG_TAILQ_HEAD(web_variableq, web_variable); /* Argument to script (key=value pair). */ typedef struct web_argument { enum web_argument_type { WEB_GET_ARGUMENT, /* HTTP GET method */ WEB_POST_ARGUMENT, /* HTTP POST method */ WEB_ARGUMENT_LAST } type; char key[WEB_ARG_KEY_MAX]; /* Key */ char *_Nullable value; /* Value data (allocated) */ AG_Size len; /* Value length in bytes */ char contentType[WEB_ARG_TYPE_MAX]; /* Content-Type or "" */ AG_TAILQ_ENTRY(web_argument) args; } WEB_Argument; /* HTTP cookie */ typedef struct web_cookie { /* Read from client */ char name[WEB_COOKIE_NAME_MAX]; /* Name of cookie */ char value[WEB_COOKIE_VALUE_MAX]; /* Value of cookie */ /* For Set-Cookie output */ char expires[WEB_COOKIE_EXPIRE_MAX]; /* Expiration date or \0 */ char domain[WEB_COOKIE_DOMAIN_MAX]; /* Domain match or \0 */ char path[WEB_COOKIE_PATH_MAX]; /* Path attribute or \0 */ Uint flags; #define WEB_COOKIE_SECURE 0x01 /* Secure attribute */ #define WEB_COOKIE_HTTPONLY 0x02 /* Http-Only attribute */ AG_TAILQ_ENTRY(web_cookie) cookies; } WEB_Cookie; /* Computed, satisfiable Range request */ typedef struct web_range_req { AG_Size first[WEB_RANGE_MAXRANGES]; /* First byte pos */ AG_Size last[WEB_RANGE_MAXRANGES]; /* Last byte pos */ Uint nRanges; } WEB_RangeReq; /* Query from web server */ typedef struct web_query { WEB_Method method; /* HTTP method */ Uint flags; #define WEB_QUERY_CONTENT_READ 0x01 /* Content has been read */ #define WEB_QUERY_KEEPALIVE 0x02 /* Keepalive requested */ #define WEB_QUERY_DEFLATE 0x04 /* Accepts deflate encoding */ #define WEB_QUERY_NOCOMPRESSION 0x08 /* Disable compression */ #define WEB_QUERY_RANGE 0x10 /* Range request */ #define WEB_QUERY_PROXIED 0x20 /* Behind proxy */ int compressLvl; /* Compression level */ char acceptLangs[WEB_LANGS_MAX] /* Accept-Language list */ [WEB_LANG_CODE_MAX]; Uint nAcceptLangs; AG_TAILQ_HEAD_(web_argument) args; /* Call arguments */ AG_TAILQ_HEAD_(web_cookie) cookies; /* HTTP cookies */ Uint nArgs; Uint nCookies; char contentType[128]; /* Client Content-Type (+attrs) */ AG_Size contentLength; /* Client Content-Length */ int rangeFrom, rangeTo; /* Range request */ char userIP[64]; /* Client IP address */ char userHost[256]; /* Client hostname */ char userAgent[WEB_USERAGENT_MAX]; /* Client User-Agent */ char code[64]; /* HTTP/1.0 code */ char head[WEB_HTTP_HEADER_MAX]; /* HTTP response headers */ Uint headLine[WEB_HTTP_MAXHEADERS]; /* Line start offsets */ Uint headLineCount; /* Total lines in header */ Uint headLen; /* Length of header in bytes */ #if AG_MODEL == AG_MEDIUM Uint32 _pad; #endif Uchar *_Nullable data; /* Raw response entity-body */ AG_Size dataSize, dataLen; char lang[8]; /* Negotiated language code */ void *_Nullable sess; /* Session object */ void *_Nullable mod; /* WEB_Module executing op */ int sock; /* Client socket (or -1) */ char date[36]; /* HTTP time */ } WEB_Query; /* Control command sent to running Frontend process. */ typedef struct web_control_cmd { Uint flags; #define WEB_CONTROL_CMD_SYNC 0x01 /* Block and return status */ enum { WEB_CONTROL_NOOP, WEB_CONTROL_SHUTDOWN, /* Perform graceful exit */ WEB_CONTROL_WORKER_CHLD /* Report Worker process exit */ } type; union { struct { char reason[16]; /* Reason for shutdown */ } shutdown; struct { pid_t pid; /* Worker PID */ int status; /* Reported exit status */ } workerQuit; } data; } WEB_ControlCmd; /* Description of user-provided option flags */ typedef struct web_flag_descr { Uint bitmask; const char *_Nonnull name; const char *_Nonnull permsRD; const char *_Nonnull permsWR; const char *_Nonnull descr; } WEB_FlagDescr; /* Log levels; sync with webLogLvlNames[] */ enum web_loglvl { WEB_LOG_EMERG, WEB_LOG_ALERT, WEB_LOG_CRIT, WEB_LOG_ERR, WEB_LOG_WARNING, WEB_LOG_NOTICE, WEB_LOG_INFO, WEB_LOG_DEBUG, WEB_LOG_QUERY, WEB_LOG_WORKER, WEB_LOG_EVENT }; typedef int (*WEB_CommandFn)(WEB_Query *_Nonnull q); /* Map an URL-specified op to a module method */ typedef struct web_command { char *_Nullable name; /* Name (NULL = list terminator) */ _Nullable WEB_CommandFn fn; /* Callback function */ const char *_Nullable type; /* Content-type (NULL = fn-defined) */ const char *_Nullable flags; /* Flags ("P"=public, "i"=index) */ } WEB_Command; /* Map URL to operation (pre-auth) */ typedef struct web_command_pre_auth { const char *_Nullable name; /* Op name (NULL = list terminator) */ void (*_Nullable fn)(WEB_Query *_Nonnull q); /* Callback function */ const char *_Nullable type; /* Content-Type (NULL = fn-defined) */ } WEB_CommandPreAuth; /* Entry in per-module menu section table. */ typedef struct web_menu_section { const char *_Nullable name; /* Display name (NULL = list terminator) */ const char *_Nullable cmd; /* Target op */ } WEB_MenuSection; /* Application server module */ typedef struct web_module_class { struct ag_object_class _inherit; char *_Nonnull name; /* Module class name */ char *_Nonnull icon; /* Icon HTML */ char *_Nonnull lname; /* Long name (HTML ok) */ char *_Nonnull desc; /* Description (HTML ok) */ int (*_Nullable workerInit)(void *_Nonnull, void *_Nonnull); void (*_Nullable workerDestroy)(void *_Nonnull); int (*_Nullable sessOpen)(void *_Nonnull, void *_Nonnull); void (*_Nullable sessClose)(void *_Nonnull, void *_Nonnull); void (*_Nullable menu)(void *_Nonnull, WEB_Query *_Nonnull, WEB_Variable *_Nonnull); WEB_MenuSection *_Nullable menuSections; WEB_Command *_Nonnull commands; } WEB_ModuleClass; typedef struct web_module { struct ag_object _inherit; /* AG_Object -> WEB_Module */ } WEB_Module; /* Session manager interface */ typedef struct web_session_ops { const char *_Nonnull name; /* Session class name */ AG_Size size; /* Structure size */ AG_SIZE_PADDING(_pad); #ifdef AG_HAVE_64BIT Uint64 flags; #else Uint flags; #endif #define WEB_SESSION_PREFORK_AUTH 0x01 /* Call auth() before fork() */ time_t sessTimeout; /* Session inactivity timeout (s) */ time_t workerTimeout; /* Worker inactivity timeout (s) */ void (*_Nullable init)(void *_Nonnull); void (*_Nullable destroy)(void *_Nonnull); int (*_Nullable load)(void *_Nonnull, AG_DataSource *_Nonnull); void (*_Nullable save)(void *_Nonnull, AG_DataSource *_Nonnull); int (*_Nullable auth)(void *_Nullable, const char *_Nonnull, const char *_Nonnull); WEB_CommandPreAuth preAuthCmds[10]; int (*_Nullable sessOpen)(void *_Nonnull, const char *_Nonnull); void (*_Nullable sessRestored)(void *_Nonnull, const char *_Nonnull); void (*_Nullable sessClose)(void *_Nonnull); void (*_Nullable sessExpired)(void *_Nonnull); void (*_Nullable beginFrontQuery)(WEB_Query *_Nonnull, const char *_Nonnull); void (*_Nonnull loginPage)(WEB_Query *_Nonnull); void (*_Nonnull logout)(WEB_Query *_Nonnull); void (*_Nullable addSelectFDs)(void *_Nonnull, fd_set *_Nullable, fd_set *_Nullable, int *_Nonnull); void (*_Nullable procSelectFDs)(void *_Nonnull, fd_set *_Nullable, fd_set *_Nullable); } WEB_SessionOps; /* HTTP method */ typedef struct web_method_ops { const char *_Nonnull name; int (*_Nonnull fn)(int, const char *_Nonnull, char *_Nonnull, void *_Nonnull, AG_Size rdBufLen, const WEB_SessionOps *_Nonnull Sops); } WEB_MethodOps; /* Session variable */ typedef struct web_session_var { char key[WEB_SESSION_VAR_KEY_MAX]; /* Variable name */ char value[WEB_SESSION_VAR_VALUE_MAX]; /* Variable value */ AG_TAILQ_ENTRY(web_session_var) vars; } WEB_SessionVar; /* Open socket between a Frontend and a running Worker. */ typedef struct web_session_socket { char sessID[WEB_SESSID_MAX]; /* Open session ID */ Uint8 _pad; int fd; /* Socket */ pid_t workerPID; /* Worker process PID */ int workerIsMyChld; /* Worker is my child */ AG_TAILQ_ENTRY(web_session_socket) sockets; } WEB_SessionSocket; AG_TAILQ_HEAD(web_session_socketq, web_session_socket); /* Session Instance */ typedef struct web_session { const WEB_SessionOps *_Nonnull ops; /* Operations */ char id[WEB_SESSID_MAX]; /* Session identifier */ Uint8 _pad1; Uint nVars; AG_TAILQ_HEAD_(web_session_var) vars; /* Session variables */ AG_TAILQ_ENTRY(web_session) sessions; int pp[2]; /* Status pipe */ Uint nEvents; /* Event counter */ Uint32 _pad2; } WEB_Session; typedef int (*WEB_LanguageFn)(const char *_Nonnull, void *_Nullable); typedef void (*WEB_MenuFn)(WEB_Query *_Nonnull, WEB_Variable *_Nonnull, void *_Nullable); typedef void (*WEB_DestroyFn)(void); typedef void (*WEB_LogFn)(enum web_loglvl, const char *_Nonnull); #define WEB_TRIM_WHITESPACE(s,end) \ while (isspace(*s)) { s++; } \ if (*s != '\0') { \ end = &s[strlen(s) - 1]; \ while (end > s && isspace(*end)) { \ *end = '\0'; \ end--; \ } \ } typedef int (*WEB_EventFilterFn)(char *_Nonnull, char *_Nonnull, char *_Nonnull, const void *_Nullable); __BEGIN_DECLS extern WEB_ModuleClass webModuleClass; /* Web(Module) class */ extern WEB_Module *_Nullable *_Nonnull webModules; /* Registered modules */ extern Uint webModuleCount; extern char *_Nullable *_Nullable webLangs; /* Handled languages */ extern Uint webLangCount; extern char webWorkerUser[WEB_USERNAME_MAX]; /* Username (in Worker) */ extern char webHomeOp[WEB_OPNAME_MAX]; /* Default (home) operation */ extern const WEB_MethodOps webMethods[]; /* HTTP methods handled */ extern struct web_variableq webVars; /* Variables for template substitution */ extern Uint webQueryCount; /* Query counter (per process) */ extern struct web_session_socketq webWorkSockets; /* Frontend->Worker sockets */ /* * Initialization */ void WEB_Init(Uint, int); void WEB_Destroy(void); void WEB_CheckSignals(void); void WEB_RegisterModule(WEB_ModuleClass *_Nonnull); void WEB_SetLogFile(const char *_Nonnull); void WEB_SetLogFn(_Nullable WEB_LogFn); void WEB_Exit(int, const char *_Nullable, ...); void WEB_SetLanguageFn(_Nullable WEB_LanguageFn, void *_Nullable); void WEB_SetMenuFn(_Nullable WEB_MenuFn, void *_Nullable); void WEB_SetDestroyFn(_Nullable WEB_DestroyFn); void WEB_AddLanguage(const char *_Nonnull); /* * Query processing */ int WEB_WorkerMain(const WEB_SessionOps *_Nonnull, WEB_Query *_Nonnull, const char *_Nonnull, const char *_Nonnull, const char *_Nonnull, int [_Nonnull 2], int); void WEB_QueryLoop(const char *_Nonnull, const char *_Nonnull, const WEB_SessionOps *_Nonnull); int WEB_ControlCommand(Uint, const WEB_ControlCmd *_Nonnull); int WEB_ControlCommandS(Uint, const char *_Nonnull); void WEB_QueryInit(WEB_Query *_Nonnull, const char *_Nonnull); void WEB_QueryDestroy(WEB_Query *_Nonnull); int WEB_QueryLoad(WEB_Query *_Nonnull, const void *_Nonnull, AG_Size); int WEB_QuerySave(int, const WEB_Query *_Nonnull); void WEB_BeginFrontQuery(WEB_Query *_Nonnull, const char *_Nonnull, const WEB_SessionOps *_Nonnull); void WEB_BeginWorkerQuery(WEB_Query *_Nonnull); int WEB_ExecWorkerQuery(WEB_Query *_Nonnull); void WEB_FlushQuery(WEB_Query *_Nonnull); int WEB_ProcessQuery(WEB_Query *_Nonnull, const WEB_SessionOps *_Nonnull, void *_Nonnull, AG_Size); /* * HTTP Headers */ void WEB_EditHeader(WEB_Query *_Nonnull, char *_Nonnull, const char *_Nonnull); WEB_Cookie *_Nonnull WEB_SetCookie(WEB_Query *_Nonnull, const char *_Nonnull, const char *_Nonnull, ...) FORMAT_ATTRIBUTE(printf,3,4); WEB_Cookie *_Nonnull WEB_SetCookieS(WEB_Query *_Nonnull, const char *_Nonnull, const char *_Nonnull); void WEB_DelCookie(WEB_Query *_Nonnull, const char *_Nonnull); /* * Form and argument parsing */ int WEB_ParseFormUrlEncoded(WEB_Query *_Nonnull, char *_Nonnull, enum web_argument_type); int WEB_ReadFormData(WEB_Query *_Nonnull, int); const char *_Nullable WEB_Get(WEB_Query *_Nonnull, const char *_Nonnull, AG_Size); const char *_Nullable WEB_GetTrim(WEB_Query *_Nonnull, const char *_Nonnull, AG_Size); int WEB_GetInt(WEB_Query *_Nonnull, const char *_Nonnull, int *_Nonnull); int WEB_GetUint(WEB_Query *_Nonnull, const char *_Nonnull, Uint *_Nonnull); int WEB_GetIntR(WEB_Query *_Nonnull, const char *_Nonnull, int *_Nonnull, int, int); int WEB_GetUintR(WEB_Query *_Nonnull, const char *_Nonnull, Uint *_Nonnull, Uint, Uint); int WEB_GetIntRange(WEB_Query *_Nonnull, const char *_Nonnull, int *_Nonnull, const char *_Nonnull, int *_Nonnull); int WEB_GetUint64(WEB_Query *_Nonnull, const char *_Nonnull, Uint64 *_Nonnull); int WEB_GetSint64(WEB_Query *_Nonnull, const char *_Nonnull, Sint64 *_Nonnull); int WEB_GetEnum(WEB_Query *_Nonnull, const char *_Nonnull, Uint *_Nonnull, Uint); int WEB_GetFloat(WEB_Query *_Nonnull, const char *_Nonnull, float *_Nonnull); int WEB_GetDouble(WEB_Query *_Nonnull, const char *_Nonnull, double *_Nonnull); int WEB_GetBool(WEB_Query *_Nonnull, const char *_Nonnull); void WEB_SetS(WEB_Query *_Nonnull, const char *_Nonnull, const char *_Nullable); void WEB_Set(WEB_Query *_Nonnull, const char *_Nonnull, const char *_Nullable, ...); int WEB_Unset(WEB_Query *_Nonnull, const char *_Nonnull); char *_Nonnull WEB_EscapeURL(WEB_Query *_Nonnull, const char *_Nonnull); char *_Nonnull WEB_UnescapeURL(WEB_Query *_Nonnull, const char *_Nonnull); /* * Logging */ void WEB_LogS(enum web_loglvl, const char *_Nonnull); void WEB_Log(enum web_loglvl, const char *_Nonnull, ...) FORMAT_ATTRIBUTE(__printf__,2,3); void WEB_LogErr(const char *_Nonnull, ...) FORMAT_ATTRIBUTE(__printf__,1,2); void WEB_LogWarn(const char *_Nonnull, ...) FORMAT_ATTRIBUTE(__printf__,1,2); void WEB_LogInfo(const char *_Nonnull, ...) FORMAT_ATTRIBUTE(__printf__,1,2); void WEB_LogNotice(const char *_Nonnull, ...) FORMAT_ATTRIBUTE(__printf__,1,2); void WEB_LogDebug(const char *_Nonnull, ...) FORMAT_ATTRIBUTE(__printf__,1,2); void WEB_LogWorker(const char *_Nonnull, ...) FORMAT_ATTRIBUTE(__printf__,1,2); void WEB_LogEvent(const char *_Nonnull, ...) FORMAT_ATTRIBUTE(__printf__,1,2); /* * Formatted output */ void WEB_Printf(WEB_Query *_Nonnull, const char *_Nonnull, ...) FORMAT_ATTRIBUTE(__printf__,2,3); void WEB_PutJSON(WEB_Query *_Nonnull, const char *_Nonnull, const char *_Nonnull, ...) FORMAT_ATTRIBUTE(__printf__,3,4); int WEB_PutJSON_HTML(WEB_Query *_Nonnull, const char *_Nonnull, const char *_Nonnull); void WEB_VAR_FilterDocument(WEB_Query *_Nonnull, const char *_Nonnull, AG_Size); void WEB_VAR_FilterFragment(WEB_Query *_Nonnull, const char *_Nonnull, AG_Size); WEB_Variable *_Nonnull WEB_VAR_Set(const char *_Nullable, const char *_Nullable, ...) FORMAT_ATTRIBUTE(__printf__, 2,3); WEB_Variable *_Nonnull WEB_VAR_SetS(const char *_Nullable, const char *_Nullable); WEB_Variable *_Nonnull WEB_VAR_SetS_NODUP(const char *_Nullable, char *_Nonnull); WEB_Variable *_Nonnull WEB_VAR_SetGlobal(const char *_Nonnull, const char *_Nullable, ...) FORMAT_ATTRIBUTE(__printf__,2,3); WEB_Variable *_Nonnull WEB_VAR_SetGlobalS(const char *_Nonnull, const char *_Nonnull); void WEB_VAR_Cat(WEB_Variable *_Nonnull, const char *_Nonnull, ...) FORMAT_ATTRIBUTE(__printf__,2,3); void WEB_VAR_Unset(const char *_Nonnull); void WEB_VAR_Wipe(const char *_Nonnull); int WEB_VAR_Defined(const char *_Nonnull) _Pure_Attribute; void WEB_VAR_Free(WEB_Variable *_Nonnull); int WEB_OutputHTML(WEB_Query *_Nonnull, const char *_Nonnull); void WEB_OutputError(WEB_Query *_Nonnull, const char *_Nonnull); void WEB_SetErrorS(const char *_Nonnull); void WEB_SetError(const char *_Nonnull, ...) FORMAT_ATTRIBUTE(__printf__,1,2); void WEB_SetSuccess(const char *_Nonnull, ...) FORMAT_ATTRIBUTE(__printf__,1,2); /* * Authentication and Session Management */ void WEB_SessionInit(WEB_Session *_Nonnull, const WEB_SessionOps *_Nonnull); void WEB_SessionDestroy(WEB_Session *_Nonnull); void WEB_CloseSession(WEB_Session *_Nonnull); int WEB_SessionLoad(void *_Nonnull, const char *_Nonnull); int WEB_SessionSaveToFD(void *_Nonnull, int); #define WEB_SessionSave(sess) WEB_SessionSaveToFD((sess),-1) void WEB_SetSV_S(void *_Nonnull, const char *_Nonnull, const char *_Nonnull); void WEB_SetSV(void *_Nonnull, const char *_Nonnull, const char *_Nonnull, ...) FORMAT_ATTRIBUTE(__printf__,3,4); int WEB_SetSV_ALL(const WEB_SessionOps *_Nonnull, const char *_Nonnull, const char *_Nonnull, const char *_Nonnull); #ifdef HAVE_SETPROCTITLE #define WEB_SetProcTitle setproctitle #else void WEB_SetProcTitle(const char *_Nonnull, ...) FORMAT_ATTRIBUTE(__printf__,1,2); #endif /* * Push Events */ int WEB_PostEventS(const char *_Nullable, _Nullable WEB_EventFilterFn, const void *_Nullable, const char *_Nonnull, const char *_Nonnull); int WEB_PostEvent(const char *_Nullable, _Nullable WEB_EventFilterFn, const void *_Nullable, const char *_Nonnull, const char *_Nonnull, ...) FORMAT_ATTRIBUTE(__printf__,5,6); __END_DECLS static __inline__ void WEB_SetHomeOp(const char *_Nonnull op) { AG_Strlcpy(webHomeOp, op, sizeof(webHomeOp)); } static __inline__ void WEB_SessionFree(WEB_Session *_Nonnull S) { WEB_SessionDestroy(S); AG_Free(S); } static __inline__ WEB_Variable *_Nonnull WEB_VAR_New(const char *_Nullable key) { return WEB_VAR_SetS(key, NULL); } static __inline__ void WEB_VAR_Grow(WEB_Variable *_Nonnull V, AG_Size len) { if (V->len+len >= V->bufSize) { V->bufSize = V->len + len + WEB_VAR_BUF_GROW; V->value = AG_Realloc(V->value, V->bufSize); } } static __inline__ void WEB_VAR_CatC(WEB_Variable *_Nonnull V, const char c) { WEB_VAR_Grow(V, 1); V->value[V->len] = c; V->value[V->len+1] = '\0'; V->len++; } static __inline__ void WEB_VAR_CatS(WEB_Variable *_Nonnull V, const char *_Nonnull s) { AG_Size len; len = strlen(s); WEB_VAR_Grow(V, len+1); memcpy(&V->value[V->len], s, len+1); V->len += len; } static __inline__ void WEB_VAR_CatS_NoHTML(WEB_Variable *_Nonnull V, const char *_Nonnull s) { const char *c; WEB_VAR_Grow(V, strlen(s)); for (c = &s[0]; *c != '\0'; c++) { if (*c == '"') { WEB_VAR_CatS(V, """); } else if (*c == '&') { WEB_VAR_CatS(V, "&"); } else if (*c == '<') { WEB_VAR_CatS(V, "<"); } else if (*c == '>') { WEB_VAR_CatS(V, ">"); } else { WEB_VAR_CatC(V, *c); } } } static __inline__ void WEB_VAR_CatS_NODUP(WEB_Variable *_Nonnull V, char *_Nonnull s) { AG_Size len = strlen(s); WEB_VAR_Grow(V, len+1); memcpy(&V->value[V->len], s, len+1); V->len += len; AG_Free(s); } static __inline__ void WEB_VAR_CatJS(WEB_Variable *_Nonnull V, const char *_Nonnull s) { const char *c; WEB_VAR_Grow(V, strlen(s)+3); WEB_VAR_CatC(V, '"'); for (c = &s[0]; *c != '\0'; c++) { if (*c == '\\') { WEB_VAR_CatS(V, "\\\\"); } else if (*c == '"') { WEB_VAR_CatS(V, "\\\""); } else { WEB_VAR_CatC(V, *c); } } WEB_VAR_CatS(V, "\","); } static __inline__ void WEB_VAR_CatJS_NODUP(WEB_Variable *_Nonnull V, char *_Nonnull s) { WEB_VAR_CatJS(V, s); AG_Free(s); } static __inline__ void WEB_VAR_CatJS_NoHTML(WEB_Variable *_Nonnull V, const char *_Nonnull s) { const char *c; WEB_VAR_Grow(V, strlen(s)+3); WEB_VAR_CatC(V, '"'); for (c = &s[0]; *c != '\0'; c++) { if (*c == '\\') { WEB_VAR_CatS(V, "\\\\"); } else if (*c == '"') { WEB_VAR_CatS(V, "\\\""); } else if (*c == '<') { WEB_VAR_CatS(V, "<"); } else if (*c == '>') { WEB_VAR_CatS(V, ">"); } else { WEB_VAR_CatC(V, *c); } } WEB_VAR_CatS(V, "\","); } static __inline__ void WEB_VAR_CatJS_NoHTML_NODUP(WEB_Variable *_Nonnull V, char *_Nonnull s) { WEB_VAR_CatJS_NoHTML(V, s); AG_Free(s); } static __inline__ void WEB_VAR_CatN(WEB_Variable *_Nonnull V, const void *_Nonnull s, AG_Size len) { WEB_VAR_Grow(V, len+1); memcpy(&V->value[V->len], s, len); V->value[V->len + len - 1] = '\0'; V->len += len; } static __inline__ void WEB_VAR_CatN_NoNUL(WEB_Variable *_Nonnull V, const void *_Nonnull s, AG_Size len) { WEB_VAR_Grow(V, len+1); memcpy(&V->value[V->len], s, len); V->len += len; } static __inline__ char *_Nullable _Pure_Attribute WEB_VAR_Get(const char *_Nonnull key) { WEB_Variable *V; AG_TAILQ_FOREACH(V, &webVars, vars) { if (V->key[0] != '\0' && strcmp(V->key, key) == 0) break; } return (V != NULL) ? V->value : NULL; } /* Set an integer range (and collapse to a single integer if min=max). */ static __inline__ WEB_Variable *_Nonnull WEB_VAR_SetIntRange(const char *_Nullable key, int min, int max) { WEB_Variable *V; if (min == max) { V = WEB_VAR_Set(key, "%d", min); } else { V = WEB_VAR_Set(key, "%d-%d", min, max); } return (V); } /* Set a float range (and collapse to a single number if min=max). */ static __inline__ WEB_Variable *_Nonnull WEB_VAR_SetFloatRange(const char *_Nullable key, float min, float max) { WEB_Variable *V; if (min == max) { V = WEB_VAR_Set(key, "%.0f", min); } else { V = WEB_VAR_Set(key, "%.0f-%.0f", min, max); } return (V); } /* Set a double range (and collapse to a single number if min=max). */ static __inline__ WEB_Variable *_Nonnull WEB_VAR_SetDoubleRange(const char *_Nullable key, double min, double max) { WEB_Variable *V; if (min == max) { V = WEB_VAR_Set(key, "%.0f", min); } else { V = WEB_VAR_Set(key, "%.0f-%.0f", min, max); } return (V); } /* Get a pointer to the named argument. Return NULL if undefined. */ static __inline__ const WEB_Argument *_Nullable _Pure_Attribute WEB_GetArgument(const WEB_Query *_Nonnull q, const char *_Nonnull key) { const WEB_Argument *arg; AG_TAILQ_FOREACH(arg, &q->args, args) { if (strcmp(arg->key, key) == 0) break; } return (arg); } /* Standard read loop. Read up to len bytes while checking signals. */ static __inline__ int WEB_SYS_Read(int fd, void *_Nonnull data, AG_Size len) { AG_Size nread; AG_Size rv; for (nread=0; nread < len; ) { rv = read(fd, data+nread, len-nread); if (rv == -1) { if (errno == EINTR || errno == EAGAIN) { WEB_CheckSignals(); continue; } else { AG_SetErrorS(strerror(errno)); return (-1); } } else if (rv == 0) { AG_SetErrorS("EOF"); return (-1); } nread += rv; } return (0); } /* Standard write loop. Write up to len bytes while checking signals. */ static __inline__ int WEB_SYS_Write(int fd, const void *_Nonnull data, AG_Size len) { AG_Size nwrote; AG_Size rv; for (nwrote = 0; nwrote < len; ) { rv = write(fd, data+nwrote, len-nwrote); if (rv == -1) { if (errno == EINTR || errno == EAGAIN) { WEB_CheckSignals(); continue; } else { AG_SetErrorS(strerror(errno)); return (-1); } } else if (rv == 0) { AG_SetErrorS("EOF"); return (-1); } nwrote += rv; } return (0); } static __inline__ void WEB_ClearHeaders(WEB_Query *_Nonnull q, const char *_Nonnull status) { AG_Size len = strlen(status); memcpy(q->head, status, len+1); q->headLen = len; q->headLine[0] = len; q->headLineCount = 1; } static __inline__ int WEB_WriteHeaders(int fd, WEB_Query *_Nonnull q) { q->head[q->headLen ] = '\r'; q->head[q->headLen+1] = '\n'; q->head[q->headLen+2] = '\0'; return WEB_SYS_Write(fd, q->head, q->headLen+2); } #define WEB_Read(q,data,len) WEB_SYS_Read((q)->sock,(data),(len)) /* Add data to the query response buffer. */ static __inline__ void WEB_Write(WEB_Query *_Nonnull q, const void *_Nonnull data, AG_Size len) { if (q->dataLen+len > q->dataSize) { q->dataSize += len+WEB_DATA_BUFSIZE; q->data = Realloc(q->data, q->dataSize); } memcpy(&q->data[q->dataLen], data, len); q->dataLen += len; } /* Recalculate header line offsets. */ static void WEB_UpdateHeaderLines(WEB_Query *_Nonnull q) { char *c; q->headLineCount = 0; for (c = q->head; *c != '\0'; c++) { if (*c == '\n' && c[1] != '\0') { if (q->headLineCount+1 >= WEB_HTTP_MAXHEADERS) { break; } q->headLine[q->headLineCount++] = (&c[1] - q->head); } } } /* Prepare to return the specified HTTP response code. */ static __inline__ void WEB_SetCode(WEB_Query *_Nonnull q, const char *_Nonnull code) { char httpCode[64]; char *head = q->head, *cEnd; AG_Size oldLen, newLen; AG_Strlcpy(httpCode, "HTTP/1.0 ", sizeof(httpCode)); AG_Strlcat(httpCode, code, sizeof(httpCode)); AG_Strlcat(httpCode, "\r\n", sizeof(httpCode)); cEnd = strchrnul(head, '\n'); newLen = strlen(httpCode); oldLen = (cEnd-head)+1; if ((q->headLen - oldLen + newLen) >= sizeof(q->head)-2) { AG_FatalError("SetCode too big"); } if (newLen != oldLen && (q->headLen - oldLen) > 0) { memmove(&head[newLen], &head[oldLen], (q->headLen - oldLen)); } memcpy(head, httpCode, newLen); if (newLen != oldLen) WEB_UpdateHeaderLines(q); } /* * Append the specified HTTP response header. * Existing headers are ignored. Duplicate headers are allowed. */ static __inline__ void WEB_AppendHeaderS(WEB_Query *_Nonnull q, const char *_Nonnull key, const char *_Nonnull value) { char newLine[WEB_HTTP_PER_HEADER_MAX]; char *cEnd; AG_Size newLen; if (q->headLineCount+1 >= WEB_HTTP_MAXHEADERS) { AG_FatalError("Too many headers"); } AG_Strlcpy(newLine, key, sizeof(newLine)); AG_Strlcat(newLine, ": ", sizeof(newLine)); AG_Strlcat(newLine, value, sizeof(newLine)); AG_Strlcat(newLine, "\r\n", sizeof(newLine)); newLen = strlen(newLine); if ((q->headLen + newLen + 1) >= sizeof(q->head)-2) { AG_FatalError("Header too big"); } cEnd = &q->head[q->headLen]; memcpy(cEnd, newLine, newLen+1); q->headLine[q->headLineCount++] = (cEnd - q->head); q->headLen += newLen; } /* * Prepare to return the specified HTTP response header. * Existing headers are updated. */ static __inline__ void WEB_SetHeaderS(WEB_Query *_Nonnull q, const char *_Nonnull key, const char *_Nonnull value) { AG_Size keyLen = strlen(key); char *cLine; Uint i; for (i = 0; i < q->headLineCount; i++) { cLine = &q->head[q->headLine[i]]; if (strncasecmp(cLine, key, keyLen) == 0 && cLine[keyLen ] == ':' && cLine[keyLen+1] == ' ') break; } if (i < q->headLineCount) { /* Update existing */ WEB_EditHeader(q, cLine, value); } else { /* Append new */ WEB_AppendHeaderS(q, key, value); } } static __inline__ void WEB_SetHeader(WEB_Query *_Nonnull q, const char *_Nonnull key, const char *_Nonnull fmt, ...) { char value[WEB_HTTP_PER_HEADER_MAX]; va_list ap; va_start(ap, fmt); vsnprintf(value, sizeof(value), fmt, ap); va_end(ap); WEB_SetHeaderS(q, key, value); } static __inline__ void WEB_AppendHeader(WEB_Query *_Nonnull q, const char *_Nonnull key, const char *_Nonnull fmt, ...) { char value[WEB_HTTP_PER_HEADER_MAX]; va_list ap; va_start(ap, fmt); vsnprintf(value, sizeof(value), fmt, ap); va_end(ap); WEB_AppendHeaderS(q, key, value); } /* Set the preferred compression level. */ static __inline__ void WEB_SetCompression(WEB_Query *_Nonnull q, int enable, int level) { if (enable) { q->flags &= ~(WEB_QUERY_NOCOMPRESSION); q->compressLvl = level; } else { q->flags |= WEB_QUERY_NOCOMPRESSION; } } static __inline__ void WEB_PutC(WEB_Query *_Nonnull q, char c) { WEB_Write(q, &c, 1); } static __inline__ void WEB_PutS(WEB_Query *_Nonnull q, const char *_Nonnull s) { WEB_Write(q, s, strlen(s)); } static __inline__ void WEB_PutJSON_S(WEB_Query *_Nonnull q, const char *_Nonnull key, const char *_Nonnull val) { const char *c; WEB_PutC(q, '"'); WEB_PutS(q, key); WEB_PutS(q, "\": \""); for (c = &val[0]; *c != '\0'; c++) { if (*c == '\\') { WEB_PutS(q, "\\\\"); } else if (*c == '"') { WEB_PutS(q, "\\\""); } else if (*c == '\r') { WEB_PutS(q, "\\r"); } else if (*c == '\n') { WEB_PutS(q, "\\n"); } else if (*c == '\t') { WEB_PutS(q, "\\t"); } else { WEB_PutC(q, *c); } } WEB_PutS(q, "\","); } static __inline__ void WEB_PutJSON_NoHTML_S(WEB_Query *_Nonnull q, const char *_Nonnull key, const char *_Nonnull val) { const char *c; WEB_PutC(q, '"'); WEB_PutS(q, key); WEB_PutS(q, "\": \""); for (c = &val[0]; *c != '\0'; c++) { if (*c == '\\') { WEB_PutS(q, "\\\\"); } else if (*c == '"') { WEB_PutS(q, "\\\""); } else if (*c == '\n') { WEB_PutS(q, "\\n"); } else if (*c == '\t') { WEB_PutS(q, "\\t"); } else if (*c == '<') { WEB_PutS(q, "<"); } else if (*c == '>') { WEB_PutS(q, ">"); } else { WEB_PutC(q, *c); } } WEB_PutS(q, "\","); } /* Return the named cookie's value */ static __inline__ char *_Nullable _Pure_Attribute WEB_GetCookie(WEB_Query *_Nonnull q, const char *_Nonnull name) { WEB_Cookie *ck; AG_TAILQ_FOREACH(ck, &q->cookies, cookies) { if (strcmp(ck->name, name) == 0) return (ck->value); } return (NULL); } /* Return a pointer to the named cookie */ static __inline__ WEB_Cookie *_Nullable _Pure_Attribute WEB_LookupCookie(WEB_Query *_Nonnull q, const char *_Nonnull name) { WEB_Cookie *ck; AG_TAILQ_FOREACH(ck, &q->cookies, cookies) { if (strcmp(ck->name, name) == 0) return (ck); } return (NULL); } /* Return the value of a session variable (or "" if it doesn't exists) */ static __inline__ const char *_Nonnull _Pure_Attribute WEB_GetSV(void *_Nonnull sess, const char *_Nonnull key) { WEB_Session *S = sess; WEB_SessionVar *SV; AG_TAILQ_FOREACH(SV, &S->vars, vars) { if (strcmp(SV->key, key) == 0) break; } if (SV != NULL) { return (SV->value); } else { return (""); } } #if defined(_AGAR_INTERNAL) || defined(_USE_AGAR_WEB) # define VAR WEB_Variable # define VAR_NAME_MAX WEB_VAR_NAME_MAX # define VAR_New WEB_VAR_New # define VAR_Unset WEB_VAR_Unset # define VAR_Wipe WEB_VAR_Wipe # define VAR_Defined WEB_VAR_Defined # define VAR_Free WEB_VAR_Free # define Get WEB_VAR_Get # define Set WEB_VAR_Set # define SetS WEB_VAR_SetS # define SetS_NODUP WEB_VAR_SetS_NODUP # define Cat WEB_VAR_Cat # define CatS WEB_VAR_CatS # define CatS_NODUP WEB_VAR_CatS_NODUP # define CatS_NoHTML WEB_VAR_CatS_NoHTML # define CatJS WEB_VAR_CatJS # define CatJS_NODUP WEB_VAR_CatJS_NODUP # define CatJS_NoHTML WEB_VAR_CatJS_NoHTML # define CatJS_NoHTML_NoDUP WEB_VAR_CatJS_NoHTML # define CatN WEB_VAR_CatN # define CatN_NoNUL WEB_VAR_CatN_NoNUL # define CatC WEB_VAR_CatC # define SetGlobal WEB_VAR_SetGlobal # define SetGlobalS WEB_VAR_SetGlobalS # define SetIntRange WEB_VAR_SetIntRange # define SetFloatRange WEB_VAR_SetFloatRange # define SetDoubleRange WEB_VAR_SetDoubleRange # define GetSV WEB_GetSV # define SetSV WEB_SetSV # define SetSV_S WEB_SetSV_S # define SESSION(p) WEB_SESSION(p) #endif /* _USE_AGAR_WEB */ #include #endif /* AG_WEB */ #endif /* _AGAR_NET_WEB_H_ */