<commit_before>/*
 * Copyright (c) 2010 Digital Bazaar, Inc. All rights reserved.
 */
#define __STDC_FORMAT_MACROS

#include "bitmunk/node/ProxyResourceHandler.h"

#include "bitmunk/node/BtpActionDelegate.h"
#include "monarch/io/ByteArrayInputStream.h"
#include "monarch/net/SocketTools.h"
#include "monarch/util/StringTokenizer.h"
#include "monarch/util/StringTools.h"

#include <algorithm>

using namespace std;
using namespace monarch::config;
using namespace monarch::http;
using namespace monarch::io;
using namespace monarch::net;
using namespace monarch::rt;
using namespace monarch::util;
using namespace monarch::util::regex;
using namespace bitmunk::protocol;
using namespace bitmunk::node;

// Note: Current implementation doesn't lock on proxy map. It is assumed it
// will be set up before use and not changed thereafter.

ProxyResourceHandler::ProxyResourceHandler(Node* node, const char* path) :
   mNode(node),
   mPath(strdup(path))
{
}

ProxyResourceHandler::~ProxyResourceHandler()
{
   ProxyResourceHandler::clearPermittedHosts();
   free(mPath);
   for(ProxyMap::iterator i = mProxyMap.begin(); i != mProxyMap.end(); i++)
   {
      PathToInfo* m = i->second;
      for(PathToInfo::iterator mi = m->begin(); mi != m->end(); mi++)
      {
         free((char*)mi->first);
      }
      delete m;
      free((char*)i->first);
   }
}

bool ProxyResourceHandler::addPermittedHost(const char* host)
{
   bool rval = true;

   // see if the host contains a wildcard
   if(strchr(host, '*') == NULL)
   {
      // not a wildcard
      mPermittedHosts.push_back(strdup(host));
      MO_CAT_INFO(BM_NODE_CAT,
         "ProxyResourceHandler added permitted host: %s", host);
   }
   else
   {
      // build regex, tokenizing on wildcards
      int paren = 0;
      string regex;
      StringTokenizer st(host, '*');
      while(st.hasNextToken())
      {
         // escape periods in token
         string token = st.nextToken();
         if(token.length() == 0 && regex.length() != 0)
         {
            regex.append(".*");
         }
         else if(token.length() > 0)
         {
            // handle ending without a colon or any port number
            if(token.at(token.length() - 1) == ':')
            {
               token.replace(token.length() - 1, 1, "($|:");
               paren++;
            }

            // start regex
            if(regex.length() == 0)
            {
               // handle starting with no period or with text then a period
               if(token.at(0) == '.')
               {
                  regex.append("(^|^.*\\.)");
                  token.erase(0, 1);
               }
               else
               {
                  regex.append("^.*");
               }
            }
            // continue regex
            else
            {
               regex.append(".*");
            }

            StringTools::replaceAll(token, ".", "\\.");
            regex.append(token);
         }
      }
      if(regex.length() == 0)
      {
         // handle the anything regex
         regex.append("^.*$");
      }
      else
      {
         // end regex
         regex.push_back('$');
         regex.append(paren, ')');
      }

      PatternRef pattern = Pattern::compile(regex.c_str(), true, false);
      if(pattern.isNull())
      {
         ExceptionRef e = new Exception(
            "Invalid permitted host wildcard format.",
            "bitmunk.node.ProxyResourceHandler.InvalidPermittedHost");
         e->getDetails()["host"] = host;
         e->getDetails()["regex"] = regex.c_str();
         Exception::push(e);
         rval = false;
      }
      else
      {
         mWildcardHosts.push_back(pattern);
         MO_CAT_INFO(BM_NODE_CAT,
            "ProxyResourceHandler added permitted host: %s (regex: '%s')",
            host, regex.c_str());
      }
   }

   return rval;
}

void ProxyResourceHandler::clearPermittedHosts()
{
   for(PermittedHosts::iterator i = mPermittedHosts.begin();
       i != mPermittedHosts.end(); i++)
   {
      free(*i);
   }
   mPermittedHosts.clear();
   mWildcardHosts.clear();
   MO_CAT_INFO(BM_NODE_CAT, "ProxyResourceHandler cleared permitted hosts");
}

