/***************************************************************************** # # # uStreamer - Lightweight and fast MJPG-HTTP streamer. # # # # Copyright (C) 2018 Maxim Devaev # # # # This program is free software: you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # # the Free Software Foundation, either version 3 of the License, or # # (at your option) any later version. # # # # This program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # GNU General Public License for more details. # # # # You should have received a copy of the GNU General Public License # # along with this program. If not, see . # # # *****************************************************************************/ #include #include #include #include #include "../tools.h" #include "path.h" char *simplify_request_path(const char *str) { // Based on Lighttpd sources: // - https://github.com/lighttpd/lighttpd1.4/blob/b31e7840d5403bc640579135b7004793b9ccd6c0/src/buffer.c#L840 // - https://github.com/lighttpd/lighttpd1.4/blob/77c01f981725512653c01cde5ca74c11633dfec4/src/t/test_buffer.c char ch; // Current character char pre1; // The one before char pre2; // The one before that char *simplified; char *start; char *out; char *slash; A_CALLOC(simplified, strlen(str) + 1); if (str[0] == '\0') { simplified[0] = '\0'; return simplified; } start = simplified; out = simplified; slash = simplified; // Skip leading spaces for (; *str == ' '; ++str); if (*str == '.') { if (str[1] == '/' || str[1] == '\0') { ++str; } else if (str[1] == '.' && (str[2] == '/' || str[2] == '\0')) { str += 2; } } pre1 = '\0'; ch = *(str++); while (ch != '\0') { pre2 = pre1; pre1 = ch; // Possibly: out == str - need to read first ch = *str; *out = pre1; out++; str++; // (out <= str) still true; also now (slash < out) if (ch == '/' || ch == '\0') { size_t toklen = out - slash; if (toklen == 3 && pre2 == '.' && pre1 == '.' && *slash == '/') { // "/../" or ("/.." at end of string) out = slash; // If there is something before "/..", there is at least one // component, which needs to be removed if (out > start) { --out; for (; out > start && *out != '/'; --out); } // Don't kill trailing '/' at end of path if (ch == '\0') { ++out; } // slash < out before, so out_new <= slash + 1 <= out_before <= str } else if (toklen == 1 || (pre2 == '/' && pre1 == '.')) { // "//" or "/./" or (("/" or "/.") at end of string) out = slash; // Don't kill trailing '/' at end of path if (ch == '\0') { ++out; } // Slash < out before, so out_new <= slash + 1 <= out_before <= str } slash = out; } } *out = '\0'; return simplified; } #if 0 int test_simplify_request_path(const char *sample, const char *expected) { char *result = simplify_request_path(sample); int retval = -!!strcmp(result, expected); printf("Testing '%s' -> '%s' ... ", sample, expected); if (retval == 0) { printf("ok\n"); } else { printf("FAILED; got '%s'\n", result); } free(result); return retval; } int main(void) { int retval = 0; # define TEST_SIMPLIFY_REQUEST_PATH(_sample, _expected) { \ retval += test_simplify_request_path(_sample, _expected); \ } TEST_SIMPLIFY_REQUEST_PATH("", ""); TEST_SIMPLIFY_REQUEST_PATH(" ", ""); TEST_SIMPLIFY_REQUEST_PATH("/", "/"); TEST_SIMPLIFY_REQUEST_PATH("//", "/"); TEST_SIMPLIFY_REQUEST_PATH("abc", "abc"); TEST_SIMPLIFY_REQUEST_PATH("abc//", "abc/"); TEST_SIMPLIFY_REQUEST_PATH("abc/./xyz", "abc/xyz"); TEST_SIMPLIFY_REQUEST_PATH("abc/.//xyz", "abc/xyz"); TEST_SIMPLIFY_REQUEST_PATH("abc/../xyz", "/xyz"); TEST_SIMPLIFY_REQUEST_PATH("/abc/./xyz", "/abc/xyz"); TEST_SIMPLIFY_REQUEST_PATH("/abc//./xyz", "/abc/xyz"); TEST_SIMPLIFY_REQUEST_PATH("/abc/../xyz", "/xyz"); TEST_SIMPLIFY_REQUEST_PATH("abc/../xyz/.", "/xyz/"); TEST_SIMPLIFY_REQUEST_PATH("/abc/../xyz/.", "/xyz/"); TEST_SIMPLIFY_REQUEST_PATH("abc/./xyz/..", "abc/"); TEST_SIMPLIFY_REQUEST_PATH("/abc/./xyz/..", "/abc/"); TEST_SIMPLIFY_REQUEST_PATH(".", ""); TEST_SIMPLIFY_REQUEST_PATH("..", ""); TEST_SIMPLIFY_REQUEST_PATH("...", "..."); TEST_SIMPLIFY_REQUEST_PATH("....", "...."); TEST_SIMPLIFY_REQUEST_PATH(".../", ".../"); TEST_SIMPLIFY_REQUEST_PATH("./xyz/..", "/"); TEST_SIMPLIFY_REQUEST_PATH(".//xyz/..", "/"); TEST_SIMPLIFY_REQUEST_PATH("/./xyz/..", "/"); TEST_SIMPLIFY_REQUEST_PATH(".././xyz/..", "/"); TEST_SIMPLIFY_REQUEST_PATH("/.././xyz/..", "/"); TEST_SIMPLIFY_REQUEST_PATH("/.././xyz/..", "/"); TEST_SIMPLIFY_REQUEST_PATH("../../../etc/passwd", "/etc/passwd"); TEST_SIMPLIFY_REQUEST_PATH("/../../../etc/passwd", "/etc/passwd"); TEST_SIMPLIFY_REQUEST_PATH(" ../../../etc/passwd", "/etc/passwd"); TEST_SIMPLIFY_REQUEST_PATH(" /../../../etc/passwd", "/etc/passwd"); TEST_SIMPLIFY_REQUEST_PATH(" /foo/bar/../../../etc/passwd", "/etc/passwd"); # undef TEST_SIMPLIFY_REQUEST_PATH if (retval < 0) { printf("===== TEST FAILED =====\n"); } return retval; } #endif