/proc/self/cwd/source/common/http/path_utility.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include "source/common/http/path_utility.h" |
2 | | |
3 | | #include "source/common/common/logger.h" |
4 | | |
5 | | #include "absl/strings/str_join.h" |
6 | | #include "absl/strings/str_replace.h" |
7 | | #include "absl/strings/str_split.h" |
8 | | #include "absl/types/optional.h" |
9 | | #include "url/url_canon.h" |
10 | | #include "url/url_canon_stdstring.h" |
11 | | |
12 | | namespace Envoy { |
13 | | namespace Http { |
14 | | |
15 | | namespace { |
16 | 226 | absl::optional<std::string> canonicalizePath(absl::string_view original_path) { |
17 | 226 | std::string canonical_path; |
18 | 226 | url::Component in_component(0, original_path.size()); |
19 | 226 | url::Component out_component; |
20 | 226 | url::StdStringCanonOutput output(&canonical_path); |
21 | 226 | if (!url::CanonicalizePath(original_path.data(), in_component, &output, &out_component)) { |
22 | 2 | return absl::nullopt; |
23 | 2 | } |
24 | 224 | output.Complete(); |
25 | 224 | return absl::make_optional(std::move(canonical_path)); |
26 | 226 | } |
27 | | } // namespace |
28 | | |
29 | | /* static */ |
30 | 226 | bool PathUtil::canonicalPath(RequestHeaderMap& headers) { |
31 | 226 | ASSERT(headers.Path()); |
32 | 226 | const auto original_path = headers.getPathValue(); |
33 | | // canonicalPath is supposed to apply on path component in URL instead of :path header |
34 | 226 | const auto query_pos = original_path.find('?'); |
35 | 226 | auto normalized_path_opt = canonicalizePath( |
36 | 226 | query_pos == original_path.npos |
37 | 226 | ? original_path |
38 | 226 | : absl::string_view(original_path.data(), query_pos) // '?' is not included |
39 | 226 | ); |
40 | | |
41 | 226 | if (!normalized_path_opt.has_value()) { |
42 | 2 | return false; |
43 | 2 | } |
44 | 224 | auto& normalized_path = normalized_path_opt.value(); |
45 | 224 | const absl::string_view query_suffix = |
46 | 224 | query_pos == original_path.npos |
47 | 224 | ? absl::string_view{} |
48 | 224 | : absl::string_view{original_path.data() + query_pos, original_path.size() - query_pos}; |
49 | 224 | if (!query_suffix.empty()) { |
50 | 178 | normalized_path.insert(normalized_path.end(), query_suffix.begin(), query_suffix.end()); |
51 | 178 | } |
52 | 224 | headers.setPath(normalized_path); |
53 | 224 | return true; |
54 | 226 | } |
55 | | |
56 | 884 | void PathUtil::mergeSlashes(RequestHeaderMap& headers) { |
57 | 884 | ASSERT(headers.Path()); |
58 | 884 | const auto original_path = headers.getPathValue(); |
59 | | // Only operate on path component in URL. |
60 | 884 | const absl::string_view::size_type query_start = original_path.find('?'); |
61 | 884 | const absl::string_view path = original_path.substr(0, query_start); |
62 | 884 | const absl::string_view query = absl::ClippedSubstr(original_path, query_start); |
63 | 884 | if (path.find("//") == absl::string_view::npos) { |
64 | 600 | return; |
65 | 600 | } |
66 | 284 | const absl::string_view path_prefix = absl::StartsWith(path, "/") ? "/" : absl::string_view(); |
67 | 284 | const absl::string_view path_suffix = absl::EndsWith(path, "/") ? "/" : absl::string_view(); |
68 | 284 | headers.setPath(absl::StrCat(path_prefix, |
69 | 284 | absl::StrJoin(absl::StrSplit(path, '/', absl::SkipEmpty()), "/"), |
70 | 284 | path_suffix, query)); |
71 | 284 | } |
72 | | |
73 | 0 | PathUtil::UnescapeSlashesResult PathUtil::unescapeSlashes(RequestHeaderMap& headers) { |
74 | 0 | ASSERT(headers.Path()); |
75 | 0 | const auto original_path = headers.getPathValue(); |
76 | 0 | const auto original_length = original_path.length(); |
77 | | // Only operate on path component in URL. |
78 | 0 | const absl::string_view::size_type query_start = original_path.find('?'); |
79 | 0 | const absl::string_view path = original_path.substr(0, query_start); |
80 | 0 | if (path.find('%') == absl::string_view::npos) { |
81 | 0 | return UnescapeSlashesResult::NotFound; |
82 | 0 | } |
83 | 0 | const absl::string_view query = absl::ClippedSubstr(original_path, query_start); |
84 | |
|
85 | 0 | static const std::vector<std::pair<absl::string_view, absl::string_view>> replacements{ |
86 | 0 | {"%2F", "/"}, |
87 | 0 | {"%2f", "/"}, |
88 | 0 | {"%5C", "\\"}, |
89 | 0 | {"%5c", "\\"}, |
90 | 0 | }; |
91 | 0 | headers.setPath(absl::StrCat(absl::StrReplaceAll(path, replacements), query)); |
92 | | |
93 | | // Path length will not match if there were unescaped %2f or %5c |
94 | 0 | return headers.getPathValue().length() != original_length |
95 | 0 | ? UnescapeSlashesResult::FoundAndUnescaped |
96 | 0 | : UnescapeSlashesResult::NotFound; |
97 | 0 | } |
98 | | |
99 | 39.9k | absl::string_view PathUtil::removeQueryAndFragment(const absl::string_view path) { |
100 | 39.9k | absl::string_view ret = path; |
101 | | // Trim query parameters and/or fragment if present. |
102 | 39.9k | size_t offset = ret.find_first_of("?#"); |
103 | 39.9k | if (offset != absl::string_view::npos) { |
104 | 5.22k | ret.remove_suffix(ret.length() - offset); |
105 | 5.22k | } |
106 | 39.9k | return ret; |
107 | 39.9k | } |
108 | | |
109 | | } // namespace Http |
110 | | } // namespace Envoy |