void ProxyResourceHandler::addMapping(
   const char* host, const char* path, const char* url,
   bool rewriteHost, bool redirect, bool permanent)
{
   /* Build the absolute path that will be searched for in an HTTP request so
      that it can be mapped to the given url. Only append the proxy service
      path if it isn't the root path and only add the given path if it isn't a
      wildcard. The end result will be either a wildcard or the proxy service
      path concatenated with the relative path without having a leading
      double-slash when the proxy service is the root service.

      The code that looks for matches will first look for the given host, then
      for a wildcard host if that fails, then for the absolute path within the
      found host mapping (if any), and then for a path wildcard if that fails.
   */
   string absPath;
   bool root = (strcmp(mPath, "/") == 0);
   bool wildcard = (strcmp(path, "*") == 0);
   if(wildcard)
   {
      absPath.push_back('*');
   }
   else
   {
      if(!root)
      {
         absPath.append(mPath);
      }
      absPath.append(path);
   }

   // find PathToInfo map to update
   PathToInfo* m;
   ProxyMap::iterator i = mProxyMap.find(host);
   if(i != mProxyMap.end())
   {
      // get existing map
      m = i->second;

      // remove any duplicate entry
      PathToInfo::iterator mi = m->find(absPath.c_str());
      if(mi != m->end())
      {
         MO_CAT_INFO(BM_NODE_CAT,
            "ProxyResourceHandler removed %s rule: %s%s/* => %s%s/*",
            mi->second.redirect ? "redirect" : "proxy",
            host, (wildcard || strlen(mi->first) == 0) ? "" : mi->first,
            mi->second.url->getHostAndPort().c_str(),
            (mi->second.url->getPath().length() == 1) ?
               "" : mi->second.url->getPath().c_str());

         free((char*)mi->first);
         m->erase(mi);
      }
   }
   else
   {
      // add new map
      m = new PathToInfo;
      mProxyMap[strdup(host)] = m;
   }

   // add new entry and log it
   MappingInfo info;
   info.url = new Url(url);
   info.rewriteHost = rewriteHost;
   info.redirect = redirect;
   info.permanent = permanent;
   (*m)[strdup(absPath.c_str())] = info;
   MO_CAT_INFO(BM_NODE_CAT,
      "ProxyResourceHandler added %s rule: %s%s/* => %s%s/*",
      redirect ? "redirect" : "proxy",
      host, (wildcard || absPath.length() == 0) ? "" : absPath.c_str(),
      info.url->getHostAndPort().c_str(),
      (info.url->getPath().length() == 1) ? "" : info.url->getPath().c_str());
}

/**
 * Proxies HTTP traffic coming from the "in" connection to the "out" connection.
 *<commit_msg>Added code to prepend schemes for proxy urls if they are not present (and URLs are not relative).<commit_after>/*
 * Copyright (c) 2010 Digital Bazaar, Inc. All rights reserved.
 */
#define __STDC_FORMAT_MACROS

#include "bitmunk/node/ProxyResourceHandler.h"

#include "bitmunk/node/BtpActionDelegate.h"
#include "monarch/io/ByteArrayInputStream.h"
#include "monarch/net/SocketTools.h"
#include "monarch/util/StringTokenizer.h"
#include "monarch/util/StringTools.h"

#include <algorithm>

using namespace std;
using namespace monarch::config;
using namespace monarch::http;
using namespace monarch::io;
using namespace monarch::net;
using namespace monarch::rt;
using namespace monarch::util;
using namespace monarch::util::regex;
using namespace bitmunk::protocol;
using namespace bitmunk::node;

// Note: Current implementation doesn't lock on proxy map. It is assumed it
// will be set up before use and not changed thereafter.

ProxyResourceHandler::ProxyResourceHandler(Node* node, const char* path) :
   mNode(node),
   mPath(strdup(path))
{
}

ProxyResourceHandler::~ProxyResourceHandler()
{
   ProxyResourceHandler::clearPermittedHosts();
   free(mPath);
   for(ProxyMap::iterator i = mProxyMap.begin(); i != mProxyMap.end(); i++)
   {
      PathToInfo* m = i->second;
      for(PathToInfo::iterator mi = m->begin(); mi != m->end(); mi++)
      {
         free((char*)mi->first);
      }
      delete m;
      free((char*)i->first);
   }
}

bool ProxyResourceHandler::addPermittedHost(const char* host)
{
   bool rval = true;

   // see if the host contains a wildcard
   if(strchr(host, '*') == NULL)
   {
      // not a wildcard
      mPermittedHosts.push_back(strdup(host));
      MO_CAT_INFO(BM_NODE_CAT,
         "ProxyResourceHandler added permitted host: %s", host);
   }
   else
   {
      // build regex, tokenizing on wildcards
      int paren = 0;
      string regex;
      StringTokenizer st(host, '*');
      while(st.hasNextToken())
      {
         // escape periods in token
         string token = st.nextToken();
         if(token.length() == 0 && regex.length() != 0)
         {
            regex.append(".*");
         }
         else if(token.length() > 0)
         {
            // handle ending without a colon or any port number
            if(token.at(token.length() - 1) == ':')
            {
               token.replace(token.length() - 1, 1, "($|:");
               paren++;
            }

            // start regex
            if(regex.length() == 0)
            {
               // handle starting with no period or with text then a period
               if(token.at(0) == '.')
               {
                  regex.append("(^|^.*\\.)");
                  token.erase(0, 1);
               }
               else
               {
                  regex.append("^.*");
               }
            }
            // continue regex
            else
            {
               regex.append(".*");
            }

            StringTools::replaceAll(token, ".", "\\.");
            regex.append(token);
         }
      }
      if(regex.length() == 0)
      {
         // handle the anything regex
         regex.append("^.*$");
      }
      else
      {
         // end regex
         regex.push_back('$');
         regex.append(paren, ')');
      }

      PatternRef pattern = Pattern::compile(regex.c_str(), true, false);
      if(pattern.isNull())
      {
         ExceptionRef e = new Exception(
            "Invalid permitted host wildcard format.",
            "bitmunk.node.ProxyResourceHandler.InvalidPermittedHost");
         e->getDetails()["host"] = host;
         e->getDetails()["regex"] = regex.c_str();
         Exception::push(e);
         rval = false;
      }
      else
      {
         mWildcardHosts.push_back(pattern);
         MO_CAT_INFO(BM_NODE_CAT,
            "ProxyResourceHandler added permitted host: %s (regex: '%s')",
            host, regex.c_str());
      }
   }

   return rval;
}

void ProxyResourceHandler::clearPermittedHosts()
{
   for(PermittedHosts::iterator i = mPermittedHosts.begin();
       i != mPermittedHosts.end(); i++)
   {
      free(*i);
   }
   mPermittedHosts.clear();
   mWildcardHosts.clear();
   MO_CAT_INFO(BM_NODE_CAT, "ProxyResourceHandler cleared permitted hosts");
}

void ProxyResourceHandler::addMapping(
   const char* host, const char* path, const char* url,
   bool rewriteHost, bool redirect, bool permanent)
{
   // prepend scheme to absolute URLs if needed
   string tmpUrl;
   if(url[0] != '/' &&
      strncmp(url, "http://", 7) != 0 &&
      strncmp(url, "https://", 8) != 0)
   {
      tmpUrl = "http://";
   }
   tmpUrl.append(url);

   /* Build the absolute path that will be searched for in an HTTP request so
      that it can be mapped to the given url. Only append the proxy service
      path if it isn't the root path and only add the given path if it isn't a
      wildcard. The end result will be either a wildcard or the proxy service
      path concatenated with the relative path without having a leading
      double-slash when the proxy service is the root service.

      The code that looks for matches will first look for the given host, then
      for a wildcard host if that fails, then for the absolute path within the
      found host mapping (if any), and then for a path wildcard if that fails.
   */
   string absPath;
   bool root = (strcmp(mPath, "/") == 0);
   bool wildcard = (strcmp(path, "*") == 0);
   if(wildcard)
   {
      absPath.push_back('*');
   }
   else
   {
      if(!root)
      {
         absPath.append(mPath);
      }
      absPath.append(path);
   }

   // find PathToInfo map to update
   PathToInfo* m;
   ProxyMap::iterator i = mProxyMap.find(host);
   if(i != mProxyMap.end())
   {
      // get existing map
      m = i->second;

      // remove any duplicate entry
      PathToInfo::iterator mi = m->find(absPath.c_str());
      if(mi != m->end())
      {
         MO_CAT_INFO(BM_NODE_CAT,
            "ProxyResourceHandler removed %s rule: %s%s/* => %s%s/*",
            mi->second.redirect ? "redirect" : "proxy",
            host, (wildcard || strlen(mi->first) == 0) ? "" : mi->first,
            (mi->second.url->getHost().length() == 0) ?
               "" : mi->second.url->getHostAndPort().c_str(),
            (mi->second.url->getPath().length() == 1) ?
               "" : mi->second.url->getPath().c_str());

         free((char*)mi->first);
         m->erase(mi);
      }
   }
   else
   {
      // add new map
      m = new PathToInfo;
      mProxyMap[strdup(host)] = m;
   }

   // add new entry and log it
   MappingInfo info;
   info.url = new Url(tmpUrl.c_str());
   info.rewriteHost = rewriteHost;
   info.redirect = redirect;
   info.permanent = permanent;
   (*m)[strdup(absPath.c_str())] = info;
   MO_CAT_INFO(BM_NODE_CAT,
      "ProxyResourceHandler added %s rule: %s%s/* => %s%s/*",
      redirect ? "redirect" : "proxy",
      host, (wildcard || absPath.length() == 0) ? "" : absPath.c_str(),
      (info.url->getHost().length() == 0) ?
         "" : info.url->getHostAndPort().c_str(),
      (info.url->getPath().length() == 1) ? "" : info.url->getPath().c_str());
}

/**
 * Proxies HTTP traffic coming from the "in" connection to the "out" connection.
 *
{'fraction_non_alphanumeric': 0.13995019565990752, 'fraction_numerical': 0.0036286019210245465, 'mean_word_length': 2.186579007027885, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 62, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5916298', 'n_tokens_mistral': 4379, 'n_tokens_neox': 4245, 'n_words': 1098}