A11 (C++ runtime)
Native C++ implementation of the A11 streaming action runtime
Loading...
Searching...
No Matches
percent.h
Go to the documentation of this file.
1/*
2 * Copyright 2026 The A11 Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
26#ifndef A11_PERCENT_H_
27#define A11_PERCENT_H_
28
29#include <cstddef>
30#include <string>
31#include <string_view>
32
33#include <absl/status/status.h>
34#include <absl/status/statusor.h>
35#include <absl/strings/ascii.h>
36#include <absl/strings/str_cat.h>
37
38namespace a11::percent {
39
41inline int HexDigit(char one) {
42 if (one >= '0' && one <= '9') {
43 return one - '0';
44 }
45 if (one >= 'a' && one <= 'f') {
46 return one - 'a' + 10;
47 }
48 if (one >= 'A' && one <= 'F') {
49 return one - 'A' + 10;
50 }
51 return -1;
52}
53
67inline std::string Decode(std::string_view value, bool plus_is_space = false) {
68 std::string decoded;
69 decoded.reserve(value.size());
70 for (size_t index = 0; index < value.size(); ++index) {
71 const char one = value[index];
72 if (plus_is_space && one == '+') {
73 decoded.push_back(' ');
74 continue;
75 }
76 if (one == '%' && index + 2 < value.size()) {
77 const int high = HexDigit(value[index + 1]);
78 const int low = HexDigit(value[index + 2]);
79 if (high >= 0 && low >= 0) {
80 decoded.push_back(static_cast<char>((high << 4) | low));
81 index += 2;
82 continue;
83 }
84 }
85 decoded.push_back(one);
86 }
87 return decoded;
88}
89
100inline absl::StatusOr<std::string> DecodeStrict(std::string_view value,
101 std::string_view what) {
102 std::string decoded;
103 decoded.reserve(value.size());
104 for (size_t index = 0; index < value.size(); ++index) {
105 if (value[index] != '%') {
106 decoded.push_back(value[index]);
107 continue;
108 }
109 if (index + 2 >= value.size()) {
110 return absl::InvalidArgumentError(
111 absl::StrCat("Truncated percent escape in ", what));
112 }
113 const int high = HexDigit(value[index + 1]);
114 const int low = HexDigit(value[index + 2]);
115 if (high < 0 || low < 0) {
116 return absl::InvalidArgumentError(
117 absl::StrCat("Invalid percent escape in ", what));
118 }
119 decoded.push_back(static_cast<char>((high << 4) | low));
120 index += 2;
121 }
122 return decoded;
123}
124
126inline std::string Encode(std::string_view value) {
127 static constexpr std::string_view kLowerHex = "0123456789abcdef";
128 std::string out;
129 out.reserve(value.size());
130 for (const char ch : value) {
131 const auto byte = static_cast<unsigned char>(ch);
132 if (absl::ascii_isalnum(byte) || byte == '-' || byte == '.' ||
133 byte == '_' || byte == '~') {
134 out.push_back(ch);
135 continue;
136 }
137 out.push_back('%');
138 out.push_back(kLowerHex[byte >> 4U]);
139 out.push_back(kLowerHex[byte & 0x0FU]);
140 }
141 return out;
142}
143
144} // namespace a11::percent
145
146#endif // A11_PERCENT_H_
std::string value
Definition discover.cc:114
Definition percent.h:38
int HexDigit(char one)
The value 0-15 of a hex digit, either case, or -1 if one is not one.
Definition percent.h:41
absl::StatusOr< std::string > DecodeStrict(std::string_view value, std::string_view what)
Percent-decodes value, or says why it cannot be decoded.
Definition percent.h:100
std::string Decode(std::string_view value, bool plus_is_space=false)
Percent-decodes value, leaving anything malformed as it stands.
Definition percent.h:67
std::string Encode(std::string_view value)
Percent-encodes everything outside RFC 3986's unreserved set.
Definition percent.h:126