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