xref: /openbmc/bmcweb/http/http_connection.hpp (revision 7bbcae59)
1 #pragma once
2 #include "bmcweb_config.h"
3 
4 #include "authorization.hpp"
5 #include "http_response.hpp"
6 #include "http_utility.hpp"
7 #include "logging.hpp"
8 #include "timer_queue.hpp"
9 #include "utility.hpp"
10 
11 #include <boost/algorithm/string.hpp>
12 #include <boost/algorithm/string/predicate.hpp>
13 #include <boost/asio/io_context.hpp>
14 #include <boost/asio/ip/tcp.hpp>
15 #include <boost/asio/ssl/stream.hpp>
16 #include <boost/beast/core/flat_static_buffer.hpp>
17 #include <boost/beast/ssl/ssl_stream.hpp>
18 #include <boost/beast/websocket.hpp>
19 #include <json_html_serializer.hpp>
20 #include <security_headers.hpp>
21 #include <ssl_key_handler.hpp>
22 
23 #include <atomic>
24 #include <chrono>
25 #include <vector>
26 
27 namespace crow
28 {
29 
30 inline void prettyPrintJson(crow::Response& res)
31 {
32     json_html_util::dumpHtml(res.body(), res.jsonValue);
33 
34     res.addHeader("Content-Type", "text/html;charset=UTF-8");
35 }
36 
37 #ifdef BMCWEB_ENABLE_DEBUG
38 static std::atomic<int> connectionCount;
39 #endif
40 
41 // request body limit size set by the bmcwebHttpReqBodyLimitMb option
42 constexpr unsigned int httpReqBodyLimit =
43     1024 * 1024 * bmcwebHttpReqBodyLimitMb;
44 
45 constexpr uint64_t loggedOutPostBodyLimit = 4096;
46 
47 constexpr uint32_t httpHeaderLimit = 8192;
48 
49 // drop all connections after 1 minute, this time limit was chosen
50 // arbitrarily and can be adjusted later if needed
51 static constexpr const size_t loggedInAttempts =
52     (60 / timerQueueTimeoutSeconds);
53 
54 static constexpr const size_t loggedOutAttempts =
55     (15 / timerQueueTimeoutSeconds);
56 
57 template <typename Adaptor, typename Handler>
58 class Connection :
59     public std::enable_shared_from_this<Connection<Adaptor, Handler>>
60 {
61   public:
62     Connection(Handler* handlerIn,
63                std::function<std::string()>& getCachedDateStrF,
64                detail::TimerQueue& timerQueueIn, Adaptor adaptorIn) :
65         adaptor(std::move(adaptorIn)),
66         handler(handlerIn), getCachedDateStr(getCachedDateStrF),
67         timerQueue(timerQueueIn)
68     {
69         parser.emplace(std::piecewise_construct, std::make_tuple());
70         parser->body_limit(httpReqBodyLimit);
71         parser->header_limit(httpHeaderLimit);
72         req.emplace(parser->get());
73 
74 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
75         prepareMutualTls();
76 #endif // BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
77 
78 #ifdef BMCWEB_ENABLE_DEBUG
79         connectionCount++;
80         BMCWEB_LOG_DEBUG << this << " Connection open, total "
81                          << connectionCount;
82 #endif
83     }
84 
85     ~Connection()
86     {
87         res.setCompleteRequestHandler(nullptr);
88         cancelDeadlineTimer();
89 #ifdef BMCWEB_ENABLE_DEBUG
90         connectionCount--;
91         BMCWEB_LOG_DEBUG << this << " Connection closed, total "
92                          << connectionCount;
93 #endif
94     }
95 
96     void prepareMutualTls()
97     {
98         std::error_code error;
99         std::filesystem::path caPath(ensuressl::trustStorePath);
100         auto caAvailable = !std::filesystem::is_empty(caPath, error);
101         caAvailable = caAvailable && !error;
102         if (caAvailable && persistent_data::SessionStore::getInstance()
103                                .getAuthMethodsConfig()
104                                .tls)
105         {
106             adaptor.set_verify_mode(boost::asio::ssl::verify_peer);
107             std::string id = "bmcweb";
108             int ret = SSL_set_session_id_context(
109                 adaptor.native_handle(),
110                 reinterpret_cast<const unsigned char*>(id.c_str()),
111                 static_cast<unsigned int>(id.length()));
112             if (ret == 0)
113             {
114                 BMCWEB_LOG_ERROR << this << " failed to set SSL id";
115             }
116         }
117 
118         adaptor.set_verify_callback([this](
119                                         bool preverified,
120                                         boost::asio::ssl::verify_context& ctx) {
121             // do nothing if TLS is disabled
122             if (!persistent_data::SessionStore::getInstance()
123                      .getAuthMethodsConfig()
124                      .tls)
125             {
126                 BMCWEB_LOG_DEBUG << this << " TLS auth_config is disabled";
127                 return true;
128             }
129 
130             // We always return true to allow full auth flow
131             if (!preverified)
132             {
133                 BMCWEB_LOG_DEBUG << this << " TLS preverification failed.";
134                 return true;
135             }
136 
137             X509_STORE_CTX* cts = ctx.native_handle();
138             if (cts == nullptr)
139             {
140                 BMCWEB_LOG_DEBUG << this << " Cannot get native TLS handle.";
141                 return true;
142             }
143 
144             // Get certificate
145             X509* peerCert =
146                 X509_STORE_CTX_get_current_cert(ctx.native_handle());
147             if (peerCert == nullptr)
148             {
149                 BMCWEB_LOG_DEBUG << this
150                                  << " Cannot get current TLS certificate.";
151                 return true;
152             }
153 
154             // Check if certificate is OK
155             int error = X509_STORE_CTX_get_error(cts);
156             if (error != X509_V_OK)
157             {
158                 BMCWEB_LOG_INFO << this << " Last TLS error is: " << error;
159                 return true;
160             }
161             // Check that we have reached final certificate in chain
162             int32_t depth = X509_STORE_CTX_get_error_depth(cts);
163             if (depth != 0)
164 
165             {
166                 BMCWEB_LOG_DEBUG
167                     << this << " Certificate verification in progress (depth "
168                     << depth << "), waiting to reach final depth";
169                 return true;
170             }
171 
172             BMCWEB_LOG_DEBUG << this
173                              << " Certificate verification of final depth";
174 
175             // Verify KeyUsage
176             bool isKeyUsageDigitalSignature = false;
177             bool isKeyUsageKeyAgreement = false;
178 
179             ASN1_BIT_STRING* usage = static_cast<ASN1_BIT_STRING*>(
180                 X509_get_ext_d2i(peerCert, NID_key_usage, nullptr, nullptr));
181 
182             if (usage == nullptr)
183             {
184                 BMCWEB_LOG_DEBUG << this << " TLS usage is null";
185                 return true;
186             }
187 
188             for (int i = 0; i < usage->length; i++)
189             {
190                 if (KU_DIGITAL_SIGNATURE & usage->data[i])
191                 {
192                     isKeyUsageDigitalSignature = true;
193                 }
194                 if (KU_KEY_AGREEMENT & usage->data[i])
195                 {
196                     isKeyUsageKeyAgreement = true;
197                 }
198             }
199             ASN1_BIT_STRING_free(usage);
200 
201             if (!isKeyUsageDigitalSignature || !isKeyUsageKeyAgreement)
202             {
203                 BMCWEB_LOG_DEBUG << this
204                                  << " Certificate ExtendedKeyUsage does "
205                                     "not allow provided certificate to "
206                                     "be used for user authentication";
207                 return true;
208             }
209 
210             // Determine that ExtendedKeyUsage includes Client Auth
211 
212             stack_st_ASN1_OBJECT* extUsage =
213                 static_cast<stack_st_ASN1_OBJECT*>(X509_get_ext_d2i(
214                     peerCert, NID_ext_key_usage, nullptr, nullptr));
215 
216             if (extUsage == nullptr)
217             {
218                 BMCWEB_LOG_DEBUG << this << " TLS extUsage is null";
219                 return true;
220             }
221 
222             bool isExKeyUsageClientAuth = false;
223             for (int i = 0; i < sk_ASN1_OBJECT_num(extUsage); i++)
224             {
225                 if (NID_client_auth ==
226                     OBJ_obj2nid(sk_ASN1_OBJECT_value(extUsage, i)))
227                 {
228                     isExKeyUsageClientAuth = true;
229                     break;
230                 }
231             }
232             sk_ASN1_OBJECT_free(extUsage);
233 
234             // Certificate has to have proper key usages set
235             if (!isExKeyUsageClientAuth)
236             {
237                 BMCWEB_LOG_DEBUG << this
238                                  << " Certificate ExtendedKeyUsage does "
239                                     "not allow provided certificate to "
240                                     "be used for user authentication";
241                 return true;
242             }
243             std::string sslUser;
244             // Extract username contained in CommonName
245             sslUser.resize(256, '\0');
246 
247             int status = X509_NAME_get_text_by_NID(
248                 X509_get_subject_name(peerCert), NID_commonName, sslUser.data(),
249                 static_cast<int>(sslUser.size()));
250 
251             if (status == -1)
252             {
253                 BMCWEB_LOG_DEBUG
254                     << this << " TLS cannot get username to create session";
255                 return true;
256             }
257 
258             size_t lastChar = sslUser.find('\0');
259             if (lastChar == std::string::npos || lastChar == 0)
260             {
261                 BMCWEB_LOG_DEBUG << this << " Invalid TLS user name";
262                 return true;
263             }
264             sslUser.resize(lastChar);
265             std::string unsupportedClientId = "";
266             session = persistent_data::SessionStore::getInstance()
267                           .generateUserSession(
268                               sslUser, req->ipAddress.to_string(),
269                               unsupportedClientId,
270                               persistent_data::PersistenceType::TIMEOUT);
271             if (auto sp = session.lock())
272             {
273                 BMCWEB_LOG_DEBUG << this
274                                  << " Generating TLS session: " << sp->uniqueId;
275             }
276             return true;
277         });
278     }
279 
280     Adaptor& socket()
281     {
282         return adaptor;
283     }
284 
285     void start()
286     {
287 
288         startDeadline(0);
289 
290         // TODO(ed) Abstract this to a more clever class with the idea of an
291         // asynchronous "start"
292         if constexpr (std::is_same_v<Adaptor,
293                                      boost::beast::ssl_stream<
294                                          boost::asio::ip::tcp::socket>>)
295         {
296             adaptor.async_handshake(boost::asio::ssl::stream_base::server,
297                                     [this, self(shared_from_this())](
298                                         const boost::system::error_code& ec) {
299                                         if (ec)
300                                         {
301                                             return;
302                                         }
303                                         doReadHeaders();
304                                     });
305         }
306         else
307         {
308             doReadHeaders();
309         }
310     }
311 
312     void handle()
313     {
314         cancelDeadlineTimer();
315 
316         // Fetch the client IP address
317         readClientIp();
318 
319         bool isInvalidRequest = false;
320 
321         // Check for HTTP version 1.1.
322         if (req->version() == 11)
323         {
324             if (req->getHeaderValue(boost::beast::http::field::host).empty())
325             {
326                 isInvalidRequest = true;
327                 res.result(boost::beast::http::status::bad_request);
328             }
329         }
330 
331         BMCWEB_LOG_INFO << "Request: "
332                         << " " << this << " HTTP/" << req->version() / 10 << "."
333                         << req->version() % 10 << ' ' << req->methodString()
334                         << " " << req->target() << " " << req->ipAddress;
335 
336         needToCallAfterHandlers = false;
337 
338         if (!isInvalidRequest)
339         {
340             res.setCompleteRequestHandler(nullptr);
341             res.isAliveHelper = [this]() -> bool { return isAlive(); };
342 
343             req->ioService = static_cast<decltype(req->ioService)>(
344                 &adaptor.get_executor().context());
345 
346             if (!res.completed)
347             {
348                 needToCallAfterHandlers = true;
349                 res.setCompleteRequestHandler([self(shared_from_this())] {
350                     boost::asio::post(self->adaptor.get_executor(),
351                                       [self] { self->completeRequest(); });
352                 });
353                 if (req->isUpgrade() &&
354                     boost::iequals(
355                         req->getHeaderValue(boost::beast::http::field::upgrade),
356                         "websocket"))
357                 {
358                     handler->handleUpgrade(*req, res, std::move(adaptor));
359                     // delete lambda with self shared_ptr
360                     // to enable connection destruction
361                     res.setCompleteRequestHandler(nullptr);
362                     return;
363                 }
364                 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(res);
365                 handler->handle(*req, asyncResp);
366             }
367             else
368             {
369                 completeRequest();
370             }
371         }
372         else
373         {
374             completeRequest();
375         }
376     }
377 
378     bool isAlive()
379     {
380 
381         if constexpr (std::is_same_v<Adaptor,
382                                      boost::beast::ssl_stream<
383                                          boost::asio::ip::tcp::socket>>)
384         {
385             return adaptor.next_layer().is_open();
386         }
387         else
388         {
389             return adaptor.is_open();
390         }
391     }
392     void close()
393     {
394         if constexpr (std::is_same_v<Adaptor,
395                                      boost::beast::ssl_stream<
396                                          boost::asio::ip::tcp::socket>>)
397         {
398             adaptor.next_layer().close();
399 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
400             if (auto sp = session.lock())
401             {
402                 BMCWEB_LOG_DEBUG << this
403                                  << " Removing TLS session: " << sp->uniqueId;
404                 persistent_data::SessionStore::getInstance().removeSession(sp);
405             }
406 #endif // BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
407         }
408         else
409         {
410             adaptor.close();
411         }
412     }
413 
414     void completeRequest()
415     {
416         BMCWEB_LOG_INFO << "Response: " << this << ' ' << req->url << ' '
417                         << res.resultInt() << " keepalive=" << req->keepAlive();
418 
419         addSecurityHeaders(*req, res);
420 
421         if (needToCallAfterHandlers)
422         {
423             crow::authorization::cleanupTempSession(*req);
424         }
425 
426         if (!isAlive())
427         {
428             // BMCWEB_LOG_DEBUG << this << " delete (socket is closed) " <<
429             // isReading
430             // << ' ' << isWriting;
431             // delete this;
432 
433             // delete lambda with self shared_ptr
434             // to enable connection destruction
435             res.setCompleteRequestHandler(nullptr);
436             return;
437         }
438         if (res.body().empty() && !res.jsonValue.empty())
439         {
440             if (http_helpers::requestPrefersHtml(*req))
441             {
442                 prettyPrintJson(res);
443             }
444             else
445             {
446                 res.jsonMode();
447                 res.body() = res.jsonValue.dump(
448                     2, ' ', true, nlohmann::json::error_handler_t::replace);
449             }
450         }
451 
452         if (res.resultInt() >= 400 && res.body().empty())
453         {
454             res.body() = std::string(res.reason());
455         }
456 
457         if (res.result() == boost::beast::http::status::no_content)
458         {
459             // Boost beast throws if content is provided on a no-content
460             // response.  Ideally, this would never happen, but in the case that
461             // it does, we don't want to throw.
462             BMCWEB_LOG_CRITICAL
463                 << this << " Response content provided but code was no-content";
464             res.body().clear();
465         }
466 
467         res.addHeader(boost::beast::http::field::date, getCachedDateStr());
468 
469         res.keepAlive(req->keepAlive());
470 
471         doWrite();
472 
473         // delete lambda with self shared_ptr
474         // to enable connection destruction
475         res.setCompleteRequestHandler(nullptr);
476     }
477 
478     void readClientIp()
479     {
480         boost::system::error_code ec;
481         BMCWEB_LOG_DEBUG << "Fetch the client IP address";
482         boost::asio::ip::tcp::endpoint endpoint =
483             boost::beast::get_lowest_layer(adaptor).remote_endpoint(ec);
484 
485         if (ec)
486         {
487             // If remote endpoint fails keep going. "ClientOriginIPAddress"
488             // will be empty.
489             BMCWEB_LOG_ERROR << "Failed to get the client's IP Address. ec : "
490                              << ec;
491         }
492         else
493         {
494             req->ipAddress = endpoint.address();
495         }
496     }
497 
498   private:
499     void doReadHeaders()
500     {
501         BMCWEB_LOG_DEBUG << this << " doReadHeaders";
502 
503         // Clean up any previous Connection.
504         boost::beast::http::async_read_header(
505             adaptor, buffer, *parser,
506             [this,
507              self(shared_from_this())](const boost::system::error_code& ec,
508                                        std::size_t bytesTransferred) {
509                 BMCWEB_LOG_ERROR << this << " async_read_header "
510                                  << bytesTransferred << " Bytes";
511                 bool errorWhileReading = false;
512                 if (ec)
513                 {
514                     errorWhileReading = true;
515                     BMCWEB_LOG_ERROR
516                         << this << " Error while reading: " << ec.message();
517                 }
518                 else
519                 {
520                     // if the adaptor isn't open anymore, and wasn't handed to a
521                     // websocket, treat as an error
522                     if (!isAlive() && !req->isUpgrade())
523                     {
524                         errorWhileReading = true;
525                     }
526                 }
527 
528                 cancelDeadlineTimer();
529 
530                 if (errorWhileReading)
531                 {
532                     close();
533                     BMCWEB_LOG_DEBUG << this << " from read(1)";
534                     return;
535                 }
536 
537                 if (!req)
538                 {
539                     close();
540                     return;
541                 }
542 
543                 // Note, despite the bmcweb coding policy on use of exceptions
544                 // for error handling, this one particular use of exceptions is
545                 // deemed acceptible, as it solved a significant error handling
546                 // problem that resulted in seg faults, the exact thing that the
547                 // exceptions rule is trying to avoid. If at some point,
548                 // boost::urls makes the parser object public (or we port it
549                 // into bmcweb locally) this will be replaced with
550                 // parser::parse, which returns a status code
551 
552                 try
553                 {
554                     req->urlView = boost::urls::url_view(req->target());
555                     req->url = req->urlView.encoded_path();
556                 }
557                 catch (std::exception& p)
558                 {
559                     BMCWEB_LOG_ERROR << p.what();
560                 }
561 
562                 crow::authorization::authenticate(*req, res, session);
563 
564                 bool loggedIn = req && req->session;
565                 if (loggedIn)
566                 {
567                     startDeadline(loggedInAttempts);
568                     BMCWEB_LOG_DEBUG << "Starting slow deadline";
569 
570                     req->urlParams = req->urlView.params();
571 
572 #ifdef BMCWEB_ENABLE_DEBUG
573                     std::string paramList = "";
574                     for (const auto param : req->urlParams)
575                     {
576                         paramList += param->key() + " " + param->value() + " ";
577                     }
578                     BMCWEB_LOG_DEBUG << "QueryParams: " << paramList;
579 #endif
580                 }
581                 else
582                 {
583                     const boost::optional<uint64_t> contentLength =
584                         parser->content_length();
585                     if (contentLength &&
586                         *contentLength > loggedOutPostBodyLimit)
587                     {
588                         BMCWEB_LOG_DEBUG << "Content length greater than limit "
589                                          << *contentLength;
590                         close();
591                         return;
592                     }
593 
594                     startDeadline(loggedOutAttempts);
595                     BMCWEB_LOG_DEBUG << "Starting quick deadline";
596                 }
597                 doRead();
598             });
599     }
600 
601     void doRead()
602     {
603         BMCWEB_LOG_DEBUG << this << " doRead";
604 
605         boost::beast::http::async_read(
606             adaptor, buffer, *parser,
607             [this,
608              self(shared_from_this())](const boost::system::error_code& ec,
609                                        std::size_t bytesTransferred) {
610                 BMCWEB_LOG_DEBUG << this << " async_read " << bytesTransferred
611                                  << " Bytes";
612 
613                 bool errorWhileReading = false;
614                 if (ec)
615                 {
616                     BMCWEB_LOG_ERROR
617                         << this << " Error while reading: " << ec.message();
618                     errorWhileReading = true;
619                 }
620                 else
621                 {
622                     if (isAlive())
623                     {
624                         cancelDeadlineTimer();
625                         bool loggedIn = req && req->session;
626                         if (loggedIn)
627                         {
628                             startDeadline(loggedInAttempts);
629                         }
630                         else
631                         {
632                             startDeadline(loggedOutAttempts);
633                         }
634                     }
635                     else
636                     {
637                         errorWhileReading = true;
638                     }
639                 }
640                 if (errorWhileReading)
641                 {
642                     cancelDeadlineTimer();
643                     close();
644                     BMCWEB_LOG_DEBUG << this << " from read(1)";
645                     return;
646                 }
647                 handle();
648             });
649     }
650 
651     void doWrite()
652     {
653         bool loggedIn = req && req->session;
654         if (loggedIn)
655         {
656             startDeadline(loggedInAttempts);
657         }
658         else
659         {
660             startDeadline(loggedOutAttempts);
661         }
662         BMCWEB_LOG_DEBUG << this << " doWrite";
663         res.preparePayload();
664         serializer.emplace(*res.stringResponse);
665         boost::beast::http::async_write(
666             adaptor, *serializer,
667             [this,
668              self(shared_from_this())](const boost::system::error_code& ec,
669                                        std::size_t bytesTransferred) {
670                 BMCWEB_LOG_DEBUG << this << " async_write " << bytesTransferred
671                                  << " bytes";
672 
673                 cancelDeadlineTimer();
674 
675                 if (ec)
676                 {
677                     BMCWEB_LOG_DEBUG << this << " from write(2)";
678                     return;
679                 }
680                 if (!res.keepAlive())
681                 {
682                     close();
683                     BMCWEB_LOG_DEBUG << this << " from write(1)";
684                     return;
685                 }
686 
687                 serializer.reset();
688                 BMCWEB_LOG_DEBUG << this << " Clearing response";
689                 res.clear();
690                 parser.emplace(std::piecewise_construct, std::make_tuple());
691                 parser->body_limit(httpReqBodyLimit); // reset body limit for
692                                                       // newly created parser
693                 buffer.consume(buffer.size());
694 
695                 req.emplace(parser->get());
696                 doReadHeaders();
697             });
698     }
699 
700     void cancelDeadlineTimer()
701     {
702         if (timerCancelKey)
703         {
704             BMCWEB_LOG_DEBUG << this << " timer cancelled: " << &timerQueue
705                              << ' ' << *timerCancelKey;
706             timerQueue.cancel(*timerCancelKey);
707             timerCancelKey.reset();
708         }
709     }
710 
711     void startDeadline(size_t timerIterations)
712     {
713         cancelDeadlineTimer();
714 
715         if (timerIterations)
716         {
717             timerIterations--;
718         }
719 
720         timerCancelKey =
721             timerQueue.add([self(shared_from_this()), timerIterations,
722                             readCount{parser->get().body().size()}] {
723                 // Mark timer as not active to avoid canceling it during
724                 // Connection destructor which leads to double free issue
725                 self->timerCancelKey.reset();
726                 if (!self->isAlive())
727                 {
728                     return;
729                 }
730 
731                 bool loggedIn = self->req && self->req->session;
732                 // allow slow uploads for logged in users
733                 if (loggedIn && self->parser->get().body().size() > readCount)
734                 {
735                     BMCWEB_LOG_DEBUG << self.get()
736                                      << " restart timer - read in progress";
737                     self->startDeadline(timerIterations);
738                     return;
739                 }
740 
741                 // Threshold can be used to drop slow connections
742                 // to protect against slow-rate DoS attack
743                 if (timerIterations)
744                 {
745                     BMCWEB_LOG_DEBUG << self.get() << " restart timer";
746                     self->startDeadline(timerIterations);
747                     return;
748                 }
749 
750                 self->close();
751             });
752 
753         if (!timerCancelKey)
754         {
755             close();
756             return;
757         }
758         BMCWEB_LOG_DEBUG << this << " timer added: " << &timerQueue << ' '
759                          << *timerCancelKey;
760     }
761 
762   private:
763     Adaptor adaptor;
764     Handler* handler;
765 
766     // Making this a std::optional allows it to be efficiently destroyed and
767     // re-created on Connection reset
768     std::optional<
769         boost::beast::http::request_parser<boost::beast::http::string_body>>
770         parser;
771 
772     boost::beast::flat_static_buffer<8192> buffer;
773 
774     std::optional<boost::beast::http::response_serializer<
775         boost::beast::http::string_body>>
776         serializer;
777 
778     std::optional<crow::Request> req;
779     crow::Response res;
780 
781     std::weak_ptr<persistent_data::UserSession> session;
782 
783     std::optional<size_t> timerCancelKey;
784 
785     bool needToCallAfterHandlers{};
786     bool needToStartReadAfterComplete{};
787 
788     std::function<std::string()>& getCachedDateStr;
789     detail::TimerQueue& timerQueue;
790 
791     using std::enable_shared_from_this<
792         Connection<Adaptor, Handler>>::shared_from_this;
793 };
794 } // namespace crow
795