Coverage Report

Created: 2025-10-10 06:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wt/src/web/WebRequest.C
Line
Count
Source
1
/*
2
 * Copyright (C) 2008 Emweb bv, Herent, Belgium.
3
 *
4
 * See the LICENSE file for terms of use.
5
 */
6
7
#include "Wt/WException.h"
8
#include "Wt/WLocale.h"
9
#include "Wt/WLogger.h"
10
#include "Wt/WDateTime.h"
11
#include "Wt/Utils.h"
12
13
#include "WebRequest.h"
14
#include "WebUtils.h"
15
#include "Configuration.h"
16
17
#include "Wt/Auth/AuthUtils.h"
18
19
#include <cstdlib>
20
21
#include <boost/algorithm/string.hpp>
22
23
#ifndef WT_NO_SPIRIT
24
25
#include <boost/version.hpp>
26
27
#if BOOST_VERSION < 103600
28
#include <boost/spirit.hpp>
29
#else
30
#include <boost/spirit/include/classic_core.hpp>
31
#include <boost/spirit/include/classic_attribute.hpp>
32
#endif
33
34
#endif // WT_NO_SPIRIT
35
36
using std::atoi;
37
38
namespace {
39
40
0
inline std::string str(const char *s) {
41
0
  return s ? std::string(s) : std::string();
42
0
}
43
44
0
bool isPrivateIP(const std::string &s) {
45
0
  return boost::starts_with(s, "127.") ||
46
0
         boost::starts_with(s, "10.") ||
47
0
         boost::starts_with(s, "192.168.") ||
48
0
         (s.size() >= 7 &&
49
0
          boost::starts_with(s, "172.") &&
50
0
          s[6] == '.' &&
51
0
          ((s[4] == '1' &&
52
0
            s[5] >= '6' &&
53
0
            s[5] <= '9') ||
54
0
           (s[4] == '2' &&
55
0
            s[5] >= '0' &&
56
0
            s[5] <= '9') ||
57
0
           (s[4] == '3' &&
58
0
            s[5] >= '0' &&
59
0
            s[5] <= '1')));
60
0
}
61
62
}
63
64
namespace Wt {
65
66
LOGGER("WebRequest");
67
68
Http::ParameterValues WebRequest::emptyValues_;
69
70
struct WebRequest::AsyncEmulation {
71
  bool done;
72
73
  AsyncEmulation()
74
0
    : done(false)
75
0
  { }
76
};
77
78
WebRequest::WebRequest()
79
0
  : entryPoint_(nullptr),
80
0
    extraStartIndex_(0),
81
0
    async_(nullptr),
82
0
    responseType_(ResponseType::Page),
83
0
    webSocketRequest_(false)
84
0
{
85
0
#ifndef BENCH
86
0
  start_ = std::chrono::high_resolution_clock::now();
87
0
#endif
88
0
}
89
90
WebRequest::~WebRequest()
91
0
{
92
0
  delete async_;
93
0
  log();
94
0
}
95
96
void WebRequest::log()
97
0
{
98
0
#ifndef BENCH
99
0
  if (start_.time_since_epoch().count() > 0) {
100
0
    auto end = std::chrono::high_resolution_clock::now();
101
0
    double microseconds
102
0
      = std::chrono::duration_cast<std::chrono::microseconds>(end - start_)
103
0
      .count();
104
0
    LOG_INFO("took " << (microseconds / 1000) << " ms");
105
106
0
    start_ = std::chrono::high_resolution_clock::time_point();
107
0
  }
108
0
#endif
109
0
}
110
111
void WebRequest::reset()
112
0
{
113
0
#ifndef BENCH
114
0
  start_ = std::chrono::high_resolution_clock::now();
115
0
#endif
116
117
0
  entryPoint_ = 0;
118
0
  delete async_;
119
0
  async_ = 0;
120
0
  webSocketRequest_ = false;
121
122
0
  parameters_.clear();
123
0
  files_.clear();
124
125
0
  urlParams_.clear();
126
0
}
127
128
void WebRequest::readWebSocketMessage(WT_MAYBE_UNUSED const ReadCallback& callback)
129
0
{
130
0
  throw WException("should not get here");
131
0
}
132
133
bool WebRequest::webSocketMessagePending() const
134
0
{
135
0
  throw WException("should not get here");
136
0
}
137
138
void WebRequest::setTransferWebSocketResourceSocketCallBack(WebSocketResourceTransferCallback cb)
139
0
{
140
0
  wsResourceTransferCb_ = cb;
141
0
}
142
143
bool WebRequest::hasTransferWebSocketResourceSocketCallBack()
144
0
{
145
0
  return wsResourceTransferCb_ != nullptr;
146
0
}
147
148
void WebRequest::transferWebSocketResourceSocket(const std::shared_ptr<WebSocketConnection> &socket)
149
0
{
150
0
  Wt::Http::Request request(*this, nullptr);
151
0
  wsResourceTransferCb_(request, socket);
152
0
}
153
154
bool WebRequest::detectDisconnect(WT_MAYBE_UNUSED const DisconnectCallback& callback)
155
0
{
156
0
  return false; /* Not implemented */
157
0
}
158
159
boost::string_view WebRequest::entryPointPath() const
160
0
{
161
0
  return boost::string_view(pathInfo()).substr(0, extraStartIndex_);
162
0
}
163
164
std::string WebRequest::fullEntryPointPath() const
165
0
{
166
0
  return scriptName() + entryPointPath().to_string();
167
0
}
168
169
boost::string_view WebRequest::extraPathInfo() const
170
0
{
171
0
  return boost::string_view(pathInfo()).substr(extraStartIndex_);
172
0
}
173
174
const char *WebRequest::userAgent() const
175
0
{
176
0
  return headerValue("User-Agent");
177
0
}
178
179
const char *WebRequest::referer() const
180
0
{
181
0
  return headerValue("Referer");
182
0
}
183
184
const char *WebRequest::contentType() const
185
0
{
186
0
  return envValue("CONTENT_TYPE");
187
0
}
188
189
::int64_t WebRequest::contentLength() const
190
0
{
191
0
  const char *lenstr = envValue("CONTENT_LENGTH");
192
193
0
  if (!lenstr || strlen(lenstr) == 0)
194
0
    return 0;
195
0
  else {
196
0
    try {
197
0
      ::int64_t len = Utils::stoll(std::string(lenstr));
198
0
      if (len < 0) {
199
0
        LOG_ERROR("Bad content-length: " << lenstr);
200
0
        throw WException("Bad content-length");
201
0
      } else {
202
0
        return len;
203
0
      }
204
0
    } catch (std::exception& e) {
205
0
      LOG_ERROR("Bad content-length: " << lenstr);
206
0
      throw WException("Bad content-length");
207
0
    }
208
0
  }
209
0
}
210
211
const std::string *WebRequest::getParameter(const std::string& name) const
212
0
{
213
0
  const Http::ParameterValues& values = getParameterValues(name);
214
215
0
  return !values.empty() ? &values[0] : 0;
216
0
}
217
218
const Http::ParameterValues&
219
WebRequest::getParameterValues(const std::string& name) const
220
0
{
221
0
  Http::ParameterMap::const_iterator i = parameters_.find(name);
222
0
  if (i != parameters_.end())
223
0
    return i->second;
224
0
  else
225
0
    return emptyValues_;
226
0
}
227
228
#ifndef WT_NO_SPIRIT
229
namespace {
230
#if BOOST_VERSION < 103600
231
  using namespace boost::spirit;
232
#else
233
  using namespace boost::spirit::classic;
234
#endif
235
236
  using namespace boost;
237
238
  /*
239
   * My first spirit parser -- spirit is nifty !
240
   *
241
   * Parses things like:
242
   *  nl-be,en-us;q=0.7,en;q=0.3
243
   *  ISO-8859-1,utf-8;q=0.7,*;q=0.7
244
   *
245
   * And store the values with indicated qualities.
246
   */
247
  class ValueListParser : public grammar<ValueListParser>
248
  {
249
  public:
250
    struct Value {
251
      std::string value;
252
      double quality;
253
254
0
      Value(std::string v, double q) : value(v), quality(q) { }
255
    };
256
257
    ValueListParser(std::vector<Value>& values)
258
0
      : values_(values)
259
0
    { }
260
261
  private:
262
    std::vector<Value>& values_;
263
264
0
    void setQuality(double v) const {
265
0
      values_.back().quality = v;
266
0
    }
267
268
0
    void addValue(char const* str, char const* end) const {
269
0
      values_.push_back(Value(std::string(str, end), 1.));
270
0
    }
271
272
    typedef ValueListParser self_t;
273
274
  public:
275
    template <typename ScannerT>
276
    struct definition
277
    {
278
      definition(ValueListParser const& self)
279
0
      {
280
0
        option
281
0
          = ((ch_p('q') | ch_p('Q'))
282
0
             >> '='
283
0
             >> ureal_p
284
0
                [
285
0
                  std::bind(&self_t::setQuality, self, std::placeholders::_1)
286
0
                ]
287
0
             )
288
0
          | (+alpha_p >> '=' >> +alnum_p)
289
0
          ;
290
291
0
        value
292
0
          = lexeme_d[(alpha_p >> +(alnum_p | '-')) | '*']
293
0
            [
294
0
               std::bind(&self_t::addValue, self, std::placeholders::_1, std::placeholders:: _2)
295
0
            ]
296
0
            >> !( ';' >> option )
297
0
          ;
298
299
0
        valuelist
300
0
          = !(value  >> *(',' >> value )) >> end_p
301
0
          ;
302
0
      }
303
304
      rule<ScannerT> option, value, valuelist;
305
306
0
      rule<ScannerT> const& start() const { return valuelist; }
307
    };
308
  };
309
}
310
311
std::string WebRequest::parsePreferredAcceptValue(const char *str) const
312
0
{
313
0
  if (!str)
314
0
    return std::string();
315
316
0
  std::vector<ValueListParser::Value> values;
317
318
0
  ValueListParser valueListParser(values);
319
320
0
  parse_info<> info = parse(str, valueListParser, space_p);
321
322
0
  if (info.full) {
323
0
    unsigned best = 0;
324
0
    for (unsigned i = 1; i < values.size(); ++i) {
325
0
      if (values[i].quality > values[best].quality)
326
0
        best = i;
327
0
    }
328
329
0
    if (best < values.size())
330
0
      return values[best].value;
331
0
    else
332
0
      return std::string();
333
0
  } else {
334
0
    LOG_ERROR("Could not parse 'Accept-Language: " << str
335
0
              << "', stopped at: '" << info.stop << '\'');
336
0
    return std::string();
337
0
  }
338
0
}
339
#else
340
std::string WebRequest::parsePreferredAcceptValue(const char *str) const
341
{
342
  return std::string();
343
}
344
#endif // WT_NO_SPIRIT
345
346
WLocale WebRequest::parseLocale() const
347
0
{
348
0
  return WLocale(parsePreferredAcceptValue(headerValue("Accept-Language")));
349
0
}
350
351
void WebRequest::setAsyncCallback(const WriteCallback& cb)
352
0
{
353
0
  asyncCallback_ = cb;
354
0
}
355
356
const WebRequest::WriteCallback& WebRequest::getAsyncCallback()
357
0
{
358
0
  return asyncCallback_;
359
0
}
360
361
void WebRequest::emulateAsync(ResponseState state)
362
0
{
363
0
  if (async_) {
364
0
    if (state == ResponseState::ResponseDone)
365
0
      async_->done = true;
366
367
0
    return;
368
0
  }
369
370
0
  if (state == ResponseState::ResponseFlush) {
371
0
    async_ = new AsyncEmulation();
372
373
    /*
374
     * Invoke callback immediately and keep doing so until we get a
375
     * flush(ResponseDone). If we do not have a callback then the application
376
     * is waiting for some event to finish te request (e.g. a resource
377
     * continuation) and thus we exit now and wait for more flush()ing.
378
     */
379
0
    while (!async_->done) {
380
0
      if (!asyncCallback_) {
381
0
        delete async_;
382
0
        async_ = nullptr;
383
0
        return;
384
0
      }
385
386
0
      WriteCallback fn = asyncCallback_;
387
0
      asyncCallback_ = WriteCallback();
388
0
      fn(WebWriteEvent::Completed);
389
0
    }
390
0
  }
391
392
0
  delete this;
393
0
}
394
395
void WebRequest::setResponseType(ResponseType responseType)
396
0
{
397
0
  responseType_ = responseType;
398
0
}
399
400
const std::vector<std::pair<std::string, std::string> > &WebRequest::urlParams() const
401
0
{
402
0
  return urlParams_;
403
0
}
404
405
std::string WebRequest::clientAddress(const Configuration &conf) const
406
0
{
407
0
  std::string remoteAddr = str(envValue("REMOTE_ADDR"));
408
0
  if (conf.behindReverseProxy()) {
409
    // Old, deprecated behavior
410
0
    std::string clientIp = str(headerValue("Client-IP"));
411
412
0
    std::vector<std::string> ips;
413
0
    if (!clientIp.empty())
414
0
      boost::split(ips, clientIp, boost::is_any_of(","));
415
416
0
    std::string forwardedFor = str(headerValue("X-Forwarded-For"));
417
418
0
    std::vector<std::string> forwardedIps;
419
0
    if (!forwardedFor.empty())
420
0
      boost::split(forwardedIps, forwardedFor, boost::is_any_of(","));
421
422
0
    Utils::insert(ips, forwardedIps);
423
424
0
    for (auto &ip : ips) {
425
0
      boost::trim(ip);
426
427
0
      if (!ip.empty()
428
0
          && !isPrivateIP(ip)) {
429
0
        return ip;
430
0
      }
431
0
    }
432
433
0
    return remoteAddr;
434
0
  } else {
435
0
    if (conf.isTrustedProxy(remoteAddr)) {
436
0
      std::string forwardedFor = str(headerValue(conf.originalIPHeader().c_str()));
437
0
      boost::trim(forwardedFor);
438
0
      std::vector<std::string> forwardedIps;
439
0
      boost::split(forwardedIps, forwardedFor, boost::is_any_of(","));
440
0
      for (auto it = forwardedIps.rbegin();
441
0
           it != forwardedIps.rend(); ++it) {
442
0
        boost::trim(*it);
443
0
        if (!it->empty()) {
444
0
          if (!conf.isTrustedProxy(*it)) {
445
0
            return *it;
446
0
          } else {
447
            /*
448
             * When the left-most address in a forwardedHeader is contained
449
             * within a trustedProxy subnet, it should be returned as the clientAddress
450
             */
451
0
            remoteAddr = *it;
452
0
          }
453
0
        }
454
0
      }
455
0
    }
456
0
    return remoteAddr;
457
0
  }
458
0
}
459
460
std::string WebRequest::hostName(const Configuration &conf) const
461
0
{
462
0
  std::string host = str(headerValue("Host"));
463
464
0
  if (conf.behindReverseProxy() ||
465
0
      conf.isTrustedProxy(remoteAddr())) {
466
0
    std::string forwardedHost = str(headerValue("X-Forwarded-Host"));
467
468
0
    if (!forwardedHost.empty()) {
469
0
      std::string::size_type i = forwardedHost.rfind(',');
470
0
      if (i == std::string::npos) {
471
0
        host = forwardedHost;
472
0
      } else {
473
0
        host = forwardedHost.substr(i + 1);
474
0
      }
475
0
    }
476
0
  }
477
478
0
  return host;
479
0
}
480
481
std::string WebRequest::urlScheme(const Configuration &conf) const
482
0
{
483
0
  if (conf.behindReverseProxy() ||
484
0
      conf.isTrustedProxy(remoteAddr())) {
485
0
    std::string forwardedProto = str(headerValue("X-Forwarded-Proto"));
486
0
    if (!forwardedProto.empty()) {
487
0
      std::string::size_type i = forwardedProto.rfind(',');
488
0
      if (i == std::string::npos)
489
0
        return forwardedProto;
490
0
      else
491
0
        return forwardedProto.substr(i+1);
492
0
    }
493
0
  }
494
495
0
  return urlScheme();
496
0
}
497
498
void WebRequest::addNonce()
499
0
{
500
0
  nonce_ = Utils::base64Encode(Auth::Utils::createSalt(18), false);
501
0
  addHeader("Content-Security-Policy", "script-src 'nonce-"+nonce()+"' 'strict-dynamic' 'unsafe-eval'");
502
0
}
503
504
}