[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/ssh352/rstudio/main/src/cpp/core/http/Request.cpp [Back]  [Original]

/*
 * Request.cpp
 *
 * Copyright (C) 2022 by Posit Software, PBC
 *
 * Unless you have received this program directly from Posit Software pursuant
 * to the terms of a commercial license agreement with Posit Software, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include 
#include 

#include 

#include 
#include 
#include 
#include 
#include 

#include 
#include 

namespace rstudio {
namespace core {
namespace http {

Request::Request() 
   : remoteUid_(-1),
     parsedCookies_(false), 
     parsedFormFields_(false), 
     parsedQueryParams_(false)
{
}

Request::~Request()
{
}

bool Request::isSecure() const
{
   // the request is secure if the browser is using HTTPS to
   // reach the server which may depend on the presence of a proxy
   return URL(proxiedUri()).protocol() == "https";
}

std::string Request::baseUri(BaseUriUse use /*= BaseUriUse::Internal*/) const
{
   // When the root path is defined as other than the default ("/")
   // we can guess with precision the actual URL show by the browser's
   // address bar when we return the proxied URI. Without it either
   // it doesn't matter because there's no proxy and the internal
   // URI is the same address visible in the browser or the proxy is
   // taking care of the path-rewrite which is something we asked
   // customers to do before v1.4
   if (rootPath() != kRequestDefaultRootPath)
      return proxiedUri();
   if (use == BaseUriUse::Internal)
      return internalUri();
   // When BaseUriUse::External, we omit the internal URI
   return "";
}

std::string Request::internalUri() const
{
   // ignore the proxy for the most part except by the scheme
   std::string scheme = URL(proxiedUri()).protocol();
   return scheme + "://" + host() + uri();
}

std::string Request::rootPath() const
{
   // if there's no proxy defining the header resort
   // to the externally defined value for root path
   std::string rootPathHeader = headerValue("X-RStudio-Root-Path");
   if (rootPathHeader == "")
   {
      rootPathHeader = rootPath_;
   }

   // be sure the root path start with slash but doesn't end with one (unless literally only "/")
   if (rootPathHeader.empty() || rootPathHeader[0] != '/')
      rootPathHeader = '/' + rootPathHeader;
   if (rootPathHeader.length() > 1 && rootPathHeader[rootPathHeader.length() - 1] == '/')
      rootPathHeader = rootPathHeader.substr(0, rootPathHeader.length() - 1);

   return rootPathHeader;
}

std::string firstInList(const std::string& input)
{
   std::string res;

   // Take the first if there's a list
   std::size_t pos = input.find(',');
   if (pos != std::string::npos)
      res = input.substr(0, pos);
   else
      res = input;
   return res;
}

std::string Request::proxiedUri() const
{
   // if using the product-specific header use it
   // it should contain the exact scheme/host/port/path
   // of the original request as send by the browser
   std::string overrideHeader = headerValue("X-RStudio-Request");
   if (!overrideHeader.empty())
   {
      return overrideHeader;
   }

   std::string root = rootPath();

   // some internal requests may contain a virtual path any attempt to
   // compose the same address as seen in browser must be include this path
   std::string vPath = virtualPath();
   if (!vPath.empty())
   {
      root += vPath;
   }

   // might be using new Forwarded header
   std::string forwarded = headerValue("Forwarded");
   if (!forwarded.empty())
   {
      std::string forwardedHost;
      boost::regex reHost("host=([\\w.:]+);?");
      boost::smatch matches;
      if (boost::regex_search(forwarded, matches, reHost))
         forwardedHost = matches[1];

      std::string protocol = "http";
      boost::regex reProto("proto=([\\w.:]+);?");
      if (boost::regex_search(forwarded, matches, reProto))
         protocol = matches[1];

      return URL::complete(protocol + "://" + forwardedHost, root + '/' + uri());
   }

   // get the protocol that was specified in the request
   // it might have been specified by rserver-http w/ ssl-enabled=1
   std::string protocol = headerValue("X-Forwarded-Proto");
   if (protocol.empty())
   {
      protocol = "http";
   }
   else
   {
      protocol = firstInList(protocol);
   }


   // might be using the legacy X-Forwarded headers
   std::string forwardedHost = headerValue("X-Forwarded-Host");
   if (!forwardedHost.empty())
   {
      forwardedHost = firstInList(forwardedHost);

      // get the port that may be specified in the request
      std::string port = headerValue("X-Forwarded-Port");
      if (!port.empty())
      {
         port = firstInList(port);

         std::size_t pos = forwardedHost.find(':');
         if (pos == std::string::npos)
         {
            forwardedHost += ":" + port;
         }
         else
         {
            forwardedHost = forwardedHost.substr(0, pos + 1) + port;
         }
      }

      return URL::complete(protocol + "://" + forwardedHost, root + '/' + uri());
   }

   // use the protocol that may have been set by X-Forwarded-Proto
   return URL::complete(protocol + "://" + host(), root + '/' + uri());
}

