File: | d/form.c |
Warning: | line 617, column 22 Access out-of-bound array element (buffer overflow) |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* | |||
2 | * The contents of this file are subject to the Mozilla Public License | |||
3 | * Version 1.1 (the "License"); you may not use this file except in | |||
4 | * compliance with the License. You may obtain a copy of the License at | |||
5 | * http://mozilla.org/. | |||
6 | * | |||
7 | * Software distributed under the License is distributed on an "AS IS" | |||
8 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See | |||
9 | * the License for the specific language governing rights and limitations | |||
10 | * under the License. | |||
11 | * | |||
12 | * The Original Code is AOLserver Code and related documentation | |||
13 | * distributed by AOL. | |||
14 | * | |||
15 | * The Initial Developer of the Original Code is America Online, | |||
16 | * Inc. Portions created by AOL are Copyright (C) 1999 America Online, | |||
17 | * Inc. All Rights Reserved. | |||
18 | * | |||
19 | * Alternatively, the contents of this file may be used under the terms | |||
20 | * of the GNU General Public License (the "GPL"), in which case the | |||
21 | * provisions of GPL are applicable instead of those above. If you wish | |||
22 | * to allow use of your version of this file only under the terms of the | |||
23 | * GPL and not to allow others to use your version of this file under the | |||
24 | * License, indicate your decision by deleting the provisions above and | |||
25 | * replace them with the notice and other provisions required by the GPL. | |||
26 | * If you do not delete the provisions above, a recipient may use your | |||
27 | * version of this file under either the License or the GPL. | |||
28 | */ | |||
29 | ||||
30 | /* | |||
31 | * form.c -- | |||
32 | * | |||
33 | * Routines for dealing with HTML FORM's. | |||
34 | */ | |||
35 | ||||
36 | #include "nsd.h" | |||
37 | ||||
38 | /* | |||
39 | * Local functions defined in this file. | |||
40 | */ | |||
41 | ||||
42 | static Ns_ReturnCode ParseQuery(char *form, Ns_Set *set, Tcl_Encoding encoding, bool_Bool translate) | |||
43 | NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))); | |||
44 | ||||
45 | static Ns_ReturnCode ParseQueryWithFallback(Tcl_Interp *interp, NsServer *servPtr, | |||
46 | char *toParse, Ns_Set *set, | |||
47 | Tcl_Encoding encoding, bool_Bool translate, | |||
48 | Tcl_Obj *fallbackCharsetObj) | |||
49 | NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(3)__attribute__((__nonnull__(3))) NS_GNUC_NONNULL(4)__attribute__((__nonnull__(4))); | |||
50 | ||||
51 | static Ns_ReturnCode ParseMultipartEntry(Conn *connPtr, Tcl_Encoding valueEncoding, const char *start, char *end) | |||
52 | NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(3)__attribute__((__nonnull__(3))) NS_GNUC_NONNULL(4)__attribute__((__nonnull__(4))); | |||
53 | ||||
54 | static char *Ext2utf(Tcl_DString *dsPtr, const char *start, size_t len, Tcl_Encoding encoding, char unescape) | |||
55 | NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))); | |||
56 | ||||
57 | static bool_Bool GetBoundary(Tcl_DString *dsPtr, const char *contentType) | |||
58 | NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))); | |||
59 | ||||
60 | static char *NextBoundary(const Tcl_DString *dsPtr, char *s, const char *e) | |||
61 | NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))) NS_GNUC_PURE; | |||
62 | ||||
63 | static bool_Bool GetValue(const char *hdr, const char *att, const char **vsPtr, const char **vePtr, char *uPtr) | |||
64 | NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))) NS_GNUC_NONNULL(3)__attribute__((__nonnull__(3))) NS_GNUC_NONNULL(4)__attribute__((__nonnull__(4))) NS_GNUC_NONNULL(5)__attribute__((__nonnull__(5))); | |||
65 | ||||
66 | ||||
67 | ||||
68 | /* | |||
69 | *---------------------------------------------------------------------- | |||
70 | * | |||
71 | * Ns_ConnGetQuery -- | |||
72 | * | |||
73 | * Return the connection query data in form of an Ns_Set. This function | |||
74 | * parses the either the query (of the request URL) or the form content | |||
75 | * (in POST requests with content type "www-form-urlencoded" or | |||
76 | * "multipart/form-data"). In case the Ns_Set for the query is already | |||
77 | * set, it is treated as cached result and is returned untouched. | |||
78 | * | |||
79 | * Results: | |||
80 | * Query data or NULL if no form data is available. | |||
81 | * | |||
82 | * Side effects: | |||
83 | * None. | |||
84 | * | |||
85 | *---------------------------------------------------------------------- | |||
86 | */ | |||
87 | ||||
88 | Ns_Set * | |||
89 | Ns_ConnGetQuery(Tcl_Interp *interp, Ns_Conn *conn, Tcl_Obj *fallbackCharsetObj, Ns_ReturnCode *rcPtr) | |||
90 | { | |||
91 | Conn *connPtr; | |||
92 | ||||
93 | NS_NONNULL_ASSERT(conn != NULL)((void) (0)); | |||
94 | connPtr = (Conn *) conn; | |||
95 | ||||
96 | /* | |||
97 | * connPtr->query is used to cache the result, in case this function is | |||
98 | * called multiple times during a single request. | |||
99 | */ | |||
100 | if (connPtr->query == NULL((void*)0)) { | |||
| ||||
101 | const char *contentType, *charset = NULL((void*)0); | |||
102 | char *content = NULL((void*)0), *toParse = NULL((void*)0); | |||
103 | size_t charsetOffset; | |||
104 | bool_Bool haveFormData = NS_FALSE0; | |||
105 | Ns_ReturnCode status = NS_OK; | |||
106 | ||||
107 | /* | |||
108 | * We are called the first time, so create an ns_set named (slightly | |||
109 | * misleading "connPtr->query") | |||
110 | */ | |||
111 | if (connPtr->formData == NULL((void*)0)) { | |||
112 | connPtr->formData = Ns_SetCreate(NS_SET_NAME_QUERY"query"); | |||
113 | } | |||
114 | connPtr->query = connPtr->formData; | |||
115 | contentType = Ns_SetIGet(connPtr->headers, "content-type"); | |||
116 | ||||
117 | if (contentType != NULL((void*)0)) { | |||
118 | charset = NsFindCharset(contentType, &charsetOffset); | |||
119 | if (strncmp(contentType, "application/x-www-form-urlencoded", 33u) == 0) { | |||
120 | haveFormData = NS_TRUE1; | |||
121 | } else if (strncmp(contentType, "multipart/form-data", 19u) == 0) { | |||
122 | haveFormData = NS_TRUE1; | |||
123 | } | |||
124 | } | |||
125 | ||||
126 | if (haveFormData
| |||
127 | /* | |||
128 | * It is unsafe to access the content when the | |||
129 | * connection is already closed due to potentially | |||
130 | * unmmapped memory. | |||
131 | */ | |||
132 | if ((connPtr->flags & NS_CONN_CLOSED0x001u) == 0u) { | |||
133 | content = connPtr->reqPtr->content; | |||
134 | } else { | |||
135 | /* | |||
136 | * Formdata is unavailable, but do not fall back to the | |||
137 | * query-as-formdata tradition. We should keep a consistent | |||
138 | * behavior. | |||
139 | */ | |||
140 | } | |||
141 | } else if (connPtr->request.query != NULL((void*)0)) { | |||
142 | /* | |||
143 | * The content has none of the "FORM" content types, so get it | |||
144 | * in good old AOLserver tradition from the query variables. | |||
145 | */ | |||
146 | toParse = connPtr->request.query; | |||
147 | status = ParseQueryWithFallback(interp, connPtr->poolPtr->servPtr, | |||
148 | toParse, connPtr->query, connPtr->urlEncoding, | |||
149 | NS_FALSE0, fallbackCharsetObj); | |||
150 | } | |||
151 | ||||
152 | if (content != NULL((void*)0)) { | |||
153 | Tcl_DString boundaryDs; | |||
154 | /* | |||
155 | * We have one of the accepted content types AND the data is | |||
156 | * provided via content string. | |||
157 | */ | |||
158 | Tcl_DStringInit(&boundaryDs); | |||
159 | ||||
160 | if (*contentType == 'a') { | |||
161 | /* | |||
162 | * The content-type is "application/x-www-form-urlencoded" | |||
163 | */ | |||
164 | bool_Bool translate; | |||
165 | Tcl_Encoding encoding; | |||
166 | #ifdef _WIN32 | |||
167 | /* | |||
168 | * Keep CRLF | |||
169 | */ | |||
170 | translate = NS_FALSE0; | |||
171 | #else | |||
172 | /* | |||
173 | * Translate CRLF -> LF, since browsers translate all | |||
174 | * LF to CRLF in the body of POST requests. | |||
175 | */ | |||
176 | translate = NS_TRUE1; | |||
177 | #endif | |||
178 | if (charset != NULL((void*)0)) { | |||
179 | encoding = Ns_GetCharsetEncoding(charset); | |||
180 | } else { | |||
181 | encoding = connPtr->urlEncoding; | |||
182 | } | |||
183 | toParse = content; | |||
184 | status = ParseQueryWithFallback(interp, connPtr->poolPtr->servPtr, | |||
185 | content, connPtr->query, encoding, | |||
186 | translate, fallbackCharsetObj); | |||
187 | ||||
188 | } else if (GetBoundary(&boundaryDs, contentType)) { | |||
189 | /* | |||
190 | * GetBoundary cares for "multipart/form-data; boundary=...". | |||
191 | */ | |||
192 | const char *formEndPtr = content + connPtr->reqPtr->length; | |||
193 | char *firstBoundary = NextBoundary(&boundaryDs, content, formEndPtr), *s; | |||
194 | Tcl_Encoding valueEncoding = connPtr->urlEncoding; | |||
195 | ||||
196 | s = firstBoundary; | |||
197 | for (;;) { | |||
198 | const char *defaultCharset; | |||
199 | ||||
200 | while (s
| |||
201 | char *e; | |||
202 | ||||
203 | s += boundaryDs.length; | |||
204 | if (*s == '\r') { | |||
205 | ++s; | |||
206 | } | |||
207 | if (*s == '\n') { | |||
208 | ++s; | |||
209 | } | |||
210 | e = NextBoundary(&boundaryDs, s, formEndPtr); | |||
211 | if (e
| |||
212 | status = ParseMultipartEntry(connPtr, valueEncoding, s, e); | |||
213 | if (status == NS_ERROR) { | |||
214 | toParse = s; | |||
215 | } | |||
216 | } | |||
217 | s = e; | |||
218 | } | |||
219 | /* | |||
220 | * We have now parsed all form fields into | |||
221 | * connPtr->query. According to the HTML5 standard, we | |||
222 | * have to check for a form entry named "_charset_" | |||
223 | * specifying the "default charset". | |||
224 | * https://datatracker.ietf.org/doc/html/rfc7578#section-4.6 | |||
225 | */ | |||
226 | defaultCharset = Ns_SetGet(connPtr->query, "_charset_"); | |||
227 | if (defaultCharset != NULL((void*)0) && strcmp(defaultCharset, "utf-8") != 0) { | |||
228 | /* | |||
229 | * We got an explicit charset different from UTF-8. We | |||
230 | * have to reparse the input data. | |||
231 | */ | |||
232 | Tcl_Encoding defaultEncoding = Ns_GetCharsetEncoding(defaultCharset); | |||
233 | ||||
234 | if (valueEncoding != NULL((void*)0)) { | |||
235 | if (valueEncoding != defaultEncoding) { | |||
236 | valueEncoding = defaultEncoding; | |||
237 | s = firstBoundary; | |||
238 | Ns_SetTrunc(connPtr->query, 0u); | |||
239 | Ns_Log(Debug, "form: retry with default charset %s", defaultCharset); | |||
240 | continue; | |||
241 | } | |||
242 | } else { | |||
243 | Ns_Log(Error, "multipart form: invalid charset specified" | |||
244 | " inside of form '%s'", defaultCharset); | |||
245 | status = NS_ERROR; | |||
246 | } | |||
247 | } | |||
248 | break; | |||
249 | } | |||
250 | } | |||
251 | Tcl_DStringFree(&boundaryDs); | |||
252 | } | |||
253 | ||||
254 | if (status == NS_ERROR) { | |||
255 | Ns_Log(Warning, "formdata: could not parse '%s'", toParse); | |||
256 | Ns_ConnClearQuery(conn); | |||
257 | if (rcPtr != NULL((void*)0)) { | |||
258 | *rcPtr = status; | |||
259 | if (interp != NULL((void*)0)) { | |||
260 | Ns_TclPrintfResult(interp, | |||
261 | "cannot decode '%s'; contains invalid UTF-8", | |||
262 | toParse); | |||
263 | Tcl_SetErrorCode(interp, "NS_INVALID_UTF8", NULL((void*)0)); | |||
264 | } | |||
265 | } | |||
266 | return NULL((void*)0); | |||
267 | } | |||
268 | } | |||
269 | ||||
270 | return connPtr->query; | |||
271 | } | |||
272 | ||||
273 | ||||
274 | ||||
275 | /* | |||
276 | *---------------------------------------------------------------------- | |||
277 | * | |||
278 | * Ns_ConnClearQuery -- | |||
279 | * | |||
280 | * Release the any query set cached up from a previous call | |||
281 | * to Ns_ConnGetQuery. Useful if the query data requires | |||
282 | * reparsing, as when the encoding changes. | |||
283 | * | |||
284 | * Results: | |||
285 | * None | |||
286 | * | |||
287 | * Side effects: | |||
288 | * None. | |||
289 | * | |||
290 | *---------------------------------------------------------------------- | |||
291 | */ | |||
292 | ||||
293 | void | |||
294 | Ns_ConnClearQuery(Ns_Conn *conn) | |||
295 | { | |||
296 | Conn *connPtr; | |||
297 | ||||
298 | NS_NONNULL_ASSERT(conn != NULL)((void) (0)); | |||
299 | connPtr = (Conn *) conn; | |||
300 | ||||
301 | if (connPtr->query != NULL((void*)0)) { | |||
302 | const Tcl_HashEntry *hPtr; | |||
303 | Tcl_HashSearch search; | |||
304 | ||||
305 | Ns_SetTrunc(connPtr->query, 0); | |||
306 | connPtr->query = NULL((void*)0); | |||
307 | ||||
308 | hPtr = Tcl_FirstHashEntry(&connPtr->files, &search); | |||
309 | while (hPtr != NULL((void*)0)) { | |||
310 | FormFile *filePtr = Tcl_GetHashValue(hPtr)((hPtr)->clientData); | |||
311 | ||||
312 | if (filePtr->hdrObj != NULL((void*)0)) { | |||
313 | Tcl_DecrRefCount(filePtr->hdrObj)do { Tcl_Obj *_objPtr = (filePtr->hdrObj); if (_objPtr-> refCount-- <= 1) { TclFreeObj(_objPtr); } } while(0); | |||
314 | } | |||
315 | if (filePtr->offObj != NULL((void*)0)) { | |||
316 | Tcl_DecrRefCount(filePtr->offObj)do { Tcl_Obj *_objPtr = (filePtr->offObj); if (_objPtr-> refCount-- <= 1) { TclFreeObj(_objPtr); } } while(0); | |||
317 | } | |||
318 | if (filePtr->sizeObj != NULL((void*)0)) { | |||
319 | Tcl_DecrRefCount(filePtr->sizeObj)do { Tcl_Obj *_objPtr = (filePtr->sizeObj); if (_objPtr-> refCount-- <= 1) { TclFreeObj(_objPtr); } } while(0); | |||
320 | } | |||
321 | ns_free(filePtr); | |||
322 | ||||
323 | hPtr = Tcl_NextHashEntry(&search); | |||
324 | } | |||
325 | Tcl_DeleteHashTable(&connPtr->files); | |||
326 | Tcl_InitHashTable(&connPtr->files, TCL_STRING_KEYS(0)); | |||
327 | } | |||
328 | } | |||
329 | ||||
330 | ||||
331 | /* | |||
332 | *---------------------------------------------------------------------- | |||
333 | * | |||
334 | * Ns_QueryToSet -- | |||
335 | * | |||
336 | * Parse query data into a given Ns_Set. | |||
337 | * | |||
338 | * Results: | |||
339 | * NS_OK. | |||
340 | * | |||
341 | * Side effects: | |||
342 | * Will add data to set. | |||
343 | * | |||
344 | *---------------------------------------------------------------------- | |||
345 | */ | |||
346 | ||||
347 | Ns_ReturnCode | |||
348 | Ns_QueryToSet(char *query, Ns_Set *set, Tcl_Encoding encoding) | |||
349 | { | |||
350 | NS_NONNULL_ASSERT(query != NULL)((void) (0)); | |||
351 | NS_NONNULL_ASSERT(set != NULL)((void) (0)); | |||
352 | ||||
353 | return ParseQuery(query, set, encoding, NS_FALSE0); | |||
354 | } | |||
355 | ||||
356 | ||||
357 | /* | |||
358 | *---------------------------------------------------------------------- | |||
359 | * | |||
360 | * NsTclParseQueryObjCmd -- | |||
361 | * | |||
362 | * Implements "ns_parsequery". | |||
363 | * | |||
364 | * Results: | |||
365 | * The Tcl result is a Tcl set with the parsed name-value pairs from | |||
366 | * the querystring argument | |||
367 | * | |||
368 | * Side effects: | |||
369 | * None. | |||
370 | * | |||
371 | *---------------------------------------------------------------------- | |||
372 | */ | |||
373 | ||||
374 | int | |||
375 | NsTclParseQueryObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const* objv) | |||
376 | { | |||
377 | int result; | |||
378 | NsInterp *itPtr = clientData; | |||
379 | char *charset = NULL((void*)0), *chars = (char *)NS_EMPTY_STRING; | |||
380 | Tcl_Obj *fallbackCharsetObj = NULL((void*)0); | |||
381 | Ns_ObjvSpec lopts[] = { | |||
382 | {"-charset", Ns_ObjvString, &charset, NULL((void*)0)}, | |||
383 | {"-fallbackcharset", Ns_ObjvObj, &fallbackCharsetObj, NULL((void*)0)}, | |||
384 | {"--", Ns_ObjvBreak, NULL((void*)0), NULL((void*)0)}, | |||
385 | {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)} | |||
386 | }; | |||
387 | Ns_ObjvSpec args[] = { | |||
388 | {"querystring", Ns_ObjvString, &chars, NULL((void*)0)}, | |||
389 | {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)} | |||
390 | }; | |||
391 | ||||
392 | if (Ns_ParseObjv(lopts, args, interp, 1, objc, objv) != NS_OK) { | |||
393 | result = TCL_ERROR1; | |||
394 | ||||
395 | } else { | |||
396 | Tcl_Encoding encoding; | |||
397 | Ns_Set *set = Ns_SetCreate(NS_SET_NAME_PARSEQ"parseq"); | |||
398 | ||||
399 | if (charset != NULL((void*)0)) { | |||
400 | encoding = Ns_GetCharsetEncoding(charset); | |||
401 | } else { | |||
402 | encoding = Ns_GetUrlEncoding(NULL((void*)0)); | |||
403 | } | |||
404 | ||||
405 | if (ParseQueryWithFallback(interp, itPtr->servPtr, | |||
406 | chars, set, encoding, | |||
407 | NS_FALSE0, fallbackCharsetObj)) { | |||
408 | Ns_TclPrintfResult(interp, "could not parse query: \"%s\"", chars); | |||
409 | Tcl_SetErrorCode(interp, "NS_INVALID_UTF8", NULL((void*)0)); | |||
410 | Ns_SetFree(set); | |||
411 | result = TCL_ERROR1; | |||
412 | } else { | |||
413 | result = Ns_TclEnterSet(interp, set, NS_TCL_SET_DYNAMIC); | |||
414 | } | |||
415 | } | |||
416 | return result; | |||
417 | } | |||
418 | ||||
419 | ||||
420 | /* | |||
421 | *---------------------------------------------------------------------- | |||
422 | * | |||
423 | * ParseQuery -- | |||
424 | * | |||
425 | * Parse the given form string for URL encoded key=value pairs, | |||
426 | * converting to UTF8 if given encoding is not NULL. | |||
427 | * | |||
428 | * Results: | |||
429 | * TCL_OK or TCL_ERROR in case parsing was not possible. | |||
430 | * | |||
431 | * Side effects: | |||
432 | * None. | |||
433 | * | |||
434 | *---------------------------------------------------------------------- | |||
435 | */ | |||
436 | ||||
437 | static Ns_ReturnCode | |||
438 | ParseQuery(char *form, Ns_Set *set, Tcl_Encoding encoding, bool_Bool translate) | |||
439 | { | |||
440 | Tcl_DString kds, vds, vds2; | |||
441 | char *p; | |||
442 | Ns_ReturnCode result = NS_OK; | |||
443 | ||||
444 | NS_NONNULL_ASSERT(form != NULL)((void) (0)); | |||
445 | NS_NONNULL_ASSERT(set != NULL)((void) (0)); | |||
446 | ||||
447 | Tcl_DStringInit(&kds); | |||
448 | Tcl_DStringInit(&vds); | |||
449 | Tcl_DStringInit(&vds2); | |||
450 | p = form; | |||
451 | ||||
452 | while (p != NULL((void*)0)) { | |||
453 | char *v; | |||
454 | const char *k; | |||
455 | ||||
456 | k = p; | |||
457 | p = strchr(p, INTCHAR('&')((int)((unsigned char)(('&'))))); | |||
458 | if (p != NULL((void*)0)) { | |||
459 | *p = '\0'; | |||
460 | } | |||
461 | v = strchr(k, INTCHAR('=')((int)((unsigned char)(('='))))); | |||
462 | if (v != NULL((void*)0)) { | |||
463 | *v = '\0'; | |||
464 | } | |||
465 | Ns_DStringSetLengthTcl_DStringSetLength(&kds, 0); | |||
466 | k = Ns_UrlQueryDecode(&kds, k, encoding, &result); | |||
467 | if (v != NULL((void*)0)) { | |||
468 | Ns_DStringSetLengthTcl_DStringSetLength(&vds, 0); | |||
469 | ||||
470 | (void) Ns_UrlQueryDecode(&vds, v+1, encoding, &result); | |||
471 | *v = '='; | |||
472 | v = vds.string; | |||
473 | if (translate) { | |||
474 | char *q = strchr(v, INTCHAR('\r')((int)((unsigned char)(('\r'))))); | |||
475 | ||||
476 | if (q != NULL((void*)0)) { | |||
477 | /* | |||
478 | * We have one or more CR in the field content. | |||
479 | * Remove these. | |||
480 | */ | |||
481 | Ns_DStringSetLengthTcl_DStringSetLength(&vds2, 0); | |||
482 | do { | |||
483 | Tcl_DStringAppend(&vds2, v, (int)(q - v)); | |||
484 | v = q +1; | |||
485 | q = strchr(v, INTCHAR('\r')((int)((unsigned char)(('\r'))))); | |||
486 | } while (q != NULL((void*)0)); | |||
487 | /* | |||
488 | * Append the remaining string. | |||
489 | */ | |||
490 | Tcl_DStringAppend(&vds2, v, -1); | |||
491 | v = vds2.string; | |||
492 | } | |||
493 | } | |||
494 | } | |||
495 | if (result == TCL_OK0) { | |||
496 | (void) Ns_SetPut(set, k, v); | |||
497 | } | |||
498 | if (p != NULL((void*)0)) { | |||
499 | *p++ = '&'; | |||
500 | } | |||
501 | } | |||
502 | Tcl_DStringFree(&kds); | |||
503 | Tcl_DStringFree(&vds); | |||
504 | Tcl_DStringFree(&vds2); | |||
505 | ||||
506 | return result; | |||
507 | } | |||
508 | ||||
509 | /* | |||
510 | *---------------------------------------------------------------------- | |||
511 | * | |||
512 | * ParseQueryWithFallback -- | |||
513 | * | |||
514 | * Helper function for ParseQuery(), which handles fallback charset for | |||
515 | * cases, where converting to UTF8 fails (due to invalid UTF-8). | |||
516 | * | |||
517 | * Results: | |||
518 | * TCL_OK or TCL_ERROR in case parsing was not possible. | |||
519 | * | |||
520 | * Side effects: | |||
521 | * None. | |||
522 | * | |||
523 | *---------------------------------------------------------------------- | |||
524 | */ | |||
525 | static Ns_ReturnCode | |||
526 | ParseQueryWithFallback(Tcl_Interp *interp, NsServer *servPtr, char *toParse, | |||
527 | Ns_Set *set, Tcl_Encoding encoding, | |||
528 | bool_Bool translate, Tcl_Obj *fallbackCharsetObj) | |||
529 | { | |||
530 | Ns_ReturnCode status; | |||
531 | ||||
532 | NS_NONNULL_ASSERT(interp != NULL)((void) (0)); | |||
533 | NS_NONNULL_ASSERT(toParse != NULL)((void) (0)); | |||
534 | NS_NONNULL_ASSERT(set != NULL)((void) (0)); | |||
535 | ||||
536 | status = ParseQuery(toParse, set, encoding, translate); | |||
537 | if (status == NS_ERROR) { | |||
538 | Tcl_Encoding fallbackEncoding = NULL((void*)0); | |||
539 | Ns_ReturnCode rc; | |||
540 | ||||
541 | /* | |||
542 | * ParseQuery failed. This might be due to invalid UTF-8. Retry with | |||
543 | * fallbackCharset if specified. | |||
544 | */ | |||
545 | rc = NsGetFallbackEncoding(interp, servPtr, fallbackCharsetObj, NS_TRUE1, &fallbackEncoding); | |||
546 | if (rc == NS_OK && fallbackEncoding != NULL((void*)0) && fallbackEncoding != encoding) { | |||
547 | Ns_Log(Notice, "Retry ParseQuery with encoding %s", | |||
548 | Ns_GetEncodingCharset(fallbackEncoding)); | |||
549 | Ns_SetTrunc(set, 0u); | |||
550 | status = ParseQuery(toParse, set, fallbackEncoding, translate); | |||
551 | } | |||
552 | } | |||
553 | return status; | |||
554 | } | |||
555 | ||||
556 | ||||
557 | /* | |||
558 | *---------------------------------------------------------------------- | |||
559 | * | |||
560 | * ParseMultipartEntry -- | |||
561 | * | |||
562 | * Parse a single part of a multipart form. | |||
563 | * | |||
564 | * Results: | |||
565 | * Ns_ReturnCode (NS_OK or NS_ERROR). | |||
566 | * | |||
567 | * Side effects: | |||
568 | * Records offset, lengths for files. After execution, connPtr->query | |||
569 | * contains the parsed form in form of an Ns_Set. | |||
570 | * | |||
571 | *---------------------------------------------------------------------- | |||
572 | */ | |||
573 | ||||
574 | static Ns_ReturnCode | |||
575 | ParseMultipartEntry(Conn *connPtr, Tcl_Encoding valueEncoding, const char *start, char *end) | |||
576 | { | |||
577 | Tcl_Encoding encoding; | |||
578 | Tcl_DString kds, vds; | |||
579 | char *e, saveend, unescape; | |||
580 | const char *ks = NULL((void*)0), *ke, *disp; | |||
581 | Ns_Set *set; | |||
582 | int isNew; | |||
583 | Ns_ReturnCode status = NS_OK; | |||
584 | ||||
585 | NS_NONNULL_ASSERT(connPtr != NULL)((void) (0)); | |||
586 | NS_NONNULL_ASSERT(start != NULL)((void) (0)); | |||
587 | NS_NONNULL_ASSERT(end != NULL)((void) (0)); | |||
588 | ||||
589 | encoding = connPtr->urlEncoding; | |||
590 | ||||
591 | Tcl_DStringInit(&kds); | |||
592 | Tcl_DStringInit(&vds); | |||
593 | set = Ns_SetCreate(NS_SET_NAME_MP"mp"); | |||
594 | ||||
595 | /* | |||
596 | * Trim off the trailing \r\n and null terminate the input. | |||
597 | */ | |||
598 | ||||
599 | if (end
| |||
600 | --end; | |||
601 | } | |||
602 | if (end
| |||
603 | --end; | |||
604 | } | |||
605 | saveend = *end; | |||
606 | *end = '\0'; | |||
607 | ||||
608 | /* | |||
609 | * Parse header lines | |||
610 | */ | |||
611 | ||||
612 | while ((e = strchr(start, INTCHAR('\n')((int)((unsigned char)(('\n')))))) != NULL((void*)0)) { | |||
613 | const char *s = start; | |||
614 | char save; | |||
615 | ||||
616 | start = e + 1; | |||
617 | if (e > s && *(e-1) == '\r') { | |||
| ||||
618 | --e; | |||
619 | } | |||
620 | if (s == e) { | |||
621 | /* | |||
622 | * Reached empty line, end of header. | |||
623 | */ | |||
624 | break; | |||
625 | } | |||
626 | save = *e; | |||
627 | *e = '\0'; | |||
628 | (void) Ns_ParseHeader(set, s, NULL((void*)0), ToLower, NULL((void*)0)); | |||
629 | *e = save; | |||
630 | } | |||
631 | ||||
632 | /* | |||
633 | * Look for valid disposition header. | |||
634 | */ | |||
635 | ||||
636 | disp = Ns_SetGet(set, "content-disposition"); | |||
637 | if (disp != NULL((void*)0) && GetValue(disp, "name=", &ks, &ke, &unescape) == NS_TRUE1) { | |||
638 | const char *key = Ext2utf(&kds, ks, (size_t)(ke - ks), encoding, unescape); | |||
639 | const char *value, *fs = NULL((void*)0), *fe = NULL((void*)0); | |||
640 | ||||
641 | if (key == NULL((void*)0)) { | |||
642 | status = NS_ERROR; | |||
643 | goto bailout; | |||
644 | } | |||
645 | Ns_Log(Debug, "ParseMultipartEntry disp '%s'", disp); | |||
646 | ||||
647 | if (GetValue(disp, "filename=", &fs, &fe, &unescape) == NS_FALSE0) { | |||
648 | /* | |||
649 | * Plain (non-file) entry. | |||
650 | */ | |||
651 | if (valueEncoding == NULL((void*)0)) { | |||
652 | valueEncoding = encoding; | |||
653 | } | |||
654 | Ns_Log(Debug, "ParseMultipartEntry LINE '%s'", start); | |||
655 | value = Ext2utf(&vds, start, (size_t)(end - start), valueEncoding, unescape); | |||
656 | if (value == NULL((void*)0)) { | |||
657 | status = NS_ERROR; | |||
658 | goto bailout; | |||
659 | } | |||
660 | } else { | |||
661 | Tcl_HashEntry *hPtr; | |||
662 | FormFile *filePtr; | |||
663 | Tcl_Interp *interp = connPtr->itPtr->interp; | |||
664 | ||||
665 | assert(fs != NULL)((void) (0)); | |||
666 | value = Ext2utf(&vds, fs, (size_t)(fe - fs), encoding, unescape); | |||
667 | if (value == NULL((void*)0)) { | |||
668 | status = NS_ERROR; | |||
669 | goto bailout; | |||
670 | } | |||
671 | ||||
672 | hPtr = Tcl_CreateHashEntry(&connPtr->files, key, &isNew)(*((&connPtr->files)->createProc))(&connPtr-> files, (const char *)(key), &isNew); | |||
673 | if (isNew != 0) { | |||
674 | ||||
675 | filePtr = ns_malloc(sizeof(FormFile)); | |||
676 | Tcl_SetHashValue(hPtr, filePtr)((hPtr)->clientData = (ClientData) (filePtr)); | |||
677 | ||||
678 | filePtr->hdrObj = Tcl_NewListObj(0, NULL((void*)0)); | |||
679 | filePtr->offObj = Tcl_NewListObj(0, NULL((void*)0)); | |||
680 | filePtr->sizeObj = Tcl_NewListObj(0, NULL((void*)0)); | |||
681 | ||||
682 | Tcl_IncrRefCount(filePtr->hdrObj)++(filePtr->hdrObj)->refCount; | |||
683 | Tcl_IncrRefCount(filePtr->offObj)++(filePtr->offObj)->refCount; | |||
684 | Tcl_IncrRefCount(filePtr->sizeObj)++(filePtr->sizeObj)->refCount; | |||
685 | } else { | |||
686 | filePtr = Tcl_GetHashValue(hPtr)((hPtr)->clientData); | |||
687 | } | |||
688 | ||||
689 | (void) Ns_TclEnterSet(interp, set, NS_TCL_SET_DYNAMIC); | |||
690 | (void) Tcl_ListObjAppendElement(interp, filePtr->hdrObj, | |||
691 | Tcl_GetObjResult(interp)); | |||
692 | Tcl_ResetResult(connPtr->itPtr->interp); | |||
693 | ||||
694 | (void) Tcl_ListObjAppendElement(interp, filePtr->offObj, | |||
695 | Tcl_NewIntObj((int)(start - connPtr->reqPtr->content))); | |||
696 | ||||
697 | (void) Tcl_ListObjAppendElement(interp, filePtr->sizeObj, | |||
698 | Tcl_NewWideIntObj((Tcl_WideInt)(end - start))); | |||
699 | set = NULL((void*)0); | |||
700 | } | |||
701 | Ns_Log(Debug, "ParseMultipartEntry sets '%s': '%s'", key, value); | |||
702 | (void) Ns_SetPut(connPtr->query, key, value); | |||
703 | } | |||
704 | ||||
705 | /* | |||
706 | * Restore the end marker. | |||
707 | */ | |||
708 | bailout: | |||
709 | *end = saveend; | |||
710 | Tcl_DStringFree(&kds); | |||
711 | Tcl_DStringFree(&vds); | |||
712 | if (set != NULL((void*)0)) { | |||
713 | Ns_SetFree(set); | |||
714 | } | |||
715 | ||||
716 | return status; | |||
717 | } | |||
718 | ||||
719 | ||||
720 | /* | |||
721 | *---------------------------------------------------------------------- | |||
722 | * | |||
723 | * GetBoundary -- | |||
724 | * | |||
725 | * Copy multipart/form-data boundary string, if any. | |||
726 | * | |||
727 | * Results: | |||
728 | * NS_TRUE if boundary copied, NS_FALSE otherwise. | |||
729 | * | |||
730 | * Side effects: | |||
731 | * Copies boundary string to given dstring. | |||
732 | * | |||
733 | *---------------------------------------------------------------------- | |||
734 | */ | |||
735 | ||||
736 | static bool_Bool | |||
737 | GetBoundary(Tcl_DString *dsPtr, const char *contentType) | |||
738 | { | |||
739 | const char *bs; | |||
740 | bool_Bool success = NS_FALSE0; | |||
741 | ||||
742 | NS_NONNULL_ASSERT(dsPtr != NULL)((void) (0)); | |||
743 | NS_NONNULL_ASSERT(contentType != NULL)((void) (0)); | |||
744 | ||||
745 | if ((Ns_StrCaseFind(contentType, "multipart/form-data") != NULL((void*)0)) | |||
746 | && ((bs = Ns_StrCaseFind(contentType, "boundary=")) != NULL((void*)0))) { | |||
747 | const char *be; | |||
748 | ||||
749 | bs += 9; | |||
750 | be = bs; | |||
751 | while ((*be != '\0') && (CHARTYPE(space, *be)(((*__ctype_b_loc ())[(int) (((int)((unsigned char)(*be))))] & (unsigned short int) _ISspace)) == 0)) { | |||
752 | ++be; | |||
753 | } | |||
754 | Tcl_DStringAppend(dsPtr, "--", 2); | |||
755 | Tcl_DStringAppend(dsPtr, bs, (int)(be - bs)); | |||
756 | success = NS_TRUE1; | |||
757 | } | |||
758 | return success; | |||
759 | } | |||
760 | ||||
761 | ||||
762 | /* | |||
763 | *---------------------------------------------------------------------- | |||
764 | * | |||
765 | * NextBoundary -- | |||
766 | * | |||
767 | * Locate the next form boundary. | |||
768 | * | |||
769 | * Results: | |||
770 | * Pointer to start of next input field or NULL on end of fields. | |||
771 | * | |||
772 | * Side effects: | |||
773 | * None. | |||
774 | * | |||
775 | *---------------------------------------------------------------------- | |||
776 | */ | |||
777 | ||||
778 | static char * | |||
779 | NextBoundary(const Tcl_DString *dsPtr, char *s, const char *e) | |||
780 | { | |||
781 | char c, sc; | |||
782 | const char *find; | |||
783 | size_t len; | |||
784 | ||||
785 | NS_NONNULL_ASSERT(dsPtr != NULL)((void) (0)); | |||
786 | NS_NONNULL_ASSERT(s != NULL)((void) (0)); | |||
787 | NS_NONNULL_ASSERT(e != NULL)((void) (0)); | |||
788 | ||||
789 | find = dsPtr->string; | |||
790 | c = *find++; | |||
791 | len = (size_t)(dsPtr->length - 1); | |||
792 | e -= len; | |||
793 | do { | |||
794 | do { | |||
795 | sc = *s++; | |||
796 | if (s > e) { | |||
797 | return NULL((void*)0); | |||
798 | } | |||
799 | } while (sc != c); | |||
800 | } while (strncmp(s, find, len) != 0); | |||
801 | s--; | |||
802 | ||||
803 | return s; | |||
804 | } | |||
805 | ||||
806 | ||||
807 | /* | |||
808 | *---------------------------------------------------------------------- | |||
809 | * | |||
810 | * GetValue -- | |||
811 | * | |||
812 | * Determine start and end of a multipart form input value. | |||
813 | * | |||
814 | * Results: | |||
815 | * NS_TRUE if attribute found and value parsed, NS_FALSE otherwise. | |||
816 | * | |||
817 | * Side effects: | |||
818 | * Start and end are stored in given pointers, quoted character, | |||
819 | * when it was preceded by a backslash. | |||
820 | * | |||
821 | *---------------------------------------------------------------------- | |||
822 | */ | |||
823 | ||||
824 | static bool_Bool | |||
825 | GetValue(const char *hdr, const char *att, const char **vsPtr, const char **vePtr, char *uPtr) | |||
826 | { | |||
827 | const char *s; | |||
828 | bool_Bool success = NS_TRUE1; | |||
829 | ||||
830 | NS_NONNULL_ASSERT(hdr != NULL)((void) (0)); | |||
831 | NS_NONNULL_ASSERT(att != NULL)((void) (0)); | |||
832 | NS_NONNULL_ASSERT(vsPtr != NULL)((void) (0)); | |||
833 | NS_NONNULL_ASSERT(vePtr != NULL)((void) (0)); | |||
834 | NS_NONNULL_ASSERT(uPtr != NULL)((void) (0)); | |||
835 | ||||
836 | s = Ns_StrCaseFind(hdr, att); | |||
837 | if (s == NULL((void*)0)) { | |||
838 | success = NS_FALSE0; | |||
839 | } else { | |||
840 | const char *e; | |||
841 | ||||
842 | s += strlen(att); | |||
843 | e = s; | |||
844 | if (*s != '"' && *s != '\'') { | |||
845 | /* | |||
846 | * End of unquoted att=value is next space. | |||
847 | */ | |||
848 | while (*e != '\0' && CHARTYPE(space, *e)(((*__ctype_b_loc ())[(int) (((int)((unsigned char)(*e))))] & (unsigned short int) _ISspace)) == 0) { | |||
849 | ++e; | |||
850 | } | |||
851 | *uPtr = '\0'; | |||
852 | } else { | |||
853 | bool_Bool escaped = NS_FALSE0; | |||
854 | ||||
855 | *uPtr = '\0'; | |||
856 | /* | |||
857 | * End of quoted att="value" is next quote. A quote within | |||
858 | * the quoted string could be escaped with a backslash. In | |||
859 | * case, an escaped quote was detected, report the quote | |||
860 | * character as result. | |||
861 | */ | |||
862 | ++e; | |||
863 | while (*e != '\0' && (escaped || *e != *s)) { | |||
864 | if (escaped) { | |||
865 | escaped = NS_FALSE0; | |||
866 | } else if (*e == '\\') { | |||
867 | *uPtr = *s; | |||
868 | escaped = NS_TRUE1; | |||
869 | } | |||
870 | ++e; | |||
871 | } | |||
872 | ++s; | |||
873 | } | |||
874 | *vsPtr = s; | |||
875 | *vePtr = e; | |||
876 | } | |||
877 | ||||
878 | return success; | |||
879 | } | |||
880 | ||||
881 | ||||
882 | /* | |||
883 | *---------------------------------------------------------------------- | |||
884 | * | |||
885 | * Ext2utf -- | |||
886 | * | |||
887 | * Convert input string to UTF. | |||
888 | * | |||
889 | * Results: | |||
890 | * Pointer to converted string or NULL, when conversion to UTF-8 fails. | |||
891 | * | |||
892 | * Side effects: | |||
893 | * Converted string is copied to given dString, overwriting | |||
894 | * any previous content. | |||
895 | * | |||
896 | *---------------------------------------------------------------------- | |||
897 | */ | |||
898 | ||||
899 | static char * | |||
900 | Ext2utf(Tcl_DString *dsPtr, const char *start, size_t len, Tcl_Encoding encoding, char unescape) | |||
901 | { | |||
902 | char *buffer; | |||
903 | ||||
904 | NS_NONNULL_ASSERT(dsPtr != NULL)((void) (0)); | |||
905 | NS_NONNULL_ASSERT(start != NULL)((void) (0)); | |||
906 | ||||
907 | if (encoding == NULL((void*)0)) { | |||
908 | Tcl_DStringSetLength(dsPtr, 0); | |||
909 | Tcl_DStringAppend(dsPtr, start, (int)len); | |||
910 | buffer = dsPtr->string; | |||
911 | } else { | |||
912 | Tcl_DString ds; | |||
913 | /* | |||
914 | * Actual to UTF conversion. | |||
915 | */ | |||
916 | if (NsEncodingIsUtf8(encoding) && !Ns_Valid_UTF8((const unsigned char *)start, len, &ds)) { | |||
917 | Ns_Log(Warning, "form: multipart contains invalid UTF8: %s", ds.string); | |||
918 | Tcl_DStringFree(&ds); | |||
919 | buffer = NULL((void*)0); | |||
920 | } else { | |||
921 | /* | |||
922 | * ExternalToUtfDString will re-init dstring. | |||
923 | */ | |||
924 | Tcl_DStringFree(dsPtr); | |||
925 | (void) Tcl_ExternalToUtfDString(encoding, start, (int)len, dsPtr); | |||
926 | buffer = dsPtr->string; | |||
927 | } | |||
928 | } | |||
929 | ||||
930 | /* | |||
931 | * In case the string contains backslash escaped characters, the | |||
932 | * backslashes have to be removed. This will shorten the resulting | |||
933 | * string. | |||
934 | */ | |||
935 | if (buffer != NULL((void*)0) && unescape != '\0') { | |||
936 | int i, j, l = (int)len; | |||
937 | ||||
938 | for (i = 0; i<l; i++) { | |||
939 | if (buffer[i] == '\\' && buffer[i+1] == unescape) { | |||
940 | for (j = i; j < l; j++) { | |||
941 | buffer[j] = buffer[j+1]; | |||
942 | } | |||
943 | l --; | |||
944 | } | |||
945 | } | |||
946 | Tcl_DStringSetLength(dsPtr, l); | |||
947 | buffer = dsPtr->string; | |||
948 | } | |||
949 | ||||
950 | return buffer; | |||
951 | } | |||
952 | ||||
953 | /* | |||
954 | * Local Variables: | |||
955 | * mode: c | |||
956 | * c-basic-offset: 4 | |||
957 | * fill-column: 78 | |||
958 | * indent-tabs-mode: nil | |||
959 | * End: | |||
960 | */ |