OpenVPN 3 Core Library
Loading...
Searching...
No Matches
httpcommon.hpp
Go to the documentation of this file.
1// OpenVPN -- An application to securely tunnel IP networks
2// over a single port, with support for SSL/TLS-based
3// session authentication and key exchange,
4// packet encryption, packet authentication, and
5// packet compression.
6//
7// Copyright (C) 2012- OpenVPN Inc.
8//
9// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
10//
11
12#pragma once
13
14// HTTP code common to both clients and servers
15
16#include <string>
17#include <memory>
18#include <utility>
19
32
33namespace openvpn::WS {
34OPENVPN_EXCEPTION(http_exception);
35
36template <typename PARENT,
37 typename CONFIG,
38 typename STATUS,
39 typename REQUEST_REPLY,
40 typename CONTENT_INFO,
41 typename CONTENT_LENGTH_TYPE, // must be signed
42 typename REFCOUNT_BASE>
43class HTTPBase : public REFCOUNT_BASE
44{
46
55
56 public:
57 void rr_reset()
58 {
59 rr_obj.reset();
60 rr_status = REQUEST_REPLY::Parser::pending;
61 rr_parser.reset();
66 rr_chunked.reset();
67 max_content_bytes = config->max_content_bytes;
69 }
70
71 void reset()
72 {
73 if (halt)
74 {
75 halt = false;
76 ready = true;
77 }
78 }
79
80 bool is_ready() const
81 {
82 return !halt && ready;
83 }
84
85 bool is_websocket() const
86 {
87 return websocket;
88 }
89
90 // If true, indicates that data can be transmitted
91 // now with immediate dispatch.
92 bool is_deferred() const
93 {
94 return out_state == S_DEFERRED;
95 }
96
97 bool http_in_started() const
98 {
99 return rr_content_bytes > CONTENT_LENGTH_TYPE(0);
100 }
101
102 bool http_out_started() const
103 {
104 return out_state != S_PRE;
105 }
106
107 const typename REQUEST_REPLY::State &request_reply() const
108 {
109 return rr_obj;
110 }
111
113 {
114 return rr_obj.headers;
115 }
116
118 {
119 return rr_content_length;
120 }
121
122 std::string ssl_handshake_details() const
123 {
124 if (ssl_sess)
126 else
127 return "";
128 }
129
131 {
132 if (ssl_sess)
134 else
135 return false;
136 }
137
139 {
140 if (ssl_sess)
142 }
143
144 const CONFIG &http_config() const
145 {
146 return *config;
147 }
148
149 void set_async_out(const bool async_out_arg)
150 {
151 async_out = async_out_arg;
152 }
153
155 {
156 if (halt)
157 return;
158 if (out_state == S_DEFERRED && (!outbuf || outbuf->empty()))
159 {
161 outbuf = std::move(buf);
162 new_outbuf();
164 }
165 else
166 OPENVPN_THROW(http_exception, "http_content_out_finish: no deferred state=" << http_out_state_string(out_state) << " outbuf_size=" + (std::to_string(outbuf ? int(outbuf->size()) : -1)) << " halt=" << halt << " ready=" << ready << " async_out=" << async_out << " websock=" << websocket);
167 }
168
169 void reduce_max_content_bytes(const CONTENT_LENGTH_TYPE new_max_content_bytes)
170 {
171 if (new_max_content_bytes && new_max_content_bytes < max_content_bytes)
172 max_content_bytes = new_max_content_bytes;
173 }
174
175 protected:
176 HTTPBase(const typename CONFIG::Ptr &config_arg)
177 : config(config_arg),
178 frame(config_arg->frame),
179 stats(config_arg->stats)
180 {
181 static_assert(CONTENT_LENGTH_TYPE(-1) < CONTENT_LENGTH_TYPE(0), "CONTENT_LENGTH_TYPE must be signed");
182 rr_reset();
183 }
184
186 {
188 }
189
190 // Transmit outgoing HTTP, either to SSL object (HTTPS) or TCP socket (HTTP)
191 void http_out()
192 {
193 if (halt)
194 return;
195 if (out_state == S_PRE)
196 {
197 if (ssl_sess)
199 return;
200 }
201 if (out_state == S_OUT && (!outbuf || outbuf->empty()))
202 {
203 if (async_out)
204 {
206 parent().base_http_content_out_needed();
207 return;
208 }
209 else
210 {
211 outbuf = parent().base_http_content_out();
212 new_outbuf();
213 }
214 }
216 }
217
219 {
220 if (ssl_sess)
221 {
222 // HTTPS
223 auto buf = BufferAllocatedRc::Create();
224 buf->swap(b); // take ownership
226 ssl_up_stack();
228
229 // In some cases, such as immediately after handshake,
230 // a write becomes possible after a read has completed.
231 http_out();
232 }
233 else
234 {
235 // HTTP
236 http_in(b);
237 }
238 }
239
240 // Callback methods in parent:
241 // BufferPtr base_http_content_out();
242 // void base_http_content_out_needed();
243 // void base_http_out_eof();
244 // bool base_http_headers_received();
245 // void base_http_content_in(BufferAllocatedRc& buf);
246 // bool base_link_send(BufferAllocatedRc& buf);
247 // bool base_send_queue_empty();
248 // void base_http_done_handler(BufferAllocatedRc& residual)
249 // void base_error_handler(const int errcode, const std::string& err);
250
251 // protected member vars
252
253 bool halt = false;
254 bool ready = true;
255 bool async_out = false;
256 bool websocket = false;
257
258 typename CONFIG::Ptr config;
259 CONTENT_INFO content_info;
261
263
266
267 private:
268 PARENT &parent()
269 {
270 return *static_cast<PARENT *>(this);
271 }
272
274 {
275 if (!outbuf || !outbuf->defined())
277 if (content_info.length == CONTENT_INFO::CHUNKED)
279 }
280
282 {
283 if (outbuf)
284 {
285 const size_t size = std::min(outbuf->size(), http_buf_size());
286 if (size)
287 {
288 if (ssl_sess)
289 {
290 // HTTPS: send outgoing cleartext HTTP data from request/reply to SSL object
291 ssize_t actual = 0;
292 try
293 {
294 actual = ssl_sess->write_cleartext_unbuffered(outbuf->data(), size);
295 }
296 catch (...)
297 {
299 throw;
300 }
301 if (actual >= 0)
302 {
303#if defined(OPENVPN_DEBUG_HTTP)
304 BufferAllocated tmp(outbuf->c_data(), actual, 0);
305 OPENVPN_LOG("OUT: " << buf_to_string(tmp));
306#endif
307 outbuf->advance(actual);
308 }
309 else if (actual == SSLConst::SHOULD_RETRY)
310 ;
311 else
312 throw http_exception("unknown write status from SSL layer");
314 }
315 else
316 {
317 // HTTP: send outgoing cleartext HTTP data from request/reply to TCP socket
318 BufferAllocated buf;
320 buf.write(outbuf->data(), size);
321#if defined(OPENVPN_DEBUG_HTTP)
322 OPENVPN_LOG("OUT: " << buf_to_string(buf));
323#endif
324 if (parent().base_link_send(buf))
325 outbuf->advance(size);
326 }
327 }
328 }
329 if (out_state == S_EOF && parent().base_send_queue_empty())
330 {
332 outbuf.reset();
333 parent().base_http_out_eof();
334 }
335 }
336
337 void chunked_content_in(BufferAllocated &buf) // called by ChunkedHelper
338 {
340 }
341
343 {
344 if (halt)
345 return;
346 if (buf.defined())
347 {
348 rr_content_bytes += buf.size();
349 if (!websocket)
350 rr_limit_bytes += buf.size() + config->msg_overhead_bytes;
352 {
353 parent().base_error_handler(STATUS::E_CONTENT_SIZE, "HTTP content too large");
354 return;
355 }
356 parent().base_http_content_in(buf);
357 }
358 }
359
360 // Receive incoming HTTP
362 {
363 if (halt || ready || buf.empty()) // if ready, indicates unsolicited input
364 return;
365
366#if defined(OPENVPN_DEBUG_HTTP)
367 OPENVPN_LOG("IN: " << buf_to_string(buf));
368#endif
369
370 if (rr_status == REQUEST_REPLY::Parser::pending)
371 {
372 // processing HTTP request/reply and headers
373 for (size_t i = 0; i < buf.size(); ++i)
374 {
375 rr_status = rr_parser.consume(rr_obj, (char)buf[i]);
376 if (rr_status == REQUEST_REPLY::Parser::pending)
377 {
379 if ((rr_header_bytes & 0x3F) == 0)
380 {
381 // only check header maximums once every 64 bytes
382 if ((config->max_header_bytes && rr_header_bytes > config->max_header_bytes)
383 || (config->max_headers && rr_obj.headers.size() > config->max_headers))
384 {
385 parent().base_error_handler(STATUS::E_HEADER_SIZE, "HTTP headers too large");
386 return;
387 }
388 }
389 }
390 else
391 {
392 // finished processing HTTP request/reply and headers
393 buf.advance(i + 1);
394 if (rr_status == REQUEST_REPLY::Parser::success)
395 {
396 if (!websocket)
397 {
399 if (rr_content_length == CONTENT_INFO::CHUNKED)
400 rr_chunked.reset(new ChunkedHelper());
401 }
402 if (!parent().base_http_headers_received())
403 {
404 // Parent wants to handle content itself,
405 // pass post-header residual data.
406 // Currently, only pgproxy uses this.
407 parent().base_http_done_handler(buf, true);
408 return;
409 }
410 break;
411 }
412 else
413 {
414 parent().base_error_handler(STATUS::E_HTTP, "HTTP headers parse error");
415 return;
416 }
417 }
418 }
419 }
420
421 if (rr_status == REQUEST_REPLY::Parser::success)
422 {
423 // processing HTTP content
424 bool done = false;
425 BufferAllocated residual;
426
427 if (websocket)
428 {
430 }
431 else if (rr_content_length >= 0)
432 {
433 const size_t needed = static_cast<size_t>(std::max(rr_content_length - rr_content_bytes, CONTENT_LENGTH_TYPE(0)));
434 if (needed <= buf.size())
435 {
436 done = true;
437 if (needed < buf.size())
438 {
439 // residual data exists
440 residual.swap(buf);
441 buf = (*frame)[Frame::READ_HTTP].copy_by_value(residual.read_alloc(needed), needed);
442 }
443 }
445 }
446 else if (rr_chunked)
447 {
448 done = rr_chunked->receive(*this, buf); // will callback to chunked_content_in
449 }
450 if (done)
451 parent().base_http_done_handler(residual, false);
452 }
453 }
454
455 // read outgoing ciphertext data from SSL object and xmit to TCP socket
457 {
458 while (!halt && ssl_sess->read_ciphertext_ready())
459 {
461 parent().base_link_send(*buf);
462 }
463 }
464
465 // read incoming cleartext data from SSL object and pass to HTTP receiver
467 {
468 BufferAllocated buf;
469 while (!halt && ssl_sess->read_cleartext_ready())
470 {
471 const Frame::Context &fc = (*frame)[Frame::READ_SSL_CLEARTEXT];
472 fc.prepare(buf);
473 ssize_t size = 0;
474 try
475 {
476 size = ssl_sess->read_cleartext(buf.data(), fc.payload());
477 }
478 catch (...)
479 {
481 throw;
482 }
483 if (size > 0)
484 {
485 buf.set_size(size);
486 http_in(buf);
487 }
488 else if (size == SSLConst::SHOULD_RETRY || !size)
489 break;
490 else if (size == SSLConst::PEER_CLOSE_NOTIFY)
491 parent().base_error_handler(STATUS::E_EOF_SSL, "SSL PEER_CLOSE_NOTIFY");
492 else
493 throw http_exception("unknown read status from SSL layer");
494 }
495 }
496
497 size_t http_buf_size() const
498 {
499 return (*frame)[Frame::WRITE_HTTP].payload();
500 }
501
502 static CONTENT_LENGTH_TYPE get_content_length(const HTTP::HeaderList &headers)
503 {
504 const std::string transfer_encoding = headers.get_value_trim("transfer-encoding");
505 if (!string::strcasecmp(transfer_encoding, "chunked"))
506 {
507 return CONTENT_INFO::CHUNKED;
508 }
509 else
510 {
511 const std::string content_length_str = headers.get_value_trim("content-length");
512 if (content_length_str.empty())
513 return 0;
514 const CONTENT_LENGTH_TYPE content_length = parse_number_throw<CONTENT_LENGTH_TYPE>(content_length_str, "content-length");
515 if (content_length < 0)
516 throw number_parse_exception("content-length is < 0");
517 return content_length;
518 }
519 }
520
521 static std::string http_out_state_string(const HTTPOutState hos)
522 {
523 switch (hos)
524 {
525 case S_PRE:
526 return "S_PRE";
527 case S_OUT:
528 return "S_OUT";
529 case S_DEFERRED:
530 return "S_DEFERRED";
531 case S_EOF:
532 return "S_EOF";
533 case S_DONE:
534 return "S_DONE";
535 default:
536 return "S_?";
537 }
538 }
539
540 // private member vars
541
542 typename REQUEST_REPLY::Parser::status rr_status;
543 typename REQUEST_REPLY::Parser rr_parser;
544 typename REQUEST_REPLY::State rr_obj;
545
546 unsigned int rr_header_bytes;
547
548 CONTENT_LENGTH_TYPE rr_content_bytes;
549 CONTENT_LENGTH_TYPE rr_content_length; // Content-Length in header
550 CONTENT_LENGTH_TYPE rr_limit_bytes;
551 std::unique_ptr<ChunkedHelper> rr_chunked;
552
553 CONTENT_LENGTH_TYPE max_content_bytes;
554
556};
557
558} // namespace openvpn::WS
void swap(BufferAllocatedType< T_ > &other)
Swaps the contents of this BufferAllocatedType object with another BufferAllocatedType object.
Definition buffer.hpp:1765
bool defined() const
Returns true if the buffer is not empty.
Definition buffer.hpp:1207
size_t size() const
Returns the size of the buffer in T objects.
Definition buffer.hpp:1225
T * data()
Get a mutable pointer to the start of the array.
Definition buffer.hpp:1433
void advance(const size_t delta)
Advances the buffer by the specified delta.
Definition buffer.hpp:1260
bool empty() const
Returns true if the buffer is empty.
Definition buffer.hpp:1219
void write(const T *data, const size_t size)
Write data to the buffer.
Definition buffer.hpp:1546
auto * read_alloc(const size_t size)
Allocate memory and read data from the buffer into the allocated memory.
Definition buffer.hpp:1326
void set_size(const size_t size)
After an external method, operating on the array as a mutable unsigned char buffer,...
Definition buffer.hpp:1367
size_t payload() const
Definition frame.hpp:97
size_t prepare(Buffer &buf) const
Definition frame.hpp:116
size_t prepare(const unsigned int context, Buffer &buf) const
Definition frame.hpp:266
@ READ_SSL_CLEARTEXT
Definition frame.hpp:42
void reset() noexcept
Points this RCPtr<T> to nullptr safely.
Definition rc.hpp:290
static Ptr Create(ArgsT &&...args)
Creates a new instance of RcEnable with the given arguments.
Definition make_rc.hpp:43
virtual void write_ciphertext(const BufferPtr &buf)=0
virtual void mark_no_cache()=0
virtual bool read_ciphertext_ready() const =0
virtual bool read_cleartext_ready() const =0
virtual std::string ssl_handshake_details() const =0
virtual bool did_full_handshake()=0
virtual BufferPtr read_ciphertext()=0
virtual ssize_t write_cleartext_unbuffered(const void *data, const size_t size)=0
virtual ssize_t read_cleartext(void *data, const size_t capacity)=0
virtual void error(const size_t type, const std::string *text=nullptr)
static BufferPtr transmit(BufferPtr buf)
Definition chunked.hpp:139
bool is_ready() const
bool http_in_started() const
bool http_out_started() const
const HTTP::HeaderList & headers() const
void chunked_content_in(BufferAllocated &buf)
void http_content_out_finish(BufferPtr buf)
CONTENT_LENGTH_TYPE rr_content_bytes
bool is_websocket() const
void tcp_in(BufferAllocated &b)
REQUEST_REPLY::Parser::status rr_status
bool is_deferred() const
void http_in(BufferAllocated &buf)
REQUEST_REPLY::Parser rr_parser
bool ssl_did_full_handshake() const
void reduce_max_content_bytes(const CONTENT_LENGTH_TYPE new_max_content_bytes)
CONTENT_LENGTH_TYPE rr_limit_bytes
HTTPBase(const typename CONFIG::Ptr &config_arg)
static CONTENT_LENGTH_TYPE get_content_length(const HTTP::HeaderList &headers)
void set_async_out(const bool async_out_arg)
CONTENT_LENGTH_TYPE rr_content_length
olong content_length() const
REQUEST_REPLY::State rr_obj
std::string ssl_handshake_details() const
std::unique_ptr< ChunkedHelper > rr_chunked
CONTENT_INFO content_info
const REQUEST_REPLY::State & request_reply() const
CONTENT_LENGTH_TYPE max_content_bytes
size_t http_buf_size() const
unsigned int rr_header_bytes
const CONFIG & http_config() const
static std::string http_out_state_string(const HTTPOutState hos)
void do_http_content_in(BufferAllocated &buf)
SessionStats::Ptr stats
#define OPENVPN_EXCEPTION(C)
#define OPENVPN_THROW(exc, stuff)
#define OPENVPN_LOG(args)
int strcasecmp(const char *s1, const char *s2)
Definition string.hpp:29
std::string buf_to_string(const Buffer &buf)
Definition bufstr.hpp:22
long olong
Definition olong.hpp:23
std::string get_value_trim(const std::string &key) const
Definition header.hpp:84