|
1 | 1 | #include "kv_cache_manager/common/standard_uri.h" |
2 | 2 |
|
3 | | -#include "kv_cache_manager/common/string_util.h" |
| 3 | +#include <sstream> |
4 | 4 |
|
5 | 5 | namespace kv_cache_manager { |
6 | 6 |
|
@@ -28,67 +28,77 @@ bool StandardUri::Parse(const std::string &uri) { |
28 | 28 | protocol_ = uri.substr(0, pos_protocol_end); |
29 | 29 |
|
30 | 30 | size_t authority_start = pos_protocol_end + 3; // skip :// |
| 31 | + // Locate the end of authority before interpreting '@' or ':'. Delimiter |
| 32 | + // characters in the path/query belong to their values, not user-info or |
| 33 | + // host/port (for example callback URLs and email addresses). |
| 34 | + size_t pos_path_start = uri.find('/', authority_start); |
| 35 | + size_t pos_query_start = uri.find('?', authority_start); |
| 36 | + size_t host_end = std::min((pos_path_start != std::string::npos ? pos_path_start : uri.size()), |
| 37 | + (pos_query_start != std::string::npos ? pos_query_start : uri.size())); |
31 | 38 | size_t host_start = authority_start; |
32 | 39 | size_t pos_at = uri.find('@', authority_start); |
33 | | - if (pos_at != std::string::npos) { |
| 40 | + if (pos_at != std::string::npos && pos_at < host_end) { |
34 | 41 | user_info_ = uri.substr(authority_start, pos_at - authority_start); |
35 | 42 | host_start = pos_at + 1; // hostname 开始位置 |
36 | 43 | } |
37 | 44 |
|
38 | | - // 找 hostname 结束的位置(可能有 port) |
39 | | - size_t pos_path_start = uri.find('/', authority_start); |
40 | | - size_t pos_query_start = uri.find('?', authority_start); |
41 | | - size_t host_end = std::min((pos_path_start != std::string::npos ? pos_path_start : uri.size()), |
42 | | - (pos_query_start != std::string::npos ? pos_query_start : uri.size()) |
43 | | - |
44 | | - ); |
45 | | - // 分离 hostname 和 port |
46 | | - std::string host_port = uri.substr(host_start, host_end - host_start); |
47 | | - size_t colon_pos = host_port.find(':'); |
48 | | - if (colon_pos == std::string::npos) { |
49 | | - hostname_ = host_port; |
| 45 | + // 分离 hostname 和 port。直接在输入中定位,避免为每个 URI 先复制 |
| 46 | + // 一份 host:port 临时字符串。 |
| 47 | + size_t colon_pos = uri.find(':', host_start); |
| 48 | + if (colon_pos == std::string::npos || colon_pos >= host_end) { |
| 49 | + hostname_ = uri.substr(host_start, host_end - host_start); |
50 | 50 | } else { |
51 | | - hostname_ = host_port.substr(0, colon_pos); |
52 | | - std::string port_str = host_port.substr(colon_pos + 1); |
| 51 | + hostname_ = uri.substr(host_start, colon_pos - host_start); |
53 | 52 | int64_t tmp_port = 0; |
54 | | - if (!StringUtil::StrToInt64(port_str.c_str(), tmp_port)) { |
| 53 | + const char *port_begin = uri.data() + colon_pos + 1; |
| 54 | + const char *port_end = uri.data() + host_end; |
| 55 | + const auto [parsed_end, parse_ec] = std::from_chars(port_begin, port_end, tmp_port); |
| 56 | + if (port_begin == port_end || *port_begin == '-' || parse_ec != std::errc{} || parsed_end != port_end) { |
| 57 | + // Parse() is also used through the direct string constructor, |
| 58 | + // whose caller observes validity rather than the return value. |
| 59 | + // Do not leave a partially parsed object looking valid. |
| 60 | + protocol_.clear(); |
| 61 | + user_info_.clear(); |
| 62 | + hostname_.clear(); |
| 63 | + port_ = 0; |
| 64 | + path_.clear(); |
| 65 | + params_.clear(); |
55 | 66 | return false; |
56 | 67 | } else { |
57 | 68 | port_ = tmp_port; |
58 | 69 | } |
59 | 70 | } |
60 | 71 |
|
61 | 72 | // 提取 path 和 query |
62 | | - if (pos_path_start != std::string::npos && pos_path_start < uri.size()) { |
| 73 | + if (pos_path_start != std::string::npos && |
| 74 | + (pos_query_start == std::string::npos || pos_path_start < pos_query_start)) { |
63 | 75 | if (pos_query_start != std::string::npos && pos_path_start < pos_query_start) { |
64 | 76 | path_ = uri.substr(pos_path_start, pos_query_start - pos_path_start); |
65 | | - std::string query_str = uri.substr(pos_query_start + 1); |
66 | | - ParseParams(query_str); |
| 77 | + ParseParams(std::string_view(uri).substr(pos_query_start + 1)); |
67 | 78 | } else { |
68 | 79 | path_ = uri.substr(pos_path_start); |
69 | 80 | } |
70 | 81 | } else if (pos_query_start != std::string::npos && pos_query_start < uri.size()) { |
71 | | - std::string query_str = uri.substr(pos_query_start + 1); |
72 | | - ParseParams(query_str); |
| 82 | + ParseParams(std::string_view(uri).substr(pos_query_start + 1)); |
73 | 83 | } |
74 | 84 | return true; |
75 | 85 | } |
76 | 86 |
|
77 | | -bool StandardUri::ParseParams(const std::string &uri_params) { |
78 | | - auto start = 0; |
| 87 | +bool StandardUri::ParseParams(std::string_view uri_params) { |
| 88 | + size_t start = 0; |
79 | 89 | while (start < uri_params.size()) { |
80 | 90 | auto end = uri_params.find('&', start); |
81 | 91 | if (end == std::string::npos) { |
82 | 92 | end = uri_params.size(); |
83 | 93 | } |
84 | 94 | auto eq_pos = uri_params.find('=', start); |
85 | 95 | if (eq_pos != std::string::npos && eq_pos < end) { |
86 | | - std::string key = uri_params.substr(start, eq_pos - start); |
87 | | - std::string value = uri_params.substr(eq_pos + 1, end - eq_pos - 1); |
| 96 | + std::string key(uri_params.substr(start, eq_pos - start)); |
| 97 | + std::string value(uri_params.substr(eq_pos + 1, end - eq_pos - 1)); |
88 | 98 | params_[key] = value; |
89 | 99 | } else { |
90 | 100 | // key但无value,value空字符串 |
91 | | - std::string key = uri_params.substr(start, end - start); |
| 101 | + std::string key(uri_params.substr(start, end - start)); |
92 | 102 | params_[key] = ""; |
93 | 103 | } |
94 | 104 | start = end + 1; |
@@ -125,6 +135,53 @@ std::string StandardUri::ToUriString() const { |
125 | 135 | return ss.str(); |
126 | 136 | } |
127 | 137 |
|
| 138 | +std::string StandardUri::ToUriStringWithExtraParam(const std::string &key, const std::string &value) const { |
| 139 | + if (!Valid() || key.empty() || HasParam(key)) { |
| 140 | + return ""; |
| 141 | + } |
| 142 | + |
| 143 | + size_t estimated_size = |
| 144 | + protocol_.size() + user_info_.size() + hostname_.size() + path_.size() + key.size() + value.size() + 8; |
| 145 | + for (const auto &[param_key, param_value] : params_) { |
| 146 | + estimated_size += param_key.size() + param_value.size() + 2; |
| 147 | + } |
| 148 | + std::string result; |
| 149 | + result.reserve(estimated_size); |
| 150 | + result.append(protocol_).append("://"); |
| 151 | + if (!user_info_.empty()) { |
| 152 | + result.append(user_info_).push_back('@'); |
| 153 | + } |
| 154 | + result.append(hostname_); |
| 155 | + if (port_ > 0) { |
| 156 | + result.push_back(':'); |
| 157 | + result.append(std::to_string(port_)); |
| 158 | + } |
| 159 | + result.append(path_); |
| 160 | + result.push_back('?'); |
| 161 | + |
| 162 | + bool first = true; |
| 163 | + bool extra_written = false; |
| 164 | + auto append_param = [&result, &first](const std::string ¶m_key, const std::string ¶m_value) { |
| 165 | + if (!first) { |
| 166 | + result.push_back('&'); |
| 167 | + } |
| 168 | + result.append(param_key).push_back('='); |
| 169 | + result.append(param_value); |
| 170 | + first = false; |
| 171 | + }; |
| 172 | + for (const auto &[param_key, param_value] : params_) { |
| 173 | + if (!extra_written && key < param_key) { |
| 174 | + append_param(key, value); |
| 175 | + extra_written = true; |
| 176 | + } |
| 177 | + append_param(param_key, param_value); |
| 178 | + } |
| 179 | + if (!extra_written) { |
| 180 | + append_param(key, value); |
| 181 | + } |
| 182 | + return result; |
| 183 | +} |
| 184 | + |
128 | 185 | StandardUri StandardUri::FromUri(const std::string &source) { |
129 | 186 | StandardUri result; |
130 | 187 | if (!result.Parse(source)) { |
|
0 commit comments