bool Request::acceptsContentType(const std::string& contentType) const
{
   return headerValue("Accept").find(contentType) != std::string::npos;
}

bool Request::acceptsEncoding(const std::string& encoding) const
{
   // read , separated fields
   using namespace boost;
   char_separator comma(", ");
   std::string accepted = acceptEncoding();
   tokenizer tokens(accepted, comma);
   return std::find(tokens.begin(), tokens.end(), encoding) != tokens.end();
}
   
boost::posix_time::ptime Request::ifModifiedSince() const
{
   using namespace boost::posix_time;
   
   std::string modifiedSinceDate = headerValue("If-Modified-Since");
   if (!modifiedSinceDate.empty())
   {
      return util::parseHttpDate(modifiedSinceDate);
   }
   else
   {
      return ptime(not_a_date_time);
   }
      
}

std::string Request::path() const
{
   std::string::size_type pos = uri().find('?');
   if (pos != std::string::npos)
      return uri().substr(0, pos);
   else
      return uri();
}

std::string Request::queryString() const
{
   // find ? in uri()
   std::string::size_type pos = uri().find('?');
   if (pos != std::string::npos)
   {
      std::string::size_type qsPos = pos + 1;
      if (uri().length() > qsPos)
         return uri().substr(qsPos);
      else
         return std::string();
   }
   else
   {
      return std::string();
   }
}
   
const Fields& Request::queryParams() const
{
   if (!parsedQueryParams_)
   {
      util::parseQueryString(queryString(), &queryParams_);
      parsedQueryParams_ = true;
   }
   
   return queryParams_;
}
   
std::string Request::cookieValue(const std::string& name) const
{
   // parse cookies on demand
   if ( !parsedCookies_ )
   {
      for (Headers::const_iterator it =
            headers().begin(); it != headers().end(); ++it )
      {
         scanHeaderForCookie(it->name, it->value);
      }
      parsedCookies_ = true;
   }

   // lookup the cookie
   std::string cookie = util::fieldValue(cookies_, name);

   // when embedded into an iFrame a legacy cookie
   // may be present for old, non-conforming browsers
   if (cookie.empty())
   {
      cookie = util::fieldValue(cookies_, name + kLegacyCookieSuffix);
   }
   return cookie;
}

void Request::addCookie(const std::string& name, const std::string& value)
{
   cookies_.push_back(std::make_pair(name, value));
   std::vector cookies;
   for (const auto& cookie : cookies_)
   {
      cookies.push_back(cookie.first + "=" + cookie.second);
   }
   setHeader("Cookie", boost::algorithm::join(cookies, "; "));
}

std::string Request::cookieValueFromHeader(const std::string& headerName) const
{
   std::string value = headerValue(headerName);

   Fields cookie;
   util::parseFields(value, ";, ", "= ", &cookie, util::FieldDecodeNone);

   if (cookie.size() > 0)
      return cookie.at(0).second;
   else
      return std::string();
}

std::string Request::formFieldValue(const std::string& name) const 
{
   ensureFormFieldsParsed();
   
   // lookup the form field
   return util::fieldValue(formFields_, name);
}
   
const Fields& Request::formFields() const
{
   ensureFormFieldsParsed();
   
   return formFields_;
}
   
const File& Request::uploadedFile(const std::string& name) const
{
   ensureFormFieldsParsed();
   
   // lookup the file
   for (Files::const_iterator it = files_.begin(); it != files_.end(); ++it)
   {
      if (it->first == name)
         return it->second;
   }
   
   // not found
   return emptyFile_;
}
   
std::string Request::queryParamValue(const std::string& name) const
{
   // lookup the query param
   return util::fieldValue(queryParams(), name);
}
   
void Request::setBody(const std::string& body)
{
   body_ = body;
   setContentLength(gsl::narrow_cast(body_.length()));
}
   
void Request::debugPrintUri(const std::string& caption) const
{
   static boost::mutex printMutex;
   LOCK_MUTEX(printMutex)
   {
      std::cerr 

Web Proxy Viewer  |  New URL  |  Original Page