1 /*
2  * $LynxId: HTParse.c,v 1.70 2012/02/09 19:57:37 tom Exp $
3  *
4  *		Parse HyperText Document Address		HTParse.c
5  *		================================
6  */
7 
8 #include <HTUtils.h>
9 #include <HTParse.h>
10 
11 #include <LYUtils.h>
12 #include <LYLeaks.h>
13 #include <LYStrings.h>
14 #include <LYCharUtils.h>
15 #include <LYGlobalDefs.h>
16 
17 #ifdef HAVE_ALLOCA_H
18 #include <alloca.h>
19 #else
20 #ifdef __MINGW32__
21 #include <malloc.h>
22 #endif /* __MINGW32__ */
23 #endif
24 
25 #ifdef USE_IDNA
26 #include <idna.h>
27 #endif
28 
29 #define HEX_ESCAPE '%'
30 
31 struct struct_parts {
32     char *access;
33     char *host;
34     char *absolute;
35     char *relative;
36     char *search;		/* treated normally as part of path */
37     char *anchor;
38 };
39 
40 #if 0				/* for debugging */
41 static void show_parts(const char *name, struct struct_parts *parts, int line)
42 {
43     if (TRACE) {
44 	CTRACE((tfp, "struct_parts(%s) %s@%d\n", name, __FILE__, line));
45 	CTRACE((tfp, "   access   '%s'\n", NONNULL(parts->access)));
46 	CTRACE((tfp, "   host     '%s'\n", NONNULL(parts->host)));
47 	CTRACE((tfp, "   absolute '%s'\n", NONNULL(parts->absolute)));
48 	CTRACE((tfp, "   relative '%s'\n", NONNULL(parts->relative)));
49 	CTRACE((tfp, "   search   '%s'\n", NONNULL(parts->search)));
50 	CTRACE((tfp, "   anchor   '%s'\n", NONNULL(parts->anchor)));
51     }
52 }
53 #define SHOW_PARTS(name) show_parts(#name, &name, __LINE__)
54 #else
55 #define SHOW_PARTS(name)	/* nothing */
56 #endif
57 
58 /*	Strip white space off a string.				HTStrip()
59  *	-------------------------------
60  *
61  * On exit,
62  *	Return value points to first non-white character, or to 0 if none.
63  *	All trailing white space is OVERWRITTEN with zero.
64  */
HTStrip(char * s)65 char *HTStrip(char *s)
66 {
67 #define SPACE(c) ((c == ' ') || (c == '\t') || (c == '\n'))
68     char *p;
69 
70     for (p = s; *p; p++) {	/* Find end of string */
71 	;
72     }
73     for (p--; p >= s; p--) {
74 	if (SPACE(*p))
75 	    *p = '\0';		/* Zap trailing blanks */
76 	else
77 	    break;
78     }
79     while (SPACE(*s))
80 	s++;			/* Strip leading blanks */
81     return s;
82 }
83 
84 /*	Scan a filename for its constituents.			scan()
85  *	-------------------------------------
86  *
87  * On entry,
88  *	name	points to a document name which may be incomplete.
89  * On exit,
90  *	absolute or relative may be nonzero (but not both).
91  *	host, anchor and access may be nonzero if they were specified.
92  *	Any which are nonzero point to zero terminated strings.
93  */
scan(char * name,struct struct_parts * parts)94 static void scan(char *name,
95 		 struct struct_parts *parts)
96 {
97     char *after_access;
98     char *p;
99 
100     parts->access = NULL;
101     parts->host = NULL;
102     parts->absolute = NULL;
103     parts->relative = NULL;
104     parts->search = NULL;	/* normally not used - kw */
105     parts->anchor = NULL;
106 
107     /*
108      * Scan left-to-right for a scheme (access).
109      */
110     after_access = name;
111     for (p = name; *p; p++) {
112 	if (*p == ':') {
113 	    *p = '\0';
114 	    parts->access = name;	/* Access name has been specified */
115 	    after_access = (p + 1);
116 	    break;
117 	}
118 	if (*p == '/' || *p == '#' || *p == ';' || *p == '?')
119 	    break;
120     }
121 
122     /*
123      * Scan left-to-right for a fragment (anchor).
124      */
125     for (p = after_access; *p; p++) {
126 	if (*p == '#') {
127 	    parts->anchor = (p + 1);
128 	    *p = '\0';		/* terminate the rest */
129 	    break;		/* leave things after first # alone - kw */
130 	}
131     }
132 
133     /*
134      * Scan left-to-right for a host or absolute path.
135      */
136     p = after_access;
137     if (*p == '/') {
138 	if (p[1] == '/') {
139 	    parts->host = (p + 2);	/* host has been specified    */
140 	    *p = '\0';		/* Terminate access           */
141 	    p = strchr(parts->host, '/');	/* look for end of host name if any */
142 	    if (p != NULL) {
143 		*p = '\0';	/* Terminate host */
144 		parts->absolute = (p + 1);	/* Root has been found */
145 	    } else {
146 		p = strchr(parts->host, '?');
147 		if (p != NULL) {
148 		    *p = '\0';	/* Terminate host */
149 		    parts->search = (p + 1);
150 		}
151 	    }
152 	} else {
153 	    parts->absolute = (p + 1);	/* Root found but no host */
154 	}
155     } else {
156 	parts->relative = (*after_access) ?
157 	    after_access : NULL;	/* NULL for "" */
158     }
159 
160     /*
161      * Check schemes that commonly have unescaped hashes.
162      */
163     if (parts->access && parts->anchor &&
164     /* optimize */ strchr("lnsdLNSD", *parts->access) != NULL) {
165 	if ((!parts->host && strcasecomp(parts->access, "lynxcgi")) ||
166 	    !strcasecomp(parts->access, "nntp") ||
167 	    !strcasecomp(parts->access, "snews") ||
168 	    !strcasecomp(parts->access, "news") ||
169 	    !strcasecomp(parts->access, "data")) {
170 	    /*
171 	     * Access specified but no host and not a lynxcgi URL, so the
172 	     * anchor may not really be one, e.g., news:j462#36487@foo.bar, or
173 	     * it's an nntp or snews URL, or news URL with a host.  Restore the
174 	     * '#' in the address.
175 	     */
176 	    /* but only if we have found a path component of which this will
177 	     * become part. - kw  */
178 	    if (parts->relative || parts->absolute) {
179 		*(parts->anchor - 1) = '#';
180 		parts->anchor = NULL;
181 	    }
182 	}
183     }
184 }				/*scan */
185 
186 #if defined(HAVE_ALLOCA) && !defined(LY_FIND_LEAKS)
187 #define LYalloca(x)        alloca(x)
188 #define LYalloca_free(x)   {}
189 #else
190 #define LYalloca(x)        malloc(x)
191 #define LYalloca_free(x)   free(x)
192 #endif
193 
strchr_or_end(char * string,int ch)194 static char *strchr_or_end(char *string, int ch)
195 {
196     char *result = strchr(string, ch);
197 
198     if (result == 0) {
199 	result = string + strlen(string);
200     }
201     return result;
202 }
203 
204 /*
205  * Given a host specification that may end with a port number, e.g.,
206  *	foobar:123
207  * point to the ':' which begins the ":port" to make it simple to handle the
208  * substring.
209  *
210  * If no port is found (or a syntax error), return null.
211  */
HTParsePort(char * host,int * portp)212 char *HTParsePort(char *host, int *portp)
213 {
214     int brackets = 0;
215     char *result = NULL;
216 
217     *portp = 0;
218     if (host != NULL) {
219 	while (*host != '\0' && result == 0) {
220 	    switch (*host++) {
221 	    case ':':
222 		if (brackets == 0 && isdigit(UCH(*host))) {
223 		    char *next = NULL;
224 
225 		    *portp = (int) strtol(host, &next, 10);
226 		    if (next != 0 && next != host && *next == '\0') {
227 			result = (host - 1);
228 			CTRACE((tfp, "HTParsePort %d\n", *portp));
229 		    }
230 		}
231 		break;
232 	    case '[':		/* for ipv6 */
233 		++brackets;
234 		break;
235 	    case ']':		/* for ipv6 */
236 		--brackets;
237 		break;
238 	    }
239 	}
240     }
241     return result;
242 }
243 
244 #ifdef USE_IDNA
hex_decode(int ch)245 static int hex_decode(int ch)
246 {
247     int result = -1;
248 
249     if (ch >= '0' && ch <= '9')
250 	result = (ch - '0');
251     else if (ch >= 'a' && ch <= 'f')
252 	result = (ch - 'a') + 10;
253     else if (ch >= 'A' && ch <= 'F')
254 	result = (ch - 'A') + 10;
255     return result;
256 }
257 
258 /*
259  * Convert in-place the given hostname to IDNA form.  That requires up to 64
260  * characters, and we've allowed for that, with MIN_PARSE.
261  */
convert_to_idna(char * host)262 static void convert_to_idna(char *host)
263 {
264     size_t length = strlen(host);
265     char *endhost = host + length;
266     char *buffer = malloc(length + 1);
267     char *output = NULL;
268     char *src, *dst;
269     int code;
270     int hi, lo;
271 
272     if (buffer != 0) {
273 	code = TRUE;
274 	for (dst = buffer, src = host; src < endhost; ++dst) {
275 	    int ch = *src++;
276 
277 	    if (ch == HEX_ESCAPE) {
278 		if ((src + 1) < endhost
279 		    && (hi = hex_decode(src[0])) >= 0
280 		    && (lo = hex_decode(src[1])) >= 0) {
281 
282 		    *dst = (char) ((hi << 4) | lo);
283 		    src += 2;
284 		} else {
285 		    CTRACE((tfp, "convert_to_idna: `%s' is malformed\n", host));
286 		    code = FALSE;
287 		    break;
288 		}
289 	    } else {
290 		*dst = (char) ch;
291 	    }
292 	}
293 	if (code) {
294 	    *dst = '\0';
295 	    code = idna_to_ascii_8z(buffer, &output, IDNA_USE_STD3_ASCII_RULES);
296 	    if (code == IDNA_SUCCESS) {
297 		strcpy(host, output);
298 	    } else {
299 		CTRACE((tfp, "convert_to_idna: `%s': %s\n",
300 			buffer,
301 			idna_strerror((Idna_rc) code)));
302 	    }
303 	    if (output)		/* "(free)" to bypass LYLeaks.c */
304 		(free) (output);
305 	}
306 	free(buffer);
307     }
308 }
309 #define MIN_PARSE 80
310 #else
311 #define MIN_PARSE 8
312 #endif
313 
314 /*	Parse a Name relative to another name.			HTParse()
315  *	--------------------------------------
316  *
317  *	This returns those parts of a name which are given (and requested)
318  *	substituting bits from the related name where necessary.
319  *
320  * On entry,
321  *	aName		A filename given
322  *	relatedName	A name relative to which aName is to be parsed
323  *	wanted		A mask for the bits which are wanted.
324  *
325  * On exit,
326  *     returns         A pointer to a malloc'd string which MUST BE FREED
327  */
HTParse(const char * aName,const char * relatedName,int wanted)328 char *HTParse(const char *aName,
329 	      const char *relatedName,
330 	      int wanted)
331 {
332     char *result = NULL;
333     char *tail = NULL;		/* a pointer to the end of the 'result' string */
334     char *return_value = NULL;
335     size_t len, len1, len2;
336     size_t need;
337     char *name = NULL;
338     char *rel = NULL;
339     char *p, *q;
340     char *acc_method;
341     struct struct_parts given, related;
342 
343     CTRACE((tfp, "HTParse: aName:`%s'\n", aName));
344     CTRACE((tfp, "   relatedName:`%s'\n", relatedName));
345 
346     if (wanted & (PARSE_STRICTPATH | PARSE_QUERY)) {	/* if detail wanted... */
347 	if ((wanted & (PARSE_STRICTPATH | PARSE_QUERY))
348 	    == (PARSE_STRICTPATH | PARSE_QUERY))	/* if strictpath AND query */
349 	    wanted |= PARSE_PATH;	/* then treat as if PARSE_PATH wanted */
350 	if (wanted & PARSE_PATH)	/* if PARSE_PATH wanted */
351 	    wanted &= ~(PARSE_STRICTPATH | PARSE_QUERY);	/* ignore details */
352     }
353 /* *INDENT-OFF* */
354     CTRACE((tfp, "   want:%s%s%s%s%s%s%s\n",
355 	    wanted & PARSE_PUNCTUATION ? " punc"   : "",
356 	    wanted & PARSE_ANCHOR      ? " anchor" : "",
357 	    wanted & PARSE_PATH        ? " path"   : "",
358 	    wanted & PARSE_HOST        ? " host"   : "",
359 	    wanted & PARSE_ACCESS      ? " access" : "",
360 	    wanted & PARSE_STRICTPATH  ? " PATH"   : "",
361 	    wanted & PARSE_QUERY       ? " QUERY"  : ""));
362 /* *INDENT-ON* */
363 
364     /*
365      * Allocate the temporary string. Optimized.
366      */
367     len1 = strlen(aName) + 1;
368     len2 = strlen(relatedName) + 1;
369     len = len1 + len2 + MIN_PARSE;	/* Lots of space: more than enough */
370 
371     need = (len * 2 + len1 + len2);
372     if (need > (size_t) max_uri_size ||
373 	(int) need < (int) len1 ||
374 	(int) need < (int) len2)
375 	return StrAllocCopy(return_value, "");
376 
377     result = tail = (char *) LYalloca(need);
378     if (result == NULL) {
379 	outofmem(__FILE__, "HTParse");
380 
381 	assert(result != NULL);
382     }
383     *result = '\0';
384     name = result + len;
385     rel = name + len1;
386 
387     /*
388      * Make working copy of the input string to cut up.
389      */
390     MemCpy(name, aName, len1);
391 
392     /*
393      * Cut up the string into URL fields.
394      */
395     scan(name, &given);
396     SHOW_PARTS(given);
397 
398     /*
399      * Now related string.
400      */
401     if ((given.access && given.host && given.absolute) || !*relatedName) {
402 	/*
403 	 * Inherit nothing!
404 	 */
405 	related.access = NULL;
406 	related.host = NULL;
407 	related.absolute = NULL;
408 	related.relative = NULL;
409 	related.search = NULL;
410 	related.anchor = NULL;
411     } else {
412 	MemCpy(rel, relatedName, len2);
413 	scan(rel, &related);
414     }
415     SHOW_PARTS(related);
416 
417     /*
418      * Handle the scheme (access) field.
419      */
420     if (given.access && given.host && !given.relative && !given.absolute) {
421 	if (!strcmp(given.access, "http") ||
422 	    !strcmp(given.access, "https") ||
423 	    !strcmp(given.access, "ftp")) {
424 
425 	    /*
426 	     * Assume root.
427 	     */
428 	    given.absolute = empty_string;
429 	}
430     }
431     acc_method = given.access ? given.access : related.access;
432     if (wanted & PARSE_ACCESS) {
433 	if (acc_method) {
434 	    strcpy(tail, acc_method);
435 	    tail += strlen(tail);
436 	    if (wanted & PARSE_PUNCTUATION) {
437 		*tail++ = ':';
438 		*tail = '\0';
439 	    }
440 	}
441     }
442 
443     /*
444      * If different schemes, inherit nothing.
445      *
446      * We'll try complying with RFC 1808 and the Fielding draft, and inherit
447      * nothing if both schemes are given, rather than only when they differ,
448      * except for file URLs - FM
449      *
450      * After trying it for a while, it's still premature, IHMO, to go along
451      * with it, so this is back to inheriting for identical schemes whether or
452      * not they are "file".  If you want to try it again yourself, uncomment
453      * the strcasecomp() below.  - FM
454      */
455     if ((given.access && related.access) &&
456 	(			/* strcasecomp(given.access, "file") || */
457 	    strcmp(given.access, related.access))) {
458 	related.host = NULL;
459 	related.absolute = NULL;
460 	related.relative = NULL;
461 	related.search = NULL;
462 	related.anchor = NULL;
463     }
464 
465     /*
466      * Handle the host field.
467      */
468     if (wanted & PARSE_HOST) {
469 	if (given.host || related.host) {
470 	    if (wanted & PARSE_PUNCTUATION) {
471 		*tail++ = '/';
472 		*tail++ = '/';
473 	    }
474 	    strcpy(tail, given.host ? given.host : related.host);
475 #define CLEAN_URLS
476 #ifdef CLEAN_URLS
477 	    /*
478 	     * Ignore default port numbers, and trailing dots on FQDNs, which
479 	     * will only cause identical addresses to look different.  (related
480 	     * is already a clean url).
481 	     */
482 	    {
483 		char *p2, *h;
484 		int portnumber;
485 
486 		if ((p2 = strchr(result, '@')) != NULL)
487 		    tail = (p2 + 1);
488 		p2 = HTParsePort(result, &portnumber);
489 		if (p2 != NULL && acc_method != NULL) {
490 		    /*
491 		     * Port specified.
492 		     */
493 #define ACC_METHOD(a,b) (!strcmp(acc_method, a) && (portnumber == b))
494 		    if (ACC_METHOD("http", 80) ||
495 			ACC_METHOD("https", 443) ||
496 			ACC_METHOD("gopher", 70) ||
497 			ACC_METHOD("ftp", 21) ||
498 			ACC_METHOD("wais", 210) ||
499 			ACC_METHOD("nntp", 119) ||
500 			ACC_METHOD("news", 119) ||
501 			ACC_METHOD("newspost", 119) ||
502 			ACC_METHOD("newsreply", 119) ||
503 			ACC_METHOD("snews", 563) ||
504 			ACC_METHOD("snewspost", 563) ||
505 			ACC_METHOD("snewsreply", 563) ||
506 			ACC_METHOD("finger", 79) ||
507 			ACC_METHOD("telnet", 23) ||
508 			ACC_METHOD("tn3270", 23) ||
509 			ACC_METHOD("rlogin", 513) ||
510 			ACC_METHOD("cso", 105))
511 			*p2 = '\0';	/* It is the default: ignore it */
512 		}
513 		if (p2 == NULL) {
514 		    int len3 = (int) strlen(tail);
515 
516 		    if (len3 > 0) {
517 			h = tail + len3 - 1;	/* last char of hostname */
518 			if (*h == '.')
519 			    *h = '\0';	/* chop final . */
520 		    }
521 		} else if (p2 != result) {
522 		    h = p2;
523 		    h--;	/* End of hostname */
524 		    if (*h == '.') {
525 			/*
526 			 * Slide p2 over h.
527 			 */
528 			while (*p2 != '\0')
529 			    *h++ = *p2++;
530 			*h = '\0';	/* terminate */
531 		    }
532 		}
533 	    }
534 #ifdef USE_IDNA
535 	    /*
536 	     * Depending on locale-support, we could have a literal UTF-8
537 	     * string as a host name, or a URL-encoded form of that.
538 	     */
539 	    convert_to_idna(tail);
540 #endif
541 #endif /* CLEAN_URLS */
542 	}
543     }
544 
545     /*
546      * Trim any blanks from the result so far - there's no excuse for blanks
547      * in a hostname.  Also update the tail here.
548      */
549     tail = LYRemoveBlanks(result);
550 
551     /*
552      * If host in given or related was ended directly with a '?' (no slash),
553      * fake the search part into absolute.  This is the only case search is
554      * returned from scan.  A host must have been present.  this restores the
555      * '?' at which the host part had been truncated in scan, we have to do
556      * this after host part handling is done.  - kw
557      */
558     if (given.search && *(given.search - 1) == '\0') {
559 	given.absolute = given.search - 1;
560 	given.absolute[0] = '?';
561     } else if (related.search && !related.absolute &&
562 	       *(related.search - 1) == '\0') {
563 	related.absolute = related.search - 1;
564 	related.absolute[0] = '?';
565     }
566 
567     /*
568      * If different hosts, inherit no path.
569      */
570     if (given.host && related.host)
571 	if (strcmp(given.host, related.host) != 0) {
572 	    related.absolute = NULL;
573 	    related.relative = NULL;
574 	    related.anchor = NULL;
575 	}
576 
577     /*
578      * Handle the path.
579      */
580     if (wanted & (PARSE_PATH | PARSE_STRICTPATH | PARSE_QUERY)) {
581 	int want_detail = (wanted & (PARSE_STRICTPATH | PARSE_QUERY));
582 
583 	if (acc_method && !given.absolute && given.relative) {
584 	    /*
585 	     * Treat all given nntp or snews paths, or given paths for news
586 	     * URLs with a host, as absolute.
587 	     */
588 	    switch (*acc_method) {
589 	    case 'N':
590 	    case 'n':
591 		if (!strcasecomp(acc_method, "nntp") ||
592 		    (!strcasecomp(acc_method, "news") &&
593 		     !strncasecomp(result, "news://", 7))) {
594 		    given.absolute = given.relative;
595 		    given.relative = NULL;
596 		}
597 		break;
598 	    case 'S':
599 	    case 's':
600 		if (!strcasecomp(acc_method, "snews")) {
601 		    given.absolute = given.relative;
602 		    given.relative = NULL;
603 		}
604 		break;
605 	    }
606 	}
607 
608 	if (given.absolute) {	/* All is given */
609 	    if (wanted & PARSE_PUNCTUATION)
610 		*tail++ = '/';
611 	    strcpy(tail, given.absolute);
612 	    CTRACE((tfp, "HTParse: (ABS)\n"));
613 	} else if (related.absolute) {	/* Adopt path not name */
614 	    char *base = tail;
615 
616 	    *tail++ = '/';
617 	    strcpy(tail, related.absolute);
618 	    if (given.relative) {
619 		/* RFC 1808 part 4 step 5 (if URL path is empty) */
620 		/* a) if given has params, add/replace that */
621 		if (given.relative[0] == ';') {
622 		    strcpy(strchr_or_end(tail, ';'), given.relative);
623 		}
624 		/* b) if given has query, add/replace that */
625 		else if (given.relative[0] == '?') {
626 		    strcpy(strchr_or_end(tail, '?'), given.relative);
627 		}
628 		/* otherwise fall through to RFC 1808 part 4 step 6 */
629 		else {
630 		    p = strchr(tail, '?');	/* Search part? */
631 		    if (p == NULL)
632 			p = (tail + strlen(tail) - 1);
633 		    for (; *p != '/'; p--) ;	/* last / */
634 		    p[1] = '\0';	/* Remove filename */
635 		    strcat(p, given.relative);	/* Add given one */
636 		}
637 		HTSimplify(base);
638 		if (*base == '\0')
639 		    strcpy(base, "/");
640 	    }
641 	    CTRACE((tfp, "HTParse: (Related-ABS)\n"));
642 	} else if (given.relative) {
643 	    strcpy(tail, given.relative);	/* what we've got */
644 	    CTRACE((tfp, "HTParse: (REL)\n"));
645 	} else if (related.relative) {
646 	    strcpy(tail, related.relative);
647 	    CTRACE((tfp, "HTParse: (Related-REL)\n"));
648 	} else {		/* No inheritance */
649 	    if (!isLYNXCGI(aName) &&
650 		!isLYNXEXEC(aName) &&
651 		!isLYNXPROG(aName)) {
652 		*tail++ = '/';
653 		*tail = '\0';
654 	    }
655 	    if (!strcmp(result, "news:/"))
656 		result[5] = '*';
657 	    CTRACE((tfp, "HTParse: (No inheritance)\n"));
658 	}
659 	if (want_detail) {
660 	    p = strchr(tail, '?');	/* Search part? */
661 	    if (p) {
662 		if (PARSE_STRICTPATH) {
663 		    *p = '\0';
664 		} else {
665 		    if (!(wanted & PARSE_PUNCTUATION))
666 			p++;
667 		    do {
668 			*tail++ = *p;
669 		    } while (*p++);
670 		}
671 	    } else {
672 		if (wanted & PARSE_QUERY)
673 		    *tail = '\0';
674 	    }
675 	}
676     }
677 
678     /*
679      * Handle the fragment (anchor).  Never inherit.
680      */
681     if (wanted & PARSE_ANCHOR) {
682 	if (given.anchor && *given.anchor) {
683 	    tail += strlen(tail);
684 	    if (wanted & PARSE_PUNCTUATION)
685 		*tail++ = '#';
686 	    strcpy(tail, given.anchor);
687 	}
688     }
689 
690     /*
691      * If there are any blanks remaining in the string, escape them as needed.
692      * See the discussion in LYLegitimizeHREF() for example.
693      */
694     if ((p = strchr(result, ' ')) != 0) {
695 	switch (is_url(result)) {
696 	case UNKNOWN_URL_TYPE:
697 	    CTRACE((tfp, "HTParse:      ignore:`%s'\n", result));
698 	    break;
699 	case LYNXEXEC_URL_TYPE:
700 	case LYNXPROG_URL_TYPE:
701 	case LYNXCGI_URL_TYPE:
702 	case LYNXPRINT_URL_TYPE:
703 	case LYNXHIST_URL_TYPE:
704 	case LYNXDOWNLOAD_URL_TYPE:
705 	case LYNXKEYMAP_URL_TYPE:
706 	case LYNXIMGMAP_URL_TYPE:
707 	case LYNXCOOKIE_URL_TYPE:
708 	case LYNXCACHE_URL_TYPE:
709 	case LYNXDIRED_URL_TYPE:
710 	case LYNXOPTIONS_URL_TYPE:
711 	case LYNXCFG_URL_TYPE:
712 	case LYNXCOMPILE_OPTS_URL_TYPE:
713 	case LYNXMESSAGES_URL_TYPE:
714 	    CTRACE((tfp, "HTParse:      spaces:`%s'\n", result));
715 	    break;
716 	case NOT_A_URL_TYPE:
717 	default:
718 	    CTRACE((tfp, "HTParse:      encode:`%s'\n", result));
719 	    do {
720 		q = p + strlen(p) + 2;
721 
722 		while (q != p + 1) {
723 		    q[0] = q[-2];
724 		    --q;
725 		}
726 		p[0] = HEX_ESCAPE;
727 		p[1] = '2';
728 		p[2] = '0';
729 	    } while ((p = strchr(result, ' ')) != 0);
730 	    break;
731 	}
732     }
733     CTRACE((tfp, "HTParse:      result:`%s'\n", result));
734 
735     StrAllocCopy(return_value, result);
736     LYalloca_free(result);
737 
738     /* FIXME: could be optimized using HTParse() internals */
739     if (*relatedName &&
740 	((wanted & PARSE_ALL_WITHOUT_ANCHOR) == PARSE_ALL_WITHOUT_ANCHOR)) {
741 	/*
742 	 * Check whether to fill in localhost.  - FM
743 	 */
744 	LYFillLocalFileURL(&return_value, relatedName);
745 	CTRACE((tfp, "pass LYFillLocalFile:`%s'\n", return_value));
746     }
747 
748     return return_value;	/* exactly the right length */
749 }
750 
751 /*	HTParseAnchor(), fast HTParse() specialization
752  *	----------------------------------------------
753  *
754  * On exit,
755  *	returns		A pointer within input string (probably to its end '\0')
756  */
HTParseAnchor(const char * aName)757 const char *HTParseAnchor(const char *aName)
758 {
759     const char *p = aName;
760 
761     for (; *p && *p != '#'; p++) {
762 	;
763     }
764     if (*p == '#') {
765 	/* the safe way based on HTParse() -
766 	 * keeping in mind scan() peculiarities on schemes:
767 	 */
768 	struct struct_parts given;
769 	size_t need = ((unsigned) ((p - aName) + (int) strlen(p) + 1));
770 	char *name;
771 
772 	if (need > (size_t) max_uri_size) {
773 	    p += strlen(p);
774 	} else {
775 	    name = (char *) LYalloca(need);
776 
777 	    if (name == NULL) {
778 		outofmem(__FILE__, "HTParseAnchor");
779 
780 		assert(name != NULL);
781 	    }
782 	    strcpy(name, aName);
783 	    scan(name, &given);
784 	    LYalloca_free(name);
785 
786 	    p++;		/*next to '#' */
787 	    if (given.anchor == NULL) {
788 		for (; *p; p++)	/*scroll to end '\0' */
789 		    ;
790 	    }
791 	}
792     }
793     return p;
794 }
795 
796 /*	Simplify a filename.				HTSimplify()
797  *	--------------------
798  *
799  *  A unix-style file is allowed to contain the sequence xxx/../ which may
800  *  be replaced by "" , and the sequence "/./" which may be replaced by "/".
801  *  Simplification helps us recognize duplicate filenames.
802  *
803  *	Thus,	/etc/junk/../fred	becomes /etc/fred
804  *		/etc/junk/./fred	becomes /etc/junk/fred
805  *
806  *	but we should NOT change
807  *		http://fred.xxx.edu/../..
808  *
809  *	or	../../albert.html
810  */
HTSimplify(char * filename)811 void HTSimplify(char *filename)
812 {
813     char *p;
814     char *q, *q1;
815 
816     if (filename == NULL)
817 	return;
818 
819     if (!(filename[0] && filename[1]) ||
820 	filename[0] == '?' || filename[1] == '?' || filename[2] == '?')
821 	return;
822 
823     if (strchr(filename, '/') != NULL) {
824 	for (p = (filename + 2); *p; p++) {
825 	    if (*p == '?') {
826 		/*
827 		 * We're still treating a ?searchpart as part of the path in
828 		 * HTParse() and scan(), but if we encounter a '?' here, assume
829 		 * it's the delimiter and break.  We also could check for a
830 		 * parameter delimiter (';') here, but the current Fielding
831 		 * draft (wisely or ill-advisedly :) says that it should be
832 		 * ignored and collapsing be allowed in its value).  The only
833 		 * defined parameter at present is ;type=[A, I, or D] for ftp
834 		 * URLs, so if there's a "/..", "/../", "/./", or terminal '.'
835 		 * following the ';', it must be due to the ';' being an
836 		 * unescaped path character and not actually a parameter
837 		 * delimiter.  - FM
838 		 */
839 		break;
840 	    }
841 	    if (*p == '/') {
842 		if ((p[1] == '.') && (p[2] == '.') &&
843 		    (p[3] == '/' || p[3] == '?' || p[3] == '\0')) {
844 		    /*
845 		     * Handle "../", "..?" or "..".
846 		     */
847 		    for (q = (p - 1); (q >= filename) && (*q != '/'); q--)
848 			/*
849 			 * Back up to previous slash or beginning of string.
850 			 */
851 			;
852 		    if ((q[0] == '/') &&
853 			(StrNCmp(q, "/../", 4) &&
854 			 StrNCmp(q, "/..?", 4)) &&
855 			!((q - 1) > filename && q[-1] == '/')) {
856 			/*
857 			 * Not at beginning of string or in a host field, so
858 			 * remove the "/xxx/..".
859 			 */
860 			q1 = (p + 3);
861 			p = q;
862 			while (*q1 != '\0')
863 			    *p++ = *q1++;
864 			*p = '\0';	/* terminate */
865 			/*
866 			 * Start again with previous slash.
867 			 */
868 			p = (q - 1);
869 		    }
870 		} else if (p[1] == '.' && p[2] == '/') {
871 		    /*
872 		     * Handle "./" by removing both characters.
873 		     */
874 		    q = p;
875 		    q1 = (p + 2);
876 		    while (*q1 != '\0')
877 			*q++ = *q1++;
878 		    *q = '\0';	/* terminate */
879 		    p--;
880 		} else if (p[1] == '.' && p[2] == '?') {
881 		    /*
882 		     * Handle ".?" by removing the dot.
883 		     */
884 		    q = (p + 1);
885 		    q1 = (p + 2);
886 		    while (*q1 != '\0')
887 			*q++ = *q1++;
888 		    *q = '\0';	/* terminate */
889 		    p--;
890 		} else if (p[1] == '.' && p[2] == '\0') {
891 		    /*
892 		     * Handle terminal "." by removing the character.
893 		     */
894 		    p[1] = '\0';
895 		}
896 	    }
897 	}
898 	if (p >= filename + 2 && *p == '?' && *(p - 1) == '.') {
899 	    if (*(p - 2) == '/') {
900 		/*
901 		 * Handle "/.?" by removing the dot.
902 		 */
903 		q = p - 1;
904 		q1 = p;
905 		while (*q1 != '\0')
906 		    *q++ = *q1++;
907 		*q = '\0';
908 	    } else if (*(p - 2) == '.' &&
909 		       p >= filename + 4 && *(p - 3) == '/' &&
910 		       (*(p - 4) != '/' ||
911 			(p > filename + 4 && *(p - 5) != ':'))) {
912 		/*
913 		 * Handle "xxx/..?"
914 		 */
915 		for (q = (p - 4); (q > filename) && (*q != '/'); q--)
916 		    /*
917 		     * Back up to previous slash or beginning of string.
918 		     */
919 		    ;
920 		if (*q == '/') {
921 		    if (q > filename && *(q - 1) == '/' &&
922 			!(q > filename + 1 && *(q - 1) != ':'))
923 			return;
924 		    q++;
925 		}
926 		if (StrNCmp(q, "../", 3) && StrNCmp(q, "./", 2)) {
927 		    /*
928 		     * Not after "//" at beginning of string or after "://",
929 		     * and xxx is not ".." or ".", so remove the "xxx/..".
930 		     */
931 		    q1 = p;
932 		    p = q;
933 		    while (*q1 != '\0')
934 			*p++ = *q1++;
935 		    *p = '\0';	/* terminate */
936 		}
937 	    }
938 	}
939     }
940 }
941 
942 /*	Make Relative Name.					HTRelative()
943  *	-------------------
944  *
945  * This function creates and returns a string which gives an expression of
946  * one address as related to another.  Where there is no relation, an absolute
947  * address is returned.
948  *
949  *  On entry,
950  *	Both names must be absolute, fully qualified names of nodes
951  *	(no anchor bits)
952  *
953  *  On exit,
954  *	The return result points to a newly allocated name which, if
955  *	parsed by HTParse relative to relatedName, will yield aName.
956  *	The caller is responsible for freeing the resulting name later.
957  *
958  */
HTRelative(const char * aName,const char * relatedName)959 char *HTRelative(const char *aName,
960 		 const char *relatedName)
961 {
962     char *result = NULL;
963     const char *p = aName;
964     const char *q = relatedName;
965     const char *after_access = NULL;
966     const char *path = NULL;
967     const char *last_slash = NULL;
968     int slashes = 0;
969 
970     for (; *p; p++, q++) {	/* Find extent of match */
971 	if (*p != *q)
972 	    break;
973 	if (*p == ':')
974 	    after_access = p + 1;
975 	if (*p == '/') {
976 	    last_slash = p;
977 	    slashes++;
978 	    if (slashes == 3)
979 		path = p;
980 	}
981     }
982 
983     /* q, p point to the first non-matching character or zero */
984 
985     if (!after_access) {	/* Different access */
986 	StrAllocCopy(result, aName);
987     } else if (slashes < 3) {	/* Different nodes */
988 	StrAllocCopy(result, after_access);
989     } else if (slashes == 3) {	/* Same node, different path */
990 	StrAllocCopy(result, path);
991     } else {			/* Some path in common */
992 	unsigned levels = 0;
993 
994 	for (; *q && (*q != '#'); q++)
995 	    if (*q == '/')
996 		levels++;
997 	result = typecallocn(char, 3 * levels + strlen(last_slash) + 1);
998 
999 	if (result == NULL)
1000 	    outofmem(__FILE__, "HTRelative");
1001 
1002 	assert(result != NULL);
1003 
1004 	result[0] = '\0';
1005 	for (; levels; levels--)
1006 	    strcat(result, "../");
1007 	strcat(result, last_slash + 1);
1008     }
1009     CTRACE((tfp,
1010 	    "HTparse: `%s' expressed relative to\n   `%s' is\n   `%s'.\n",
1011 	    aName, relatedName, result));
1012     return result;
1013 }
1014 
1015 #define AlloCopy(next,base,extra) \
1016 	typecallocn(char, ((next - base) + ((int) extra)))
1017 
1018 /*	Escape undesirable characters using %			HTEscape()
1019  *	-------------------------------------
1020  *
1021  *	This function takes a pointer to a string in which
1022  *	some characters may be unacceptable unescaped.
1023  *	It returns a string which has these characters
1024  *	represented by a '%' character followed by two hex digits.
1025  *
1026  *	Unlike HTUnEscape(), this routine returns a calloc'd string.
1027  */
1028 /* *INDENT-OFF* */
1029 static const unsigned char isAcceptable[96] =
1030 
1031 /*	Bit 0		xalpha		-- see HTFile.h
1032  *	Bit 1		xpalpha		-- as xalpha but with plus.
1033  *	Bit 2 ...	path		-- as xpalphas but with /
1034  */
1035     /*	 0 1 2 3 4 5 6 7 8 9 A B C D E F */
1036     {	 0,0,0,0,0,0,0,0,0,0,7,6,0,7,7,4,	/* 2x	!"#$%&'()*+,-./  */
1037 	 7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0,	/* 3x  0123456789:;<=>?  */
1038 	 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,	/* 4x  @ABCDEFGHIJKLMNO  */
1039 	 7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,7,	/* 5X  PQRSTUVWXYZ[\]^_  */
1040 	 0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,	/* 6x  `abcdefghijklmno  */
1041 	 7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0 };	/* 7X  pqrstuvwxyz{|}~	DEL */
1042 /* *INDENT-ON* */
1043 
1044 static const char *hex = "0123456789ABCDEF";
1045 
1046 #define ACCEPTABLE(a)	( a>=32 && a<128 && ((isAcceptable[a-32]) & mask))
1047 
HTEscape(const char * str,unsigned mask)1048 char *HTEscape(const char *str,
1049 	       unsigned mask)
1050 {
1051     const char *p;
1052     char *q;
1053     char *result;
1054     size_t unacceptable = 0;
1055 
1056     for (p = str; *p; p++)
1057 	if (!ACCEPTABLE(UCH(TOASCII(*p))))
1058 	    unacceptable++;
1059     result = AlloCopy(p, str, (unacceptable * 2) + 1);
1060 
1061     if (result == NULL)
1062 	outofmem(__FILE__, "HTEscape");
1063 
1064     assert(result != NULL);
1065 
1066     for (q = result, p = str; *p; p++) {
1067 	unsigned char a = UCH(TOASCII(*p));
1068 
1069 	if (!ACCEPTABLE(a)) {
1070 	    *q++ = HEX_ESCAPE;	/* Means hex coming */
1071 	    *q++ = hex[a >> 4];
1072 	    *q++ = hex[a & 15];
1073 	} else
1074 	    *q++ = *p;
1075     }
1076     *q = '\0';			/* Terminate */
1077     return result;
1078 }
1079 
1080 /*	Escape unsafe characters using %			HTEscapeUnsafe()
1081  *	--------------------------------
1082  *
1083  *	This function takes a pointer to a string in which
1084  *	some characters may be that may be unsafe are unescaped.
1085  *	It returns a string which has these characters
1086  *	represented by a '%' character followed by two hex digits.
1087  *
1088  *	Unlike HTUnEscape(), this routine returns a malloc'd string.
1089  */
1090 #define UNSAFE(ch) (((ch) <= 32) || ((ch) >= 127))
1091 
HTEscapeUnsafe(const char * str)1092 char *HTEscapeUnsafe(const char *str)
1093 {
1094     const char *p;
1095     char *q;
1096     char *result;
1097     size_t unacceptable = 0;
1098 
1099     for (p = str; *p; p++)
1100 	if (UNSAFE(UCH(TOASCII(*p))))
1101 	    unacceptable++;
1102     result = AlloCopy(p, str, (unacceptable * 2) + 1);
1103 
1104     if (result == NULL)
1105 	outofmem(__FILE__, "HTEscapeUnsafe");
1106 
1107     assert(result != NULL);
1108 
1109     for (q = result, p = str; *p; p++) {
1110 	unsigned char a = UCH(TOASCII(*p));
1111 
1112 	if (UNSAFE(a)) {
1113 	    *q++ = HEX_ESCAPE;	/* Means hex coming */
1114 	    *q++ = hex[a >> 4];
1115 	    *q++ = hex[a & 15];
1116 	} else
1117 	    *q++ = *p;
1118     }
1119     *q = '\0';			/* Terminate */
1120     return result;
1121 }
1122 
1123 /*	Escape undesirable characters using % but space to +.	HTEscapeSP()
1124  *	-----------------------------------------------------
1125  *
1126  *	This function takes a pointer to a string in which
1127  *	some characters may be unacceptable unescaped.
1128  *	It returns a string which has these characters
1129  *	represented by a '%' character followed by two hex digits,
1130  *	except that spaces are converted to '+' instead of %2B.
1131  *
1132  *	Unlike HTUnEscape(), this routine returns a calloced string.
1133  */
HTEscapeSP(const char * str,unsigned mask)1134 char *HTEscapeSP(const char *str,
1135 		 unsigned mask)
1136 {
1137     const char *p;
1138     char *q;
1139     char *result;
1140     size_t unacceptable = 0;
1141 
1142     for (p = str; *p; p++)
1143 	if (!(*p == ' ' || ACCEPTABLE(UCH(TOASCII(*p)))))
1144 	    unacceptable++;
1145     result = AlloCopy(p, str, (unacceptable * 2) + 1);
1146 
1147     if (result == NULL)
1148 	outofmem(__FILE__, "HTEscape");
1149 
1150     assert(result != NULL);
1151 
1152     for (q = result, p = str; *p; p++) {
1153 	unsigned char a = UCH(TOASCII(*p));
1154 
1155 	if (a == 32) {
1156 	    *q++ = '+';
1157 	} else if (!ACCEPTABLE(a)) {
1158 	    *q++ = HEX_ESCAPE;	/* Means hex coming */
1159 	    *q++ = hex[a >> 4];
1160 	    *q++ = hex[a & 15];
1161 	} else {
1162 	    *q++ = *p;
1163 	}
1164     }
1165     *q = '\0';			/* Terminate */
1166     return result;
1167 }
1168 
1169 /*	Decode %xx escaped characters.				HTUnEscape()
1170  *	------------------------------
1171  *
1172  *	This function takes a pointer to a string in which some
1173  *	characters may have been encoded in %xy form, where xy is
1174  *	the ASCII hex code for character 16x+y.
1175  *	The string is converted in place, as it will never grow.
1176  */
from_hex(int c)1177 static char from_hex(int c)
1178 {
1179     return (char) (c >= '0' && c <= '9' ? c - '0'
1180 		   : c >= 'A' && c <= 'F' ? c - 'A' + 10
1181 		   : c - 'a' + 10);	/* accept small letters just in case */
1182 }
1183 
HTUnEscape(char * str)1184 char *HTUnEscape(char *str)
1185 {
1186     char *p = str;
1187     char *q = str;
1188 
1189     if (!(p && *p))
1190 	return str;
1191 
1192     while (*p != '\0') {
1193 	if (*p == HEX_ESCAPE &&
1194 	/*
1195 	 * Tests shouldn't be needed, but better safe than sorry.
1196 	 */
1197 	    p[1] && p[2] &&
1198 	    isxdigit(UCH(p[1])) &&
1199 	    isxdigit(UCH(p[2]))) {
1200 	    p++;
1201 	    if (*p)
1202 		*q = (char) (from_hex(*p++) * 16);
1203 	    if (*p) {
1204 		/*
1205 		 * Careful! FROMASCII() may evaluate its arg more than once!
1206 		 */
1207 		/* S/390 -- gil -- 0221 */
1208 		*q = (char) (*q + from_hex(*p++));
1209 	    }
1210 	    *q = FROMASCII(*q);
1211 	    q++;
1212 	} else {
1213 	    *q++ = *p++;
1214 	}
1215     }
1216 
1217     *q = '\0';
1218     return str;
1219 
1220 }				/* HTUnEscape */
1221 
1222 /*	Decode some %xx escaped characters.		      HTUnEscapeSome()
1223  *	-----------------------------------			Klaus Weide
1224  *							    (kweide@tezcat.com)
1225  *	This function takes a pointer to a string in which some
1226  *	characters may have been encoded in %xy form, where xy is
1227  *	the ASCII hex code for character 16x+y, and a pointer to
1228  *	a second string containing one or more characters which
1229  *	should be unescaped if escaped in the first string.
1230  *	The first string is converted in place, as it will never grow.
1231  */
HTUnEscapeSome(char * str,const char * do_trans)1232 char *HTUnEscapeSome(char *str,
1233 		     const char *do_trans)
1234 {
1235     char *p = str;
1236     char *q = str;
1237     char testcode;
1238 
1239     if (p == NULL || *p == '\0' || do_trans == NULL || *do_trans == '\0')
1240 	return str;
1241 
1242     while (*p != '\0') {
1243 	if (*p == HEX_ESCAPE &&
1244 	    p[1] && p[2] &&	/* tests shouldn't be needed, but.. */
1245 	    isxdigit(UCH(p[1])) &&
1246 	    isxdigit(UCH(p[2])) &&
1247 	    (testcode = (char) FROMASCII(from_hex(p[1]) * 16 +
1248 					 from_hex(p[2]))) &&	/* %00 no good */
1249 	    strchr(do_trans, testcode)) {	/* it's one of the ones we want */
1250 	    *q++ = testcode;
1251 	    p += 3;
1252 	} else {
1253 	    *q++ = *p++;
1254 	}
1255     }
1256 
1257     *q = '\0';
1258     return str;
1259 
1260 }				/* HTUnEscapeSome */
1261 /* *INDENT-OFF* */
1262 static const unsigned char crfc[96] =
1263 
1264 /*	Bit 0		xalpha		-- need "quoting"
1265  *	Bit 1		xpalpha		-- need \escape if quoted
1266  */
1267     /*	 0 1 2 3 4 5 6 7 8 9 A B C D E F */
1268     {	 1,0,3,0,0,0,0,0,1,1,0,0,1,0,1,0,	/* 2x	!"#$%&'()*+,-./  */
1269 	 0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,	/* 3x  0123456789:;<=>?  */
1270 	 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	/* 4x  @ABCDEFGHIJKLMNO  */
1271 	 0,0,0,0,0,0,0,0,0,0,0,1,2,1,0,0,	/* 5X  PQRSTUVWXYZ[\]^_  */
1272 	 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,	/* 6x  `abcdefghijklmno  */
1273 	 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3 };	/* 7X  pqrstuvwxyz{|}~	DEL */
1274 /* *INDENT-ON* */
1275 
1276 #define ASCII_TAB '\011'
1277 #define ASCII_LF  '\012'
1278 #define ASCII_CR  '\015'
1279 #define ASCII_SPC '\040'
1280 #define ASCII_BAK '\134'
1281 
1282 /*
1283  *  Turn a string which is not a RFC 822 token into a quoted-string. - KW
1284  *  The "quoted" parameter tells whether we need the beginning/ending quote
1285  *  marks.  If not, the caller will provide them -TD
1286  */
HTMake822Word(char ** str,int quoted)1287 void HTMake822Word(char **str,
1288 		   int quoted)
1289 {
1290     const char *p;
1291     char *q;
1292     char *result;
1293     unsigned char a;
1294     unsigned added = 0;
1295 
1296     if (isEmpty(*str)) {
1297 	StrAllocCopy(*str, quoted ? "\"\"" : "");
1298 	return;
1299     }
1300     for (p = *str; *p; p++) {
1301 	a = UCH(TOASCII(*p));	/* S/390 -- gil -- 0240 */
1302 	if (a < 32 || a >= 128 ||
1303 	    ((crfc[a - 32]) & 1)) {
1304 	    if (!added)
1305 		added = 2;
1306 	    if (a >= 160 || a == '\t')
1307 		continue;
1308 	    if (a == '\r' || a == '\n')
1309 		added += 2;
1310 	    else if ((a & 127) < 32 || ((crfc[a - 32]) & 2))
1311 		added++;
1312 	}
1313     }
1314     if (!added)
1315 	return;
1316     result = AlloCopy(p, *str, added + 1);
1317     if (result == NULL)
1318 	outofmem(__FILE__, "HTMake822Word");
1319 
1320     assert(result != NULL);
1321 
1322     q = result;
1323     if (quoted)
1324 	*q++ = '"';
1325     /*
1326      * Having converted the character to ASCII, we can't use symbolic
1327      * escape codes, since they're in the host character set, which
1328      * is not necessarily ASCII.  Thus we use octal escape codes instead.
1329      * -- gil (Paul Gilmartin) <pg@sweng.stortek.com>
1330      */
1331     /* S/390 -- gil -- 0268 */
1332     for (p = *str; *p; p++) {
1333 	a = UCH(TOASCII(*p));
1334 	if ((a != ASCII_TAB) &&
1335 	    ((a & 127) < ASCII_SPC ||
1336 	     (a < 128 && ((crfc[a - 32]) & 2))))
1337 	    *q++ = ASCII_BAK;
1338 	*q++ = *p;
1339 	if (a == ASCII_LF ||
1340 	    (a == ASCII_CR && (TOASCII(*(p + 1)) != ASCII_LF)))
1341 	    *q++ = ' ';
1342     }
1343     if (quoted)
1344 	*q++ = '"';
1345     *q = '\0';			/* Terminate */
1346     FREE(*str);
1347     *str = result;
1348 }
1349