Blob


1 /*
2 * Copyright (c) 2021 Matthias Schmidt <xhr@giessen.ccc.de>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 #include <ctype.h>
18 #include <limits.h>
19 #include <string.h>
21 #include "log.h"
22 #include "twind.h"
24 char hex_to_int(char);
25 char* uridecode(const char *);
27 /*
28 * The following two functions are from https://geekhideout.com/urlcode.shtml
29 * and provided without license restrictions
30 */
31 char hex_to_int(char ch) {
32 return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
33 }
35 char *
36 uridecode(const char *request)
37 {
38 char *temp = xmalloc(strlen(request) + 1);
39 const char *p;
40 char *pt;
42 p = request;
43 pt = temp;
45 while (*p) {
46 if (*p == '%') {
47 if (p[1] && p[2]) {
48 *pt++ = hex_to_int(p[1]) << 4 | hex_to_int(p[2]);
49 p += 2;
50 }
51 } else if (*p == '+') {
52 *pt++ = ' ';
53 } else {
54 *pt++ = *p;
55 }
56 p++;
57 }
58 *pt = '\0';
60 return temp;
61 }
63 int
64 get_path_from_request(char *request, char *finalpath)
65 {
66 char hostname[MAXREQLEN];
67 char localpath[MAXREQLEN];
68 char temp[MAXREQLEN];
69 char *p, *decoded_request;
70 int pos = 0, ret;
72 memset(hostname, 0, sizeof(hostname));
73 memset(localpath, 0, sizeof(localpath));
74 memset(temp, 0, sizeof(temp));
76 p = request;
78 if ((p = strchr(request, '\r')) == NULL) {
79 log_info("\\r missing from request, abort processing");
80 return -1;
81 }
83 *p = '\0'; /* Strip \r\n */
84 p = request;
86 if (strncmp(p, "gemini://", 9) != 0) {
87 log_info("Gemini scheme missing, abort processing");
88 return -1;
89 }
90 memmove(request, p + 9, strlen(request) + 1 - 9);
92 decoded_request = uridecode(request);
94 /* save hostname */
95 if ((p = strchr(decoded_request, '/')) != NULL)
96 snprintf(hostname, strlen(decoded_request) - strlen(p)+1, "%s",
97 decoded_request);
98 else
99 snprintf(hostname, strlen(decoded_request)+1, "%s", decoded_request);
101 /* Strip possible port (e.g. :1965) from hostname */
102 if ((p = strrchr(hostname, ':')) != NULL) {
103 pos = strlen(hostname) - strlen(p);
104 if (pos < 0 || pos > _POSIX_HOST_NAME_MAX)
105 fatalx("pos while shorten hostname out of range");
106 hostname[pos] = '\0';
109 /* Remove ../ for security reasons */
110 while ((p = strstr(decoded_request, "/..")) != NULL) {
111 memmove(decoded_request, p + 3, strlen(p) + 1 - 3);
114 if ((p = strchr(decoded_request, '/')) != NULL) {
115 /* Save all after the first / in localpath */
116 snprintf(localpath, strlen(decoded_request), "%s", p+1);
117 if (strlen(localpath) == 0) {
118 /*
119 * If the request is 'example.com/', localpart will be empty. In this case
120 * write the default to it.
121 */
122 sprintf(localpath, "index.gmi");
124 } else {
125 /* There is no slash in the request, so assume index.gmi */
126 sprintf(localpath, "index.gmi");
129 /*
130 *We do not need to take the base dir aka /var/db/gemini into account
131 * since we already chroot() to _PATH_TWIND_CHROOT .
133 * Here, a string truncation could happen. This can be implemented
134 * better! XXX FIXME
135 */
136 snprintf(finalpath, MAXREQLEN, "%s/%s", hostname, localpath);
138 /* Check if the wanted path exists and if it's a directory */
139 ret = check_gemini_file(finalpath);
140 if (ret < 0) {
141 log_debug("%s not found", finalpath);
142 free(decoded_request);
143 return -2;
144 } else if (ret == 1) {
145 log_debug("%s is a directory", finalpath);
146 /* Auto append index.gmi if destination is a directory */
147 snprintf(temp, MAXREQLEN, "%s", finalpath);
148 snprintf(finalpath, MAXREQLEN, "%s/index.gmi", temp);
151 log_debug("Got request for %s on server %s -> %s",
152 localpath, hostname, finalpath);
154 /* decoded_request is no longer used, so it can be freed */
155 free(decoded_request);
157 return 0;