Repository navigation
Expand file tree
/
Copy pathforms_urlencoded.hpp
More file actions
157 lines (130 loc) · 6.17 KB
/
Copy pathforms_urlencoded.hpp
File metadata and controls
157 lines (130 loc) · 6.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/*
This file is part of libhttpserver
Copyright (C) 2011-2026 Sebastiano Merlino
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the file
LICENSE in the distribution; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
// TASK-116: the strict incremental application/x-www-form-urlencoded
// body decoder (PRD-V3N-REQ-021/022, v2 within-cap parity per
// PRD-V3N-REQ-038, DR-V3-001). NOT part of the installed surface.
//
// Wire semantics (v2's documented contract, reproduced within cap):
// '&' separates pairs; the FIRST '=' in a pair separates name from
// value (later '='s are value data); a token with no '=' is a name
// with an empty value; empty tokens (a&&b, trailing '&') are skipped;
// '+' is 0x20; a complete case-insensitive %HH is the decoded byte;
// every other raw byte -- NUL and high-bit included -- passes through
// verbatim (values are length-carrying strings, so a decoded %00 is
// storable). Repeated names append in arrival order; first-value
// lookup is the caller's rule. Decoded bytes never re-enter the pair
// state machine: a %26 is data, never a separator.
//
// The TASK-116 deltas over v2 (migration-noted): an incomplete or
// non-hex %HH -- including one truncated at end of body and one split
// across feed boundaries -- is a typed invalid_argument rejection,
// where v2 passed the bytes through literally; the byte and field
// budgets are typed limit_exceeded rejections, where v2 truncated
// silently.
//
// Bounded admission: max_total_bytes counts RAW body bytes consumed
// and max_fields counts decoded pairs emitted, so decoded storage is
// bounded by construction (decoded bytes <= raw bytes <= cap, plus
// the field count cap). Exactly-at-cap succeeds. A field materializes
// only when its token flushes (at a separator or finish()), so the
// stored prefix provably stops at the failing feed: the pending token
// and every later pair never materialize. Rejections are sticky: a
// failed decoder refuses further feeds and finish() with the same
// typed outcome.
#if !defined(HTTPSERVER_COMPILATION)
#error "httpserver/detail/forms_urlencoded.hpp is internal; only include it when compiling libhttpserver (HTTPSERVER_COMPILATION must be defined)."
#endif
#ifndef SRC_HTTPSERVER_DETAIL_FORMS_URLENCODED_HPP_
#define SRC_HTTPSERVER_DETAIL_FORMS_URLENCODED_HPP_
#include <cstddef>
#include <cstdint>
#include <span>
#include <string>
#include <utility>
#include <vector>
#include <httpserver/http/outcome.hpp>
namespace httpserver {
namespace detail {
// Incremental decoder for one urlencoded request body. Not
// thread-safe: the caller feeds it on the reader's thread. State
// survives arbitrary feed boundaries (the escape accumulator included,
// the classic urlencoded-parser bug), so streaming feeds and one-shot
// feeds decode identically.
class urlencoded_decoder {
public:
urlencoded_decoder(std::uint64_t max_total_bytes,
std::uint64_t max_fields) noexcept
: max_total_bytes_(max_total_bytes), max_fields_(max_fields) { }
// Decodes a prefix of the raw body. Bytes before a failing byte
// are consumed into the token state (the stored prefix stops at
// the failure); after a typed failure every later feed returns the
// same outcome and consumes nothing.
http::outcome feed(std::span<const std::byte> wire);
// Ends the body: flushes a pending token as a field (budget
// checked) and rejects an escape still in progress (truncated at
// end of body). Idempotent after a failure.
http::outcome finish();
// The decoded fields in arrival order (repeated names appended);
// the pending token of an unfinished or failed decode is not
// included. Moves the storage out.
std::vector<std::pair<std::string, std::string>> take_fields() {
return std::move(fields_);
}
private:
// One wire byte into the token state machine. Order matters: an
// in-progress escape sees every byte as its next digit (a
// separator cutting an escape short is malformed), and '&' itself
// is never a token byte.
bool feed_byte(unsigned char c);
// Strict %HH: both digits must be case-insensitive hex; anything
// else is the typed malformed rejection.
bool feed_escape(unsigned char c);
// A separator: a token that consumed at least one wire byte is a
// field; an empty token is skipped and never consumes field
// budget.
bool flush_token();
// Emits the pending token as a field. The budget check precedes
// the append, so the over-cap pair never materializes.
bool commit_field();
void append(char c);
void reset_token();
static bool is_hex(unsigned char c) noexcept {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')
|| (c >= 'A' && c <= 'F');
}
static unsigned char hex_value(unsigned char c) noexcept;
// Records the typed, sticky rejection and freezes the stored
// prefix. Returns false so byte handlers can `return fail(...)`.
bool fail(http::outcome_code code, const char* what);
std::uint64_t max_total_bytes_;
std::uint64_t max_fields_;
std::vector<std::pair<std::string, std::string>> fields_;
std::string name_;
std::string value_;
http::outcome failure_;
std::uint64_t bytes_seen_ = 0;
unsigned char escape_value_ = 0;
unsigned escape_digits_ = 0;
bool in_value_ = false;
bool saw_token_byte_ = false;
bool escaping_ = false;
bool failed_ = false;
};
} // namespace detail
} // namespace httpserver
#endif // SRC_HTTPSERVER_DETAIL_FORMS_URLENCODED_HPP_