1 /*****************************************************************************
2 #                                                                            #
3 #    uStreamer - Lightweight and fast MJPG-HTTP streamer.                    #
4 #                                                                            #
5 #    Copyright (C) 2018-2021  Maxim Devaev <mdevaev@gmail.com>               #
6 #                                                                            #
7 #    This program is free software: you can redistribute it and/or modify    #
8 #    it under the terms of the GNU General Public License as published by    #
9 #    the Free Software Foundation, either version 3 of the License, or       #
10 #    (at your option) any later version.                                     #
11 #                                                                            #
12 #    This program is distributed in the hope that it will be useful,         #
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of          #
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           #
15 #    GNU General Public License for more details.                            #
16 #                                                                            #
17 #    You should have received a copy of the GNU General Public License       #
18 #    along with this program.  If not, see <https://www.gnu.org/licenses/>.  #
19 #                                                                            #
20 *****************************************************************************/
21 
22 
23 #include "path.h"
24 
25 
simplify_request_path(const char * str)26 char *simplify_request_path(const char *str) {
27 	// Based on Lighttpd sources:
28 	//   - https://github.com/lighttpd/lighttpd1.4/blob/b31e7840d5403bc640579135b7004793b9ccd6c0/src/buffer.c#L840
29 	//   - https://github.com/lighttpd/lighttpd1.4/blob/77c01f981725512653c01cde5ca74c11633dfec4/src/t/test_buffer.c
30 
31 	char ch; // Current character
32 	char pre1; // The one before
33 	char pre2; // The one before that
34 	char *simplified;
35 	char *start;
36 	char *out;
37 	char *slash;
38 
39 	A_CALLOC(simplified, strlen(str) + 1);
40 
41 	if (str[0] == '\0') {
42 		simplified[0] = '\0';
43 		return simplified;
44 	}
45 
46 	start = simplified;
47 	out = simplified;
48 	slash = simplified;
49 
50 	// Skip leading spaces
51 	for (; *str == ' '; ++str);
52 
53 	if (*str == '.') {
54 		if (str[1] == '/' || str[1] == '\0') {
55 			++str;
56 		} else if (str[1] == '.' && (str[2] == '/' || str[2] == '\0')) {
57 			str += 2;
58 		}
59 	}
60 
61 	pre1 = '\0';
62 	ch = *(str++);
63 
64 	while (ch != '\0') {
65 		pre2 = pre1;
66 		pre1 = ch;
67 
68 		// Possibly: out == str - need to read first
69 		ch = *str;
70 		*out = pre1;
71 
72 		out++;
73 		str++;
74 		// (out <= str) still true; also now (slash < out)
75 
76 		if (ch == '/' || ch == '\0') {
77 			size_t toklen = out - slash;
78 
79 			if (toklen == 3 && pre2 == '.' && pre1 == '.' && *slash == '/') {
80 				// "/../" or ("/.." at end of string)
81 				out = slash;
82 				// If there is something before "/..", there is at least one
83 				// component, which needs to be removed
84 				if (out > start) {
85 					--out;
86 					for (; out > start && *out != '/'; --out);
87 				}
88 
89 				// Don't kill trailing '/' at end of path
90 				if (ch == '\0') {
91 					++out;
92 				}
93 				// slash < out before, so out_new <= slash + 1 <= out_before <= str
94 			} else if (toklen == 1 || (pre2 == '/' && pre1 == '.')) {
95 				// "//" or "/./" or (("/" or "/.") at end of string)
96 				out = slash;
97 				// Don't kill trailing '/' at end of path
98 				if (ch == '\0') {
99 					++out;
100 				}
101 				// Slash < out before, so out_new <= slash + 1 <= out_before <= str
102 			}
103 
104 			slash = out;
105 		}
106 	}
107 
108 	*out = '\0';
109 	return simplified;
110 }
111 
112 #ifdef TEST_HTTP_PATH
113 
test_simplify_request_path(const char * sample,const char * expected)114 int test_simplify_request_path(const char *sample, const char *expected) {
115 	char *result = simplify_request_path(sample);
116 	int retval = -!!strcmp(result, expected);
117 
118 	printf("Testing '%s' -> '%s' ... ", sample, expected);
119 	if (retval == 0) {
120 		printf("ok\n");
121 	} else {
122 		printf("FAILED; got '%s'\n", result);
123 	}
124 	free(result);
125 	return retval;
126 }
127 
main(void)128 int main(void) {
129 	int retval = 0;
130 
131 #	define TEST_SIMPLIFY_REQUEST_PATH(_sample, _expected) { \
132 			retval += test_simplify_request_path(_sample, _expected); \
133 		}
134 
135 	TEST_SIMPLIFY_REQUEST_PATH("", "");
136 	TEST_SIMPLIFY_REQUEST_PATH("   ", "");
137 	TEST_SIMPLIFY_REQUEST_PATH("/", "/");
138 	TEST_SIMPLIFY_REQUEST_PATH("//", "/");
139 	TEST_SIMPLIFY_REQUEST_PATH("abc", "abc");
140 	TEST_SIMPLIFY_REQUEST_PATH("abc//", "abc/");
141 	TEST_SIMPLIFY_REQUEST_PATH("abc/./xyz", "abc/xyz");
142 	TEST_SIMPLIFY_REQUEST_PATH("abc/.//xyz", "abc/xyz");
143 	TEST_SIMPLIFY_REQUEST_PATH("abc/../xyz", "/xyz");
144 	TEST_SIMPLIFY_REQUEST_PATH("/abc/./xyz", "/abc/xyz");
145 	TEST_SIMPLIFY_REQUEST_PATH("/abc//./xyz", "/abc/xyz");
146 	TEST_SIMPLIFY_REQUEST_PATH("/abc/../xyz", "/xyz");
147 	TEST_SIMPLIFY_REQUEST_PATH("abc/../xyz/.", "/xyz/");
148 	TEST_SIMPLIFY_REQUEST_PATH("/abc/../xyz/.", "/xyz/");
149 	TEST_SIMPLIFY_REQUEST_PATH("abc/./xyz/..", "abc/");
150 	TEST_SIMPLIFY_REQUEST_PATH("/abc/./xyz/..", "/abc/");
151 	TEST_SIMPLIFY_REQUEST_PATH(".", "");
152 	TEST_SIMPLIFY_REQUEST_PATH("..", "");
153 	TEST_SIMPLIFY_REQUEST_PATH("...", "...");
154 	TEST_SIMPLIFY_REQUEST_PATH("....", "....");
155 	TEST_SIMPLIFY_REQUEST_PATH(".../", ".../");
156 	TEST_SIMPLIFY_REQUEST_PATH("./xyz/..", "/");
157 	TEST_SIMPLIFY_REQUEST_PATH(".//xyz/..", "/");
158 	TEST_SIMPLIFY_REQUEST_PATH("/./xyz/..", "/");
159 	TEST_SIMPLIFY_REQUEST_PATH(".././xyz/..", "/");
160 	TEST_SIMPLIFY_REQUEST_PATH("/.././xyz/..", "/");
161 	TEST_SIMPLIFY_REQUEST_PATH("/.././xyz/..", "/");
162 	TEST_SIMPLIFY_REQUEST_PATH("../../../etc/passwd", "/etc/passwd");
163 	TEST_SIMPLIFY_REQUEST_PATH("/../../../etc/passwd", "/etc/passwd");
164 	TEST_SIMPLIFY_REQUEST_PATH("   ../../../etc/passwd", "/etc/passwd");
165 	TEST_SIMPLIFY_REQUEST_PATH("   /../../../etc/passwd", "/etc/passwd");
166 	TEST_SIMPLIFY_REQUEST_PATH("   /foo/bar/../../../etc/passwd", "/etc/passwd");
167 
168 #	undef TEST_SIMPLIFY_REQUEST_PATH
169 
170 	if (retval < 0) {
171 		printf("===== TEST FAILED =====\n");
172 	}
173 	return retval;
174 }
175 
176 #endif
177