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 memset(temp, 0, strlen(request)+1);
44 p = request;
45 pt = temp;
47 while (*p) {
48 if (*p == '%') {
49 if (p[1] && p[2]) {
50 *pt++ = hex_to_int(p[1]) << 4 | hex_to_int(p[2]);
51 p += 2;
52 }
53 } else if (*p == '+') {
54 *pt++ = ' ';
55 } else {
56 *pt++ = *p;
57 }
58 p++;
59 }
60 *pt = '\0';
62 return temp;
63 }
65 int
66 get_path_from_request(char *request, char *finalpath)
67 {
68 char hostname[MAXREQLEN];
69 char localpath[MAXREQLEN];
70 char temp[MAXREQLEN];
71 char *p, *decoded_request;
72 int pos = 0, ret;
74 memset(hostname, 0, sizeof(hostname));
75 memset(localpath, 0, sizeof(localpath));
76 memset(temp, 0, sizeof(temp));
78 p = request;
80 if ((p = strchr(request, '\r')) == NULL) {
81 log_info("\\r missing from request, abort processing");
82 return -1;
83 }
85 *p = '\0'; /* Strip \r\n */
86 p = request;
88 if (strncmp(p, "gemini://", 9) != 0) {
89 log_info("Gemini scheme missing, abort processing");
90 return -1;
91 }
92 memmove(request, p + 9, strlen(request) + 1 - 9);
94 decoded_request = uridecode(request);
96 /* save hostname */
97 if ((p = strchr(decoded_request, '/')) != NULL)
98 snprintf(hostname, strlen(decoded_request) - strlen(p)+1, "%s",
99 decoded_request);
100 else
101 snprintf(hostname, strlen(decoded_request)+1, "%s", decoded_request);
103 /* Strip possible port (e.g. :1965) from hostname */
104 if ((p = strrchr(hostname, ':')) != NULL) {
105 pos = strlen(hostname) - strlen(p);
106 if (pos < 0 || pos > _POSIX_HOST_NAME_MAX)
107 fatalx("pos while shorten hostname out of range");
108 hostname[pos] = '\0';
111 /* Remove ../ for security reasons */
112 while ((p = strstr(decoded_request, "/..")) != NULL) {
113 memmove(decoded_request, p + 3, strlen(p) + 1 - 3);
116 if ((p = strchr(decoded_request, '/')) != NULL) {
117 /* Save all after the first / in localpath */
118 snprintf(localpath, strlen(decoded_request), "%s", p+1);
119 if (strlen(localpath) == 0) {
120 /*
121 * If the request is 'example.com/', localpart will be empty. In this case
122 * write the default to it.
123 */
124 sprintf(localpath, "index.gmi");
126 } else {
127 /* There is no slash in the request, so assume index.gmi */
128 sprintf(localpath, "index.gmi");
131 /*
132 *We do not need to take the base dir aka /var/db/gemini into account
133 * since we already chroot() to _PATH_TWIND_CHROOT .
135 * Here, a string truncation could happen. This can be implemented
136 * better! XXX FIXME
137 */
138 snprintf(finalpath, MAXREQLEN, "%s/%s", hostname, localpath);
140 /* Check if the wanted path exists and if it's a directory */
141 ret = check_gemini_file(finalpath);
142 if (ret < 0) {
143 log_debug("%s not found", finalpath);
144 free(decoded_request);
145 return -2;
146 } else if (ret == 1) {
147 log_debug("%s is a directory", finalpath);
148 /* Auto append index.gmi if destination is a directory */
149 snprintf(temp, MAXREQLEN, "%s", finalpath);
150 snprintf(finalpath, MAXREQLEN, "%s/index.gmi", temp);
153 log_debug("Got request for %s on server %s -> %s",
154 localpath, hostname, finalpath);
156 /* decoded_request is no longer used, so it can be freed */
157 free(decoded_request);
159 return 0;