const fs = require('fs')
const express = require('express');
const https = require('https');
const path = require('path');
// var hostBucket = [];
// https.createServer({
//     cert: fs.readFileSync('./oobeservermain.pem'),
//     key: fs.readFileSync('./oobeservermain.key')
// },async (req, res)=>{
//     console.log("Serving request for " + req.headers.host);
//     if (req.headers.host === 'accounts.google.com') {
//         res.writeHead(200, '', {
//             "Content-Type": "text/html",
//             "x-manage-chrome-accounts": "incognito=1"
//         });
//         res.end('Intercepting google accounts.');
//         return;
//     };
    
//     if (req.headers.host === 'play.google.com') {
//         res.writeHead(200, '', {
//             "Content-Type": "text/html",
//         });
//         res.end("This exploit was written by CRZero and Chromium Labs.\nPrimary Developer: MCRideable#3693.\n Combination of k1llswitch and certain chromium vulnerabilities. We will be shortly pwning your browser and placing a shell in the current page. Link to incognito? <a href='//accounts.google.com/SignOutOptions' >link1</a>");
//         return;
//     };
//     let url = req.url;
//     console.log(req.url);
//     let resp;
//     try {
//     resp = await axios.request({
//         url: url,
//         headers: req.headers,
//         responseType: "arraybuffer"
//     });
// }catch {
//     res.writeHead(404);
//     res.end("Failed");
//     return; 
// }
    
//     res.writeHead(resp.status, resp.statusText, resp.headers);
//     if (resp.data) {
//         res.end(resp.data);
//     }

// }).listen(3000);
//Credits to https://medium.com/@nimit95/a-simple-http-https-proxy-in-node-js-4eb0444f38fc or @nimit95
const net = require('net');
const server = net.createServer();
// miniServerMap[host] = new MiniServer();
/**
 * @type {Object<string,MiniServer>}
 */
var miniServerMap = {};

// manifest.json is per website
// Location: configs/<website name>/manifest.json (ex. www.google.com/manifest.json)


/**
 * @type {import('./proxy').ServerConfig}
 */
let a;

/**
 * @type {Object<string, {
 *  filter: (f: import('./proxy').FilterInfo)=>void,
 *  proxy: (config: import('./proxy').ServerConfig, clientsock: net.Socket)=>void,
 *  config: import('./proxy').ServerConfig
 * }>}
 */
let serverCallbackMap = {};



/**
 * 
 * @param {import('./proxy').ServerConfig} config 
 */
function readServerConfig(address, config) {
  /**
   * 
   * @returns {import('./proxy').FilterFunction}
   */
  const defaultServerFilterGetter = function () {
    if (config.filterPath) { // Filter path takes precedence as it handles all cases
      return require(filterPath).filter;
    }
    else if (config.filters) {
      
      return function ({tls}) {
        if (tls) {
          return config.filters.includes('https');
        }
        else {
          return config.filters.includes('http');
        }
      }
    }
  }
  const defaultServerProxyGetter = function () {
    if (config.proxyPath) { // Proxypath takes precedence over rev proxy due to js handling nature
      return require(path.resolve(__dirname,"configs", address, config.proxyPath)).proxy;
    }else if (config.reverseProxyUrl) {
      const url = config.reverseProxyUrl;
      const a = url + req.path;
      const x = new URL(a);
      const socketDNS = x.host;


      /**
       * @param {net.Socket} clientsock
       */
      return function (config, clientsock) {
        const as = net.createConnection({
          host: x.host,
          port: parseInt(x.port)
        });
        clientsock.pipe(as);
        
      }
    }
    
  }

  const configData = {filter: defaultServerFilterGetter, proxy: defaultServerProxyGetter, config};
  return configData;
}
function getAllServerConfigs() {
  const allConfigDir = path.resolve(__dirname, 'configs');
  const a = fs.readdirSync(allConfigDir);
  for (const server of a){
    console.info("Reading config for: ", server);
    var serverPath = null;
    const files = fs.readdirSync((serverPath = path.resolve(allConfigDir, server)));
    if (!files.includes("manifest.json")) {
      console.error(`Could not read config for ${server}. Moving on to next server`);
      continue;
    }
    const manifestData = fs.readFileSync(path.resolve(serverPath, 'manifest.json'), {encoding: 'utf8'});
    console.log(manifestData)
    /**
     * @type {import('./proxy').ServerConfig}
     */
    const serverConfig = JSON.parse(manifestData);
    const funcs = readServerConfig(server,serverConfig);
    serverCallbackMap[serverConfig.matches ?? server] = funcs;
  }
}
getAllServerConfigs()
// FilterInfo: {
//     host: string,
//     tls: boolean,
//}
server.on('connection', (clientToProxySocket) => {
    // We need only the data once, the starting packet
    // console.log("client connected");
    clientToProxySocket.once('data', (data) => {
      let isTLSConnection = data.toString().indexOf('CONNECT') !== -1;
      var path = null;
      //Considering Port as 80 by default 
      let serverPort = 80;
      let serverAddress;
      var useMiniServer= false;
      if (isTLSConnection) {
        // Port changed to 443, parsing the host from CONNECT 
        serverPort = 443;
        serverAddress = data.toString()
                            .split('CONNECT ')[1]
                            .split(' ')[0].split(':')[0];
        // console.log(serverAddress);
        
      } else {
         // Parsing HOST from HTTP
         serverAddress = data.toString()
                             .split('Host: ')[1].split('\r\n')[0];
         const firstLine = data.toString().split('\r\n')[0];
         path = firstLine.split(' ')[1];
        //  console.log(serverAddress);
      }
      var isFiltered = false;
      var using = null;
      Object.keys(serverCallbackMap).forEach((v)=>{
        // console.log("Proxy is: "+ v);
        // console.log(new RegExp(v).test(serverAddress));
        if (new RegExp(v).test(serverAddress)) {
          // It matches. We should run the handler.
          using = v;
          isFiltered = serverCallbackMap[v].filter({
            tls: isTLSConnection,
            host: serverAddress,
            path: path
          });
          // console.log(isFiltered);
        }
      })

      if (isFiltered) {
        serverCallbackMap[using].proxy()(serverCallbackMap[using].config, clientToProxySocket);
        return;
      }
      let proxyToServerSocket = net.createConnection({
        host: serverAddress,
        port: serverPort
      }, () => {
        // console.log('PROXY TO SERVER SET UP');
        
        if (isTLSConnection) {
          //Send Back OK to HTTPS CONNECT Request
          clientToProxySocket.write('HTTP/1.1 200 OK\r\n\n');
        } else {
          proxyToServerSocket.write(data);
        }
        // Piping the sockets
        clientToProxySocket.pipe(proxyToServerSocket);
        proxyToServerSocket.pipe(clientToProxySocket);
        
        proxyToServerSocket.on('error', (err) => {
          // console.log('PROXY TO SERVER may have disconnected.');
          // console.log(err);
        });
      });
      proxyToServerSocket.on('error', (e)=>{
        console.log(e);
      })
      clientToProxySocket.on('error', err => {
        console.log('CLIENT TO PROXY may have disconnected.');
      });
    });
  });
server.on('error', (err) => {
  console.log('SERVER ERROR');
  console.log(err);
});
server.on('close', () => {
  console.log('Client Disconnected');
});
server.listen(8126, () => {
  console.log('Server running at http://localhost:' + 8126);
});
//Source code below is for creating a mini server or a server that serves requests within memory.(or not)