GCC Code Coverage Report


Directory: ./
File: src/util/uri.cc
Date: 2025-09-01 06:19:01
Exec Total Coverage
Lines: 85 145 58.6%
Functions: 6 15 40.0%
Branches: 84 120 70.0%

Line Branch Exec Source
1 #include "na64util/uri.hh"
2 #include "na64util/str-fmt.hh"
3
4 #include <wordexp.h>
5 #include <regex>
6
7 namespace na64dp {
8 namespace errors {
9 InvalidURI::InvalidURI(const char * es) throw() : GenericRuntimeError(es) {}
10 } // namespace ::na64dp::errors
11 namespace util {
12
13 std::vector<std::string>
14 expand_names( const std::string & expr ) {
15 std::vector<std::string> r;
16 wordexp_t p;
17 char ** w;
18 int wordexpResult = wordexp( expr.c_str(), &p, WRDE_UNDEF | WRDE_NOCMD );
19 switch(wordexpResult) {
20 case WRDE_BADCHAR: {
21 NA64DP_RUNTIME_ERROR("wordexp(\"%s\") error: illegal character"
22 " occurence", expr.c_str() );
23 } break;
24 case WRDE_BADVAL: {
25 NA64DP_RUNTIME_ERROR("wordexp(\"%s\") error: undefined shell"
26 " variable referenced", expr.c_str());
27 } break;
28 case WRDE_CMDSUB: {
29 NA64DP_RUNTIME_ERROR("wordexp(\"%s\") error: command substitution"
30 " prohibited", expr.c_str());
31 } break;
32 case WRDE_NOSPACE: {
33 NA64DP_RUNTIME_ERROR("wordexp(\"%s\") error: out of memory"
34 , expr.c_str());
35 } break;
36 case WRDE_SYNTAX: {
37 NA64DP_RUNTIME_ERROR("wordexp(\"%s\") error: shell syntax error"
38 , expr.c_str());
39 } break;
40 };
41 w = p.we_wordv;
42 if( 0 == p.we_wordc ) {
43 wordfree( &p );
44 return r;
45 }
46 r.reserve(p.we_wordc);
47 for( size_t i=0; i < p.we_wordc; i++ ) {
48 r.push_back(w[i]);
49 }
50 wordfree( &p );
51 return r;
52 }
53
54 std::string
55 expand_name(const std::string & expr) {
56 auto l = expand_names(expr);
57 if(l.empty())
58 NA64DP_RUNTIME_ERROR( "Expression \"%s\" expanded as none strings, but"
59 " single non-empty string expected."
60 , expr.c_str() );
61 if(l.size() > 1)
62 NA64DP_RUNTIME_ERROR( "Expression \"%s\" expanded as %zu entries, but"
63 " single non-empty string expected."
64 , expr.c_str(), l.size() );
65 if(l[0].empty())
66 NA64DP_RUNTIME_ERROR( "Expression \"%s\" expanded as empty string, but"
67 " non-empty string expected."
68 , expr.c_str() );
69 return l[0];
70 }
71
72
73 std::unordered_multimap<std::string, std::string>
74 2 URI::parse_query_string( const std::string & str
75 , const std::string & rxS
76 ) {
77
1/1
✓ Branch 1 taken 2 times.
2 std::regex rx(rxS);
78 2 std::unordered_multimap<std::string, std::string> result;
79
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
2 if(str.empty()) return result;
80 auto const vec = std::vector<std::string>(
81
1/1
✓ Branch 1 taken 1 times.
2 std::sregex_token_iterator{begin(str), end(str), rx, -1},
82
1/1
✓ Branch 1 taken 1 times.
2 std::sregex_token_iterator{}
83
1/1
✓ Branch 1 taken 1 times.
1 );
84
3/3
✓ Branch 4 taken 2 times.
✓ Branch 8 taken 2 times.
✓ Branch 9 taken 1 times.
3 for(auto entry : vec) {
85 2 size_t nEq = entry.find('=');
86
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if(std::string::npos != nEq)
87
3/3
✓ Branch 1 taken 2 times.
✓ Branch 4 taken 2 times.
✓ Branch 7 taken 2 times.
2 result.emplace( entry.substr(0, nEq), entry.substr(nEq+1) );
88 else
89 result.emplace( entry, "");
90 2 }
91 1 return result;
92 2 }
93
94 void
95 URI::scheme(const std::string & scheme_) {
96 _scheme = scheme_;
97 }
98
99 void
100 URI::port(const std::string & strPortNo) {
101 // TODO: check only int
102 _port = strPortNo;
103 }
104
105 void
106 URI::port(uint16_t portNo) {
107 _port = std::to_string(portNo);
108 }
109
110 void
111 URI::host(const std::string & host_) {
112 _host = host_;
113 }
114
115 void
116 URI::path(const std::string & path_) {
117 // TODO: check validity
118 _path = path_;
119 }
120
121 //
122 // URI and related
123
124 //static const std::string _2encode = " !\"#$%&'()*+,/:;=?@[]";
125
126 std::string
127 1 URI::encode(const std::string & s) {
128 1 std::string encoded;
129
1/1
✓ Branch 2 taken 1 times.
1 encoded.reserve(s.size());
130
2/2
✓ Branch 1 taken 36 times.
✓ Branch 2 taken 1 times.
37 for(const char * c = s.c_str(); *c != '\0'; ++c) {
131
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 14 times.
36 if( isalnum(*c)
132
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 || *c == '-'
133
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 || *c == '_'
134
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 || *c == '.'
135
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 || *c == '~' ) {
136
1/1
✓ Branch 1 taken 14 times.
14 encoded += *c;
137 14 continue;
138 } else {
139 char bf[8];
140 22 snprintf(bf, sizeof(bf), "%02X", (int) *c);
141
1/1
✓ Branch 1 taken 22 times.
22 encoded += '%';
142
1/1
✓ Branch 1 taken 22 times.
22 encoded += bf;
143 }
144 }
145 1 return encoded;
146 }
147
148 std::string
149 1 URI::decode(const std::string & s) {
150 1 std::string decoded;
151
1/1
✓ Branch 2 taken 1 times.
1 decoded.reserve(s.size());
152
2/2
✓ Branch 1 taken 36 times.
✓ Branch 2 taken 1 times.
37 for(const char * c = s.c_str(); '\0' != *c; ++c) {
153
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 22 times.
36 if(*c != '%') {
154
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 if('+' != *c)
155
1/1
✓ Branch 1 taken 14 times.
14 decoded += *c;
156 else
157 decoded += ' ';
158 } else {
159
2/4
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 22 times.
22 if(c[1] == '\0' || c[2] == '\0') {
160 throw std::runtime_error("Bad string to decode (%%-encoding truncated)");
161 }
162 int code;
163 22 char pbf[] = { c[1], c[2], '\0' };
164 22 sscanf(pbf, "%02X", &code);
165
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 if(code > 0xff) {
166 throw std::runtime_error("Could not url-decode code large"
167 " number (extended set?)");
168 }
169
1/1
✓ Branch 1 taken 22 times.
22 decoded += (char) code;
170 22 c += 2;
171 }
172 }
173 1 return decoded;
174 }
175
176 // See: https://www.rfc-editor.org/rfc/rfc3986#appendix-B
177 static const std::regex _rxURI(
178 R"~(^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?)~");
179 // 12 3 4 5 6 7 8 9
180
181 2 URI::URI(const std::string & strUri) {
182 2 std::smatch m;
183
2/3
✓ Branch 1 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
2 if(!std::regex_match(strUri, m, _rxURI)) {
184 char errBf[128];
185 snprintf( errBf, sizeof(errBf)
186 , "String does not match URI format: \"%s\""
187 , strUri.c_str() );
188 throw errors::InvalidURI(errBf);
189 }
190
191
1/1
✓ Branch 3 taken 2 times.
2 std::vector<std::string> groups(m.begin(), m.end());
192
193
1/1
✓ Branch 2 taken 2 times.
2 _scheme = groups[2];
194 //_host = groups[4];
195
1/1
✓ Branch 2 taken 2 times.
2 _path = groups[5];
196
2/2
✓ Branch 1 taken 2 times.
✓ Branch 5 taken 2 times.
2 _qParams = parse_query_string(groups[7]);
197
1/1
✓ Branch 2 taken 2 times.
2 _fragment = groups[9];
198
199
1/1
✓ Branch 2 taken 2 times.
2 std::string authority = groups[4];
200
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
2 if(!authority.empty()) {
201 1 size_t n = authority.find('@');
202
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if(n != std::string::npos) {
203
1/1
✓ Branch 1 taken 1 times.
1 _userinfo = authority.substr(0, n);
204 }
205
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if(n == std::string::npos) n = 0; else ++n;
206 1 size_t nn = authority.find(':', n);
207
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if(nn != std::string::npos) {
208
1/1
✓ Branch 1 taken 1 times.
1 _host = authority.substr(n, nn - n );
209
1/1
✓ Branch 1 taken 1 times.
1 _port = authority.substr(++nn);
210 } else {
211 _host = authority.substr(n);
212 }
213 }
214
215 #if 0 // might be helpful in debug
216 std::cout << " scheme: \"" << _scheme << "\"" << std::endl
217 << " authority: \"" << authority << "\"" << std::endl
218 << " userinfo: \"" << _userinfo << "\"" << std::endl
219 << " host: \"" << _host << "\"" << std::endl
220 << " port: \"" << _port << "\"" << std::endl
221 << " path: \"" << _path << "\"" << std::endl
222 << " query: \"" << _query << "\"" << std::endl
223 << " fragment: \"" << _fragment << "\"" << std::endl
224 ;
225 #endif
226 2 }
227
228 std::string
229 3 URI::authority() const {
230
1/1
✓ Branch 1 taken 3 times.
3 std::ostringstream oss;
231
4/4
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
✓ Branch 4 taken 1 times.
✓ Branch 7 taken 1 times.
3 if( !_userinfo.empty() ) oss << _userinfo << '@';
232 3 oss << ( _host.empty()
233
7/10
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 2 times.
✓ Branch 12 taken 2 times.
✓ Branch 13 taken 1 times.
✗ Branch 16 not taken.
8 ? ((_port.empty() && _userinfo.empty()) ? "" : "localhost")
234
1/1
✓ Branch 1 taken 1 times.
1 : _host
235
1/1
✓ Branch 1 taken 3 times.
3 );
236
4/4
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
✓ Branch 4 taken 1 times.
✓ Branch 7 taken 1 times.
3 if( !_port.empty()) oss << ':' << _port;
237
1/1
✓ Branch 1 taken 3 times.
6 return oss.str();
238 3 }
239
240 std::string
241 URI::query_str() const {
242 std::ostringstream oss;
243 bool isFirst = true;
244 for(auto entry : _qParams) {
245 if(isFirst) isFirst = false; else oss << "&";
246 oss << entry.first << "=" << entry.second;
247 }
248 return oss.str();
249 }
250
251 std::string
252 1 URI::to_str(bool noCheck) const {
253
1/1
✓ Branch 1 taken 1 times.
1 std::ostringstream oss;
254
255
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if(!_scheme.empty()) oss << _scheme << ':';
256
1/1
✓ Branch 1 taken 1 times.
1 std::string auth = authority();
257
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if(!auth.empty()) {
258 oss << "//" << authority();
259 }
260
2/3
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
1 if(!_path.empty()) oss << _path;
261
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if(!_qParams.empty()) oss << '?' << query_str();
262
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if(!_fragment.empty()) oss << '#' << _fragment;
263
264
1/1
✓ Branch 1 taken 1 times.
1 std::string s = oss.str();
265
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if(!noCheck) {
266
2/3
✓ Branch 1 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
1 if(!std::regex_match(s, _rxURI)) {
267 char errBf[128];
268 snprintf( errBf, sizeof(errBf)
269 , "Incomplete URI: \"%s\""
270 , s.c_str() );
271 throw errors::InvalidURI(errBf);
272 }
273 }
274 2 return s;
275 1 }
276
277 } // namespace ::na64dp::util
278 } // namespace na64dp
279