Coverage Report

Created: 2024-09-19 09:45

/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