Bug Summary

File:d/form.c
Warning:line 617, column 22
Access out-of-bound array element (buffer overflow)

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-unknown-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name form.c -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/home/isvv/naviserver/nsd -resource-dir /usr/local/lib/clang/15.0.0 -D _FORTIFY_SOURCE=2 -D NDEBUG -D SYSTEM_MALLOC -I ../include -I /usr/include/tcl8.6 -D HAVE_CONFIG_H -internal-isystem /usr/local/lib/clang/15.0.0/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/11/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -O2 -std=c99 -fdebug-compilation-dir=/home/isvv/naviserver/nsd -ferror-limit 19 -stack-protector 2 -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -analyzer-checker alpha -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /tmp/scan-build-2022-07-23-130959-11103-1 -x c form.c
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
42static 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
45static 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
51static 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
54static 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
57static 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
60static 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
63static 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
88Ns_Set *
89Ns_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)) {
1
Assuming field 'query' is equal to NULL
2
Taking true branch
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)) {
3
Assuming field 'formData' is not equal to NULL
4
Taking false branch
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)) {
5
Assuming 'contentType' is not equal to NULL
6
Taking true branch
118 charset = NsFindCharset(contentType, &charsetOffset);
119 if (strncmp(contentType, "application/x-www-form-urlencoded", 33u) == 0) {
7
Assuming the condition is false
8
Taking false branch
120 haveFormData = NS_TRUE1;
121 } else if (strncmp(contentType, "multipart/form-data", 19u) == 0) {
9
Taking true branch
122 haveFormData = NS_TRUE1;
123 }
124 }
125
126 if (haveFormData
9.1
'haveFormData' is true
) {
10
Taking true branch
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) {
11
Assuming the condition is true
12
Taking true branch
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)) {
13
Assuming 'content' is not equal to NULL
14
Taking true branch
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') {
15
Assuming the condition is false
16
Taking false branch
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)) {
17
Taking true branch
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 (;;) {
18
Loop condition is true. Entering loop body
198 const char *defaultCharset;
199
200 while (s
18.1
's' is not equal to NULL
!= NULL((void*)0)) {
19
Loop condition is true. Entering loop body
201 char *e;
202
203 s += boundaryDs.length;
204 if (*s == '\r') {
20
Assuming the condition is false
21
Taking false branch
205 ++s;
206 }
207 if (*s == '\n') {
22
Assuming the condition is false
23
Taking false branch
208 ++s;
209 }
210 e = NextBoundary(&boundaryDs, s, formEndPtr);
211 if (e
23.1
'e' is not equal to NULL
!= NULL((void*)0)) {
24
Taking true branch
212 status = ParseMultipartEntry(connPtr, valueEncoding, s, e);
25
Calling 'ParseMultipartEntry'
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
293void
294Ns_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
347Ns_ReturnCode
348Ns_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
374int
375NsTclParseQueryObjCmd(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
437static Ns_ReturnCode
438ParseQuery(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 */
525static Ns_ReturnCode
526ParseQueryWithFallback(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
574static Ns_ReturnCode
575ParseMultipartEntry(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
25.1
'end' is <= 'start'
> start && *(end-1) == '\n') {
600 --end;
601 }
602 if (end
25.2
'end' is <= 'start'
> start && *(end-1) == '\r') {
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)) {
26
Assuming the condition is true
27
Loop condition is true. Entering loop body
613 const char *s = start;
614 char save;
615
616 start = e + 1;
617 if (e > s && *(e-1) == '\r') {
28
Assuming 'e' is > 's'
29
Access out-of-bound array element (buffer overflow)
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
736static bool_Bool
737GetBoundary(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
778static char *
779NextBoundary(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
824static bool_Bool
825GetValue(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
899static char *
900Ext2utf(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 */