/* * The contents of this file are subject to the AOLserver Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://aolserver.com/. * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is AOLserver Code and related documentation * distributed by AOL. * * The Initial Developer of the Original Code is America Online, * Inc. Portions created by AOL are Copyright (C) 1999 America Online, * Inc. All Rights Reserved. * * Alternatively, the contents of this file may be used under the terms * of the GNU General Public License (the "GPL"), in which case the * provisions of GPL are applicable instead of those above. If you wish * to allow use of your version of this file only under the terms of the * GPL and not to allow others to use your version of this file under the * License, indicate your decision by deleting the provisions above and * replace them with the notice and other provisions required by the GPL. * If you do not delete the provisions above, a recipient may use your * version of this file under either the License or the GPL. */ /* * form.c -- * * Routines for dealing with HTML FORM's. */ static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsd/form.c,v 1.19 2005/07/18 23:33:06 jgdavidson Exp $, compiled: " __DATE__ " " __TIME__; #include "nsd.h" static void ParseQuery(char *form, char *formend, Ns_Set *set, Tcl_Encoding encoding); static void ParseMultiInput(Conn *connPtr, char *form, Tcl_Encoding encoding, char *start, char *end); static char *Ext2Utf(Tcl_DString *dsPtr, char *s, int len, Tcl_Encoding encoding); static int GetBoundary(Tcl_DString *dsPtr, Ns_Conn *conn); static char *NextBoundry(Tcl_DString *dsPtr, char *s, char *e); static int GetValue(char *hdr, char *att, char **vsPtr, char **vePtr); /* *---------------------------------------------------------------------- * * Ns_ConnGetQuery -- * * Get the connection query data, either by reading the content * of a POST request or get it from the query string * * Results: * Query data or NULL if error * * Side effects: * * *---------------------------------------------------------------------- */ Ns_Set * Ns_ConnGetQuery(Ns_Conn *conn) { Conn *connPtr = (Conn *) conn; Tcl_Encoding encoding; Tcl_DString bound; char *s, *e, *form, *formend; if (!NsCheckQuery(conn)) { Ns_ConnClearQuery(conn); } if (connPtr->query == NULL) { encoding = connPtr->queryEncoding = Ns_ConnGetUrlEncoding(conn); connPtr->query = Ns_SetCreate(NULL); if (!STREQ(connPtr->request->method, "POST")) { form = connPtr->request->query; if (form != NULL) { ParseQuery(form, NULL, connPtr->query, encoding); } } else if ((form = Ns_ConnContent(conn)) != NULL) { Tcl_DStringInit(&bound); formend = form + connPtr->contentLength; if (!GetBoundary(&bound, conn)) { ParseQuery(form, formend, connPtr->query, encoding); } else { s = NextBoundry(&bound, form, formend); while (s != NULL) { s += bound.length; if (*s == '\r') ++s; if (*s == '\n') ++s; e = NextBoundry(&bound, s, formend); if (e != NULL) { ParseMultiInput(connPtr, form, encoding, s, e); } s = e; } } Tcl_DStringFree(&bound); } } return connPtr->query; } /* *---------------------------------------------------------------------- * * Ns_ConnClearQuery -- * * Release the any query set cached up from a previous call * to Ns_ConnGetQuery. Useful if the query data requires * reparsing, as when the encoding changes. * * Results: * Query data or NULL if error * * Side effects: * * *---------------------------------------------------------------------- */ void Ns_ConnClearQuery(Ns_Conn *conn) { Conn *connPtr = (Conn *) conn; Tcl_HashEntry *hPtr; Tcl_HashSearch search; Ns_ConnFile *filePtr; if (conn == NULL || connPtr->query == NULL) { return; } Ns_SetFree(connPtr->query); connPtr->query = NULL; connPtr->queryEncoding = NULL; hPtr = Tcl_FirstHashEntry(&connPtr->files, &search); while (hPtr != NULL) { filePtr = Tcl_GetHashValue(hPtr); Ns_SetFree(filePtr->headers); ns_free(filePtr); hPtr = Tcl_NextHashEntry(&search); } Tcl_DeleteHashTable(&connPtr->files); Tcl_InitHashTable(&connPtr->files, TCL_STRING_KEYS); } /* *---------------------------------------------------------------------- * * Ns_ConnGetFile -- * * Return the Ns_ConnFile struct for given filename contained * in a multipart form POST. * * Results: * Pointer to Ns_ConnFile or NULL if no such file. * * Side effects: * None. * *---------------------------------------------------------------------- */ Ns_ConnFile * Ns_ConnGetFile(Ns_Conn *conn, char *file) { Conn *connPtr = (Conn *) conn; Tcl_HashEntry *hPtr; if (Ns_ConnGetQuery(conn) != NULL) { hPtr = Tcl_FindHashEntry(&connPtr->files, file); if (hPtr != NULL) { return Tcl_GetHashValue(hPtr); } } return NULL; } /* *---------------------------------------------------------------------- * * Ns_ConnFirstFile -- * * Begin a search of all files contained within a multipart * browser upload. Note the order of the files are stored * in a hash table and the order returned is random. * * Results: * Pointer to first Ns_ConnFile or NULL if no files present. * * Side effects: * Contents of the Tcl_HashSearch struct pointed to by searchPtr * will be initialized and updated for the search. This * parameter must be passed to subsequent calls to * Ns_ConnNextFile. * *---------------------------------------------------------------------- */ Ns_ConnFile * Ns_ConnFirstFile(Ns_Conn *conn, Tcl_HashSearch *searchPtr) { Conn *connPtr = (Conn *) conn; Tcl_HashEntry *hPtr; if (Ns_ConnGetQuery(conn) != NULL) { hPtr = Tcl_FirstHashEntry(&connPtr->files, searchPtr); if (hPtr != NULL) { return Tcl_GetHashValue(hPtr); } } return NULL; } /* *---------------------------------------------------------------------- * * Ns_ConnNextFile -- * * Return the next Ns_ConnFile in a search of all files. * * Results: * Pointer to next Ns_ConnFile or NULL when search is complete. * * Side effects: * None. * *---------------------------------------------------------------------- */ Ns_ConnFile * Ns_ConnNextFile(Tcl_HashSearch *searchPtr) { Tcl_HashEntry *hPtr; hPtr = Tcl_NextHashEntry(searchPtr); return (hPtr ? Tcl_GetHashValue(hPtr) : NULL); } /* *---------------------------------------------------------------------- * * Ns_QueryToSet -- * * Parse query data into an Ns_Set * * Results: * NS_OK. * * Side effects: * Will add data to set without any UTF conversion. * *---------------------------------------------------------------------- */ int Ns_QueryToSet(char *query, Ns_Set *set) { ParseQuery(query, NULL, set, NULL); return NS_OK; } /* *---------------------------------------------------------------------- * * NsTclParseQueryObjCmd -- * * This procedure implements the AOLserver Tcl * * ns_parsequery querystring * * command. * * Results: * The Tcl result is a Tcl set with the parsed name-value pairs from * the querystring argument * * Side effects: * None external. * *---------------------------------------------------------------------- */ int NsTclParseQueryObjCmd(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj **objv) { Ns_Set *set; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "querystring"); return TCL_ERROR; } set = Ns_SetCreate(NULL); if (Ns_QueryToSet(Tcl_GetString(objv[1]), set) != NS_OK) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "could not parse: \"", Tcl_GetString(objv[1]), "\"", NULL); Ns_SetFree(set); return TCL_ERROR; } return Ns_TclEnterSet(interp, set, NS_TCL_SET_DYNAMIC); } /* *---------------------------------------------------------------------- * * NsCheckQuery -- * * Validate the connection query was decoded with the current * URL encoding. * * Results: * 1 if query is valid, 0 otherwise. * * Side effects: * None. * *---------------------------------------------------------------------- */ int NsCheckQuery(Ns_Conn *conn) { Conn *connPtr = (Conn *) conn; if (connPtr->queryEncoding != Ns_ConnGetUrlEncoding(conn)) { return 0; } return 1; } /* *---------------------------------------------------------------------- * * ParseQuery -- * * Parse the given form string for URL encoded key=value pairs, * converting to UTF if given encoding is not NULL. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void ParseQuery(char *form, char *formend, Ns_Set *set, Tcl_Encoding encoding) { char *next, *key, *value; Tcl_DString kds, vds, eds; int len; if (formend == NULL) { formend = form + strlen(form); } Tcl_DStringInit(&kds); Tcl_DStringInit(&vds); Tcl_DStringInit(&eds); next = form; while (next != NULL) { key = next; len = formend - key; next = memchr(next, '&', len); if (next != NULL) { *next = '\0'; } else { key = Tcl_DStringAppend(&eds, key, len); } value = memchr(key, '=', len); if (value != NULL) { *value = '\0'; } Ns_DStringTrunc(&kds, 0); key = Ns_DecodeUrlWithEncoding(&kds, key, encoding); if (value != NULL) { Ns_DStringTrunc(&vds, 0); Ns_DecodeUrlWithEncoding(&vds, value+1, encoding); *value = '='; value = vds.string; } Ns_SetPut(set, key, value); if (next != NULL) { *next++ = '&'; } } Tcl_DStringFree(&kds); Tcl_DStringFree(&vds); Tcl_DStringFree(&eds); } /* *---------------------------------------------------------------------- * * ParseMulitInput -- * * Parse the a multipart form input. * * Results: * None. * * Side effects: * Records offset, lengths for files. * *---------------------------------------------------------------------- */ static void ParseMultiInput(Conn *connPtr, char *form, Tcl_Encoding encoding, char *start, char *end) { Tcl_DString kds, vds; Tcl_HashEntry *hPtr; Ns_ConnFile *filePtr; char *s, *e, *ks, *ke, *fs, *fe, save, saveend; char *key, *value, *disp; Ns_Set *set; int new; Tcl_DStringInit(&kds); Tcl_DStringInit(&vds); set = Ns_SetCreate(NULL); /* * Trim off the trailing \r\n and null terminate the input. */ if (end > start && end[-1] == '\n') --end; if (end > start && end[-1] == '\r') --end; saveend = *end; *end = '\0'; /* * Parse header lines */ ks = fs = NULL; while ((e = strchr(start, '\n')) != NULL) { s = start; start = e + 1; if (e > s && e[-1] == '\r') { --e; } if (s == e) { break; } save = *e; *e = '\0'; Ns_ParseHeader(set, s, ToLower); *e = save; } /* * Look for valid disposition header. */ disp = Ns_SetGet(set, "content-disposition"); if (disp != NULL && GetValue(disp, "name=", &ks, &ke)) { key = Ext2Utf(&kds, ks, ke-ks, encoding); if (!GetValue(disp, "filename=", &fs, &fe)) { value = Ext2Utf(&vds, start, end-start, encoding); } else { value = Ext2Utf(&vds, fs, fe-fs, encoding); hPtr = Tcl_CreateHashEntry(&connPtr->files, key, &new); if (new) { filePtr = ns_malloc(sizeof(Ns_ConnFile)); filePtr->name = Tcl_GetHashKey(&connPtr->files, hPtr); filePtr->headers = set; filePtr->offset = start - form; filePtr->length = end - start; Tcl_SetHashValue(hPtr, filePtr); set = NULL; } } Ns_SetPut(connPtr->query, key, value); } /* * Restore the end marker. */ *end = saveend; Tcl_DStringFree(&kds); Tcl_DStringFree(&vds); if (set != NULL) { Ns_SetFree(set); } } /* *---------------------------------------------------------------------- * * GetBoundary -- * * Copy multipart/form-data boundy string, if any. * * Results: * 1 if boundy copied, 0 otherwise. * * Side effects: * Copies boundry string to given dstring. * *---------------------------------------------------------------------- */ static int GetBoundary(Tcl_DString *dsPtr, Ns_Conn *conn) { char *type, *bs, *be; type = Ns_SetIGet(conn->headers, "content-type"); if (type != NULL && Ns_StrCaseFind(type, "multipart/form-data") != NULL && (bs = Ns_StrCaseFind(type, "boundary=")) != NULL) { bs += 9; be = bs; while (*be && !isspace(UCHAR(*be))) { ++be; } Tcl_DStringAppend(dsPtr, "--", 2); Tcl_DStringAppend(dsPtr, bs, be-bs); return 1; } return 0; } /* *---------------------------------------------------------------------- * * NextBoundary -- * * Locate the next form boundry. * * Results: * Pointer to start of next input field or NULL on end of fields. * * Side effects: * None. * *---------------------------------------------------------------------- */ static char * NextBoundry(Tcl_DString *dsPtr, char *s, char *e) { char c, sc, *find; size_t len; find = dsPtr->string; c = *find++; len = dsPtr->length-1; e -= len; do { do { sc = *s++; if (s > e) { return NULL; } } while (sc != c); } while (strncmp(s, find, len) != 0); s--; return s; } /* *---------------------------------------------------------------------- * * GetValue -- * * Determine start and end of a multipart form input value. * * Results: * 1 if attribute found and value parsed, 0 otherwise. * * Side effects: * Start and end are stored in given pointers. * *---------------------------------------------------------------------- */ static int GetValue(char *hdr, char *att, char **vsPtr, char **vePtr) { char *s, *e; s = Ns_StrCaseFind(hdr, att); if (s == NULL) { return 0; } s += strlen(att); e = s; if (*s != '"' && *s != '\'') { /* NB: End of unquoted att=value is next space. */ while (*e && !isspace(UCHAR(*e))) { ++e; } } else { /* NB: End of quoted att="value" is next quote. */ ++e; while (*e && *e != *s) { ++e; } ++s; } *vsPtr = s; *vePtr = e; return 1; } /* *---------------------------------------------------------------------- * * Ext2Utf -- * * Convert input string to UTF. * * Results: * Pointer to converted string. * * Side effects: * Converted string is copied to given dstring, overwriting * any previous content. * *---------------------------------------------------------------------- */ static char * Ext2Utf(Tcl_DString *dsPtr, char *start, int len, Tcl_Encoding encoding) { if (encoding == NULL) { Tcl_DStringTrunc(dsPtr, 0); Tcl_DStringAppend(dsPtr, start, len); } else { /* NB: ExternalToUtfDString will re-init dstring. */ Tcl_DStringFree(dsPtr); Tcl_ExternalToUtfDString(encoding, start, len, dsPtr); } return dsPtr->string; }