WPILibC++ 2027.0.0-alpha-4
Loading...
Searching...
No Matches
HttpUtil.hpp
Go to the documentation of this file.
1// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5#pragma once
6
7#include <initializer_list>
8#include <memory>
9#include <optional>
10#include <span>
11#include <string>
12#include <string_view>
13#include <utility>
14#include <vector>
15
20#include "wpi/util/SmallVector.hpp"
23
24namespace wpi::net {
25
26// Unescape a %xx-encoded URI.
27// @param buf Buffer for output
28// @param error Set to true if an error occurred
29// @return Escaped string
30std::string_view UnescapeURI(std::string_view str,
32 bool* error);
33
34// Escape a string with %xx-encoding.
35// @param buf Buffer for output
36// @param spacePlus If true, encodes spaces to '+' rather than "%20"
37// @return Escaped string
38std::string_view EscapeURI(std::string_view str,
40 bool spacePlus = true);
41
42// Escape a string for HTML output.
43// @param buf Buffer for output
44// @return Escaped string
45std::string_view EscapeHTML(std::string_view str,
47
48// Parse a set of HTTP headers. Saves just the Content-Type and Content-Length
49// fields.
50// @param is Input stream
51// @param contentType If not null, Content-Type contents are saved here.
52// @param contentLength If not null, Content-Length contents are saved here.
53// @return False if error occurred in input stream
57
58// Look for a MIME multi-part boundary. On return, the input stream will
59// be located at the character following the boundary (usually "\r\n").
60// @param is Input stream
61// @param boundary Boundary string to scan for (not including "--" prefix)
62// @param saveBuf If not null, all scanned characters up to but not including
63// the boundary are saved to this string
64// @return False if error occurred on input stream, true if boundary found.
66 std::string_view boundary, std::string* saveBuf);
67
68/**
69 * Map for looking up elements of the query portion of a URI. Does not
70 * handle multiple elements with the same name. This is a reference type;
71 * it does not make a copy of the query string, so it is important that the
72 * query string has a lifetime at least as long as this object.
73 */
75 public:
76 /**
77 * Constructs an empty map (with no entries).
78 */
79 HttpQueryMap() = default;
80
81 /**
82 * Constructs from an escaped query string. Note: does not make a copy of
83 * the query string, so it must not be a temporary.
84 *
85 * @param query query string
86 */
87 explicit HttpQueryMap(std::string_view query);
88
89 /**
90 * Gets an element of the query string. Both the name and the returned
91 * value are unescaped strings.
92 *
93 * @param name name (unescaped)
94 * @param buf result buffer for value
95 * @return Optional unescaped value. Returns an empty optional if the
96 * name is not present in the query map.
97 */
98 std::optional<std::string_view> Get(
99 std::string_view name, wpi::util::SmallVectorImpl<char>& buf) const;
100
101 private:
103};
104
105class HttpPathRef;
106
107/**
108 * Class for HTTP path matching. A root path is represented as a single
109 * empty element, otherwise the path consists of each non-empty element
110 * between the '/' characters:
111 * - "" -> []
112 * - "/" -> [""]
113 * - "/foo" -> ["foo"]
114 * - "/foo/bar" -> ["foo", "bar"]
115 * - "/foo//bar/" -> ["foo", "bar"]
116 *
117 * All path elements are unescaped.
118 */
119class HttpPath {
120 public:
121 /**
122 * Constructs an empty HTTP path.
123 */
124 HttpPath() = default;
125
126 /**
127 * Constructs a HTTP path from an escaped path string. Makes a copy of the
128 * path, so it's safe to be a temporary.
129 */
130 explicit HttpPath(std::string_view path);
131
132 /**
133 * Evaluates to true if the path is not empty.
134 */
135 explicit operator bool() const { return !empty(); }
136
137 /**
138 * Returns true if the path has no elements.
139 */
140 bool empty() const { return m_pathEnds.empty(); }
141
142 /**
143 * Returns number of elements in the path.
144 */
145 size_t size() const { return m_pathEnds.size(); }
146
147 /**
148 * Returns true if the path exactly matches the provided match list.
149 *
150 * @param match match list
151 * @return True if path equals match list
152 */
153 bool equals(std::initializer_list<std::string_view> match) const {
154 return equals(0, {match.begin(), match.end()});
155 }
156 bool equals(std::span<const std::string_view> match) const {
157 return equals(0, match);
158 }
159 bool equals(std::string_view match) const { return equals(0, {match}); }
160
161 /**
162 * Returns true if the elements of the path starting at the "start" element
163 * match the provided match list, and there are no additional elements.
164 *
165 * @param start element to start matching at
166 * @param match match list
167 * @return True if match
168 */
169 bool equals(size_t start,
170 std::initializer_list<std::string_view> match) const {
171 return equals(start, {match.begin(), match.end()});
172 }
173 bool equals(size_t start, std::span<const std::string_view> match) const {
174 if (m_pathEnds.size() != (start + match.size())) {
175 return false;
176 }
177 return startswith(start, match);
178 }
179 bool equals(size_t start, std::string_view match) const {
180 return equals(start, {match});
181 }
182
183 /**
184 * Returns true if the first elements of the path match the provided match
185 * list. The path may have additional elements.
186 *
187 * @param match match list
188 * @return True if path starts with match list
189 */
190 bool startswith(std::initializer_list<std::string_view> match) const {
191 return startswith(0, {match.begin(), match.end()});
192 }
193 bool startswith(std::span<const std::string_view> match) const {
194 return startswith(0, match);
195 }
196 bool startswith(std::string_view match) const {
197 return startswith(0, {match});
198 }
199
200 /**
201 * Returns true if the elements of the path starting at the "start" element
202 * match the provided match list. The path may have additional elements.
203 *
204 * @param start element to start matching at
205 * @param match match list
206 * @return True if path starting at the start element matches the match list
207 */
208 bool startswith(size_t start,
209 std::initializer_list<std::string_view> match) const {
210 return startswith(start, {match.begin(), match.end()});
211 }
212
213 bool startswith(size_t start, std::span<const std::string_view> match) const;
214
215 bool startswith(size_t start, std::string_view match) const {
216 return startswith(start, {match});
217 }
218
219 /**
220 * Gets a single element of the path.
221 */
222 std::string_view operator[](size_t n) const;
223
224 /**
225 * Returns a path reference with the first N elements of the path removed.
226 */
227 HttpPathRef drop_front(size_t n) const;
228
229 private:
231 wpi::util::SmallVector<size_t, 16> m_pathEnds;
232};
233
234/**
235 * Proxy reference object for a portion of a HttpPath.
236 */
238 public:
239 HttpPathRef() = default;
240 /*implicit*/ HttpPathRef(const HttpPath& path, // NOLINT
241 size_t start = 0)
242 : m_path(&path), m_start(start) {}
243
244 explicit operator bool() const { return !empty(); }
245 bool empty() const { return m_path && m_path->size() == m_start; }
246 size_t size() const { return m_path ? m_path->size() - m_start : 0; }
247
248 bool equals(std::initializer_list<std::string_view> match) const {
249 return equals(0, {match.begin(), match.end()});
250 }
251 bool equals(std::span<const std::string_view> match) const {
252 return equals(0, match);
253 }
254 bool equals(std::string_view match) const { return equals(0, {match}); }
255
256 bool equals(size_t start,
257 std::initializer_list<std::string_view> match) const {
258 return equals(start, {match.begin(), match.end()});
259 }
260 bool equals(size_t start, std::span<const std::string_view> match) const {
261 return m_path ? m_path->equals(m_start + start, match) : false;
262 }
263 bool equals(size_t start, std::string_view match) const {
264 return equals(start, {match});
265 }
266
267 bool startswith(std::initializer_list<std::string_view> match) const {
268 return startswith(0, {match.begin(), match.end()});
269 }
270 bool startswith(std::span<const std::string_view> match) const {
271 return startswith(0, match);
272 }
273 bool startswith(std::string_view match) const {
274 return startswith(0, {match});
275 }
276
277 bool startswith(size_t start,
278 std::initializer_list<std::string_view> match) const {
279 return startswith(start, {match.begin(), match.end()});
280 }
281 bool startswith(size_t start, std::span<const std::string_view> match) const {
282 return m_path ? m_path->startswith(m_start + start, match) : false;
283 }
284 bool startswith(size_t start, std::string_view match) const {
285 return startswith(start, {match});
286 }
287
288 std::string_view operator[](size_t n) const {
289 return m_path ? m_path->operator[](m_start + n) : std::string_view{};
290 }
291 HttpPathRef drop_front(size_t n) const {
292 return m_path ? m_path->drop_front(m_start + n) : HttpPathRef{};
293 }
294
295 private:
296 const HttpPath* m_path = nullptr;
297 size_t m_start = 0;
298};
299
301 public:
302 HttpLocation() = default;
303 HttpLocation(std::string_view url_, bool* error, std::string* errorMsg);
304
305 std::string url; // retain copy
306 std::string user; // unescaped
307 std::string password; // unescaped
308 std::string host;
309 int port;
310 std::string path; // escaped, not including leading '/'
311 std::vector<std::pair<std::string, std::string>> params; // unescaped
312 std::string fragment;
313};
314
316 public:
317 HttpRequest() = default;
318
319 explicit HttpRequest(const HttpLocation& loc)
320 : host{loc.host}, port{loc.port} {
321 SetPath(loc.path, loc.params);
322 SetAuth(loc);
323 }
324
325 template <typename T>
326 HttpRequest(const HttpLocation& loc, const T& extraParams)
327 : host{loc.host}, port{loc.port} {
328 wpi::util::SmallVector<std::pair<std::string_view, std::string_view>, 4>
329 params;
330 for (const auto& p : loc.params) {
331 params.emplace_back(std::pair{GetFirst(p), GetSecond(p)});
332 }
333 for (const auto& p : extraParams) {
334 params.emplace_back(std::pair{GetFirst(p), GetSecond(p)});
335 }
336 SetPath(loc.path, params);
337 SetAuth(loc);
338 }
339
340 HttpRequest(const HttpLocation& loc, std::string_view path_)
341 : host{loc.host}, port{loc.port}, path{path_} {
342 SetAuth(loc);
343 }
344
345 template <typename T>
346 HttpRequest(const HttpLocation& loc, std::string_view path_, const T& params)
347 : host{loc.host}, port{loc.port} {
348 SetPath(path_, params);
349 SetAuth(loc);
350 }
351
353 int port;
354 std::string auth;
356
357 private:
358 void SetAuth(const HttpLocation& loc);
359
360 template <typename T>
361 void SetPath(std::string_view path_, const T& params) {
362 // Build location including query string
363 wpi::util::raw_svector_ostream pathOs{path};
364 pathOs << path_;
365 bool first = true;
366 for (const auto& param : params) {
367 if (first) {
368 pathOs << '?';
369 first = false;
370 } else {
371 pathOs << '&';
372 }
374 pathOs << EscapeURI(GetFirst(param), escapeBuf, false);
375 if (!GetSecond(param).empty()) {
376 pathOs << '=' << EscapeURI(GetSecond(param), escapeBuf, false);
377 }
378 }
379 }
380
381 template <typename T>
382 static std::string_view GetFirst(const T& elem) {
383 return elem.first;
384 }
385 template <typename T>
386 static std::string_view GetFirst(const std::pair<std::string, T>& elem) {
387 return elem.first;
388 }
389 template <typename T>
390 static std::string_view GetSecond(const T& elem) {
391 return elem.second;
392 }
393};
394
396 public:
397 HttpConnection(std::unique_ptr<wpi::net::NetworkStream> stream_, int timeout)
398 : stream{std::move(stream_)}, is{*stream, timeout}, os{*stream, true} {}
399
400 bool Handshake(const HttpRequest& request, std::string* warnMsg);
401
402 std::unique_ptr<wpi::net::NetworkStream> stream;
405
406 // Valid after Handshake() is successful
409
410 explicit operator bool() const { return stream && !is.has_error(); }
411};
412
414 public:
415 explicit HttpMultipartScanner(std::string_view boundary,
416 bool saveSkipped = false) {
417 Reset(saveSkipped);
418 SetBoundary(boundary);
419 }
420
421 // Change the boundary. This is only safe to do when IsDone() is true (or
422 // immediately after construction).
423 void SetBoundary(std::string_view boundary);
424
425 // Reset the scanner. This allows reuse of internal buffers.
426 void Reset(bool saveSkipped = false);
427
428 // Execute the scanner. Will automatically call Reset() on entry if IsDone()
429 // is true.
430 // @param in input data
431 // @return the input not consumed; empty if all input consumed
432 std::string_view Execute(std::string_view in);
433
434 // Returns true when the boundary has been found.
435 bool IsDone() const { return m_state == kDone; }
436
437 // Get the skipped data. Will be empty if saveSkipped was false.
438 std::string_view GetSkipped() const {
439 return m_saveSkipped ? std::string_view{m_buf} : std::string_view{};
440 }
441
442 private:
443 wpi::util::SmallString<64> m_boundaryWith, m_boundaryWithout;
444
445 // Internal state
446 enum State { kBoundary, kPadding, kDone };
447 State m_state;
448 size_t m_posWith, m_posWithout;
449 enum Dashes { kUnknown, kWith, kWithout };
450 Dashes m_dashes;
451
452 // Buffer
453 bool m_saveSkipped;
454 std::string m_buf;
455};
456
457inline HttpPathRef HttpPath::drop_front(size_t n) const {
458 return HttpPathRef(*this, n);
459}
460
461} // namespace wpi::net
This file defines the SmallString class.
@ name
Definition base.h:690
HttpConnection(std::unique_ptr< wpi::net::NetworkStream > stream_, int timeout)
Definition HttpUtil.hpp:397
wpi::net::raw_socket_istream is
Definition HttpUtil.hpp:403
std::unique_ptr< wpi::net::NetworkStream > stream
Definition HttpUtil.hpp:402
bool Handshake(const HttpRequest &request, std::string *warnMsg)
wpi::util::SmallString< 64 > contentLength
Definition HttpUtil.hpp:408
wpi::net::raw_socket_ostream os
Definition HttpUtil.hpp:404
wpi::util::SmallString< 64 > contentType
Definition HttpUtil.hpp:407
Definition HttpUtil.hpp:300
std::vector< std::pair< std::string, std::string > > params
Definition HttpUtil.hpp:311
HttpLocation(std::string_view url_, bool *error, std::string *errorMsg)
std::string fragment
Definition HttpUtil.hpp:312
std::string url
Definition HttpUtil.hpp:305
int port
Definition HttpUtil.hpp:309
std::string host
Definition HttpUtil.hpp:308
std::string path
Definition HttpUtil.hpp:310
std::string password
Definition HttpUtil.hpp:307
std::string user
Definition HttpUtil.hpp:306
std::string_view Execute(std::string_view in)
std::string_view GetSkipped() const
Definition HttpUtil.hpp:438
void Reset(bool saveSkipped=false)
bool IsDone() const
Definition HttpUtil.hpp:435
HttpMultipartScanner(std::string_view boundary, bool saveSkipped=false)
Definition HttpUtil.hpp:415
void SetBoundary(std::string_view boundary)
Class for HTTP path matching.
Definition HttpUtil.hpp:119
bool equals(std::initializer_list< std::string_view > match) const
Returns true if the path exactly matches the provided match list.
Definition HttpUtil.hpp:153
bool equals(std::string_view match) const
Definition HttpUtil.hpp:159
HttpPath()=default
Constructs an empty HTTP path.
bool equals(std::span< const std::string_view > match) const
Definition HttpUtil.hpp:156
bool startswith(size_t start, std::span< const std::string_view > match) const
bool startswith(size_t start, std::string_view match) const
Definition HttpUtil.hpp:215
HttpPath(std::string_view path)
Constructs a HTTP path from an escaped path string.
bool equals(size_t start, std::span< const std::string_view > match) const
Definition HttpUtil.hpp:173
size_t size() const
Returns number of elements in the path.
Definition HttpUtil.hpp:145
bool startswith(std::string_view match) const
Definition HttpUtil.hpp:196
std::string_view operator[](size_t n) const
Gets a single element of the path.
bool startswith(std::span< const std::string_view > match) const
Definition HttpUtil.hpp:193
bool startswith(size_t start, std::initializer_list< std::string_view > match) const
Returns true if the elements of the path starting at the "start" element match the provided match lis...
Definition HttpUtil.hpp:208
bool startswith(std::initializer_list< std::string_view > match) const
Returns true if the first elements of the path match the provided match list.
Definition HttpUtil.hpp:190
bool equals(size_t start, std::initializer_list< std::string_view > match) const
Returns true if the elements of the path starting at the "start" element match the provided match lis...
Definition HttpUtil.hpp:169
bool empty() const
Returns true if the path has no elements.
Definition HttpUtil.hpp:140
bool equals(size_t start, std::string_view match) const
Definition HttpUtil.hpp:179
HttpPathRef drop_front(size_t n) const
Returns a path reference with the first N elements of the path removed.
Definition HttpUtil.hpp:457
Proxy reference object for a portion of a HttpPath.
Definition HttpUtil.hpp:237
bool equals(std::string_view match) const
Definition HttpUtil.hpp:254
bool startswith(std::string_view match) const
Definition HttpUtil.hpp:273
bool startswith(std::initializer_list< std::string_view > match) const
Definition HttpUtil.hpp:267
bool empty() const
Definition HttpUtil.hpp:245
std::string_view operator[](size_t n) const
Definition HttpUtil.hpp:288
size_t size() const
Definition HttpUtil.hpp:246
bool equals(std::span< const std::string_view > match) const
Definition HttpUtil.hpp:251
bool startswith(size_t start, std::initializer_list< std::string_view > match) const
Definition HttpUtil.hpp:277
bool equals(std::initializer_list< std::string_view > match) const
Definition HttpUtil.hpp:248
bool startswith(std::span< const std::string_view > match) const
Definition HttpUtil.hpp:270
bool startswith(size_t start, std::string_view match) const
Definition HttpUtil.hpp:284
bool equals(size_t start, std::span< const std::string_view > match) const
Definition HttpUtil.hpp:260
HttpPathRef(const HttpPath &path, size_t start=0)
Definition HttpUtil.hpp:240
bool startswith(size_t start, std::span< const std::string_view > match) const
Definition HttpUtil.hpp:281
bool equals(size_t start, std::string_view match) const
Definition HttpUtil.hpp:263
HttpPathRef drop_front(size_t n) const
Definition HttpUtil.hpp:291
bool equals(size_t start, std::initializer_list< std::string_view > match) const
Definition HttpUtil.hpp:256
HttpQueryMap(std::string_view query)
Constructs from an escaped query string.
std::optional< std::string_view > Get(std::string_view name, wpi::util::SmallVectorImpl< char > &buf) const
Gets an element of the query string.
HttpQueryMap()=default
Constructs an empty map (with no entries).
Definition HttpUtil.hpp:315
wpi::util::SmallString< 128 > path
Definition HttpUtil.hpp:355
HttpRequest(const HttpLocation &loc, std::string_view path_, const T &params)
Definition HttpUtil.hpp:346
HttpRequest(const HttpLocation &loc)
Definition HttpUtil.hpp:319
wpi::util::SmallString< 128 > host
Definition HttpUtil.hpp:352
HttpRequest(const HttpLocation &loc, std::string_view path_)
Definition HttpUtil.hpp:340
int port
Definition HttpUtil.hpp:353
HttpRequest(const HttpLocation &loc, const T &extraParams)
Definition HttpUtil.hpp:326
std::string auth
Definition HttpUtil.hpp:354
Definition raw_socket_istream.hpp:13
Definition raw_socket_ostream.hpp:13
SmallString - A SmallString is just a SmallVector with methods and accessors that make it work better...
Definition SmallString.hpp:27
Definition BooleanTopic.hpp:24
StringMap is a sorted associative container that contains key-value pairs with unique string keys.
Definition StringMap.hpp:26
Definition raw_istream.hpp:21
T * p
Definition format.h:758
Definition StringMap.hpp:773
Definition raw_socket_ostream.hpp:9
bool FindMultipartBoundary(wpi::util::raw_istream &is, std::string_view boundary, std::string *saveBuf)
std::string_view UnescapeURI(std::string_view str, wpi::util::SmallVectorImpl< char > &buf, bool *error)
bool ParseHttpHeaders(wpi::util::raw_istream &is, wpi::util::SmallVectorImpl< char > *contentType, wpi::util::SmallVectorImpl< char > *contentLength)
std::string_view EscapeURI(std::string_view str, wpi::util::SmallVectorImpl< char > &buf, bool spacePlus=true)
std::string_view EscapeHTML(std::string_view str, wpi::util::SmallVectorImpl< char > &buf)