/*## (c) SECURITY EXPLORATIONS    2018 poland                                #*/
/*##     http://www.security-explorations.com                                #*/

/* THIS SOFTWARE IS PROTECTED BY DOMESTIC AND INTERNATIONAL COPYRIGHT LAWS    */
/* UNAUTHORISED COPYING OF THIS SOFTWARE IN EITHER SOURCE OR BINARY FORM IS   */
/* EXPRESSLY FORBIDDEN. ANY USE, INCLUDING THE REPRODUCTION, MODIFICATION,    */
/* DISTRIBUTION, TRANSMISSION, RE-PUBLICATION, STORAGE OR DISPLAY OF ANY      */
/* PART OF THE SOFTWARE, FOR COMMERCIAL OR ANY OTHER PURPOSES REQUIRES A      */
/* VALID LICENSE FROM THE COPYRIGHT HOLDER.                                   */

/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS    */
/* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,*/
/* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    */
/* SECURITY EXPLORATIONS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, */
/* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF  */
/* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE     */
/* SOFTWARE.                                                                  */

import java.lang.*;
import java.util.*;
import java.io.*;
import java.lang.reflect.*;
import tv.osmosys.net.*;
import com.adb.xion.net.*;
import com.adb.xion.net.www.content.*;

public class HTTPSniffer {
 public static class URIHandler implements URIConnectionHandler {
  public URIConnection createConnection(URI uri) {
   URIConnection conn=null;

   try {
    String scheme=uri.getScheme();

    if (scheme==null) return null;

    scheme=scheme.toLowerCase();
 

    if (scheme.equals("http")||scheme.equals("https")) {
     String mappeduri=(String)urimap.get(uri.toExternalForm());
     if (mappeduri!=null) {
      uri=new URI(mappeduri); 

      if (uri.getScheme().equals("flash")) {
       conn=URIConnectionFactory.getInstance().createURIConnection(uri);
      } else {
       conn=new ProxyHTTPConnection(uri);
      }
     } else {
      conn=new ProxyHTTPConnection(uri);
     }
    } else {
     conn=new com.adb.xion.net.nativehttp.URIConnection(uri);
    }   
   } catch(Throwable t) {}

   return conn;
  }

  public static URIConnection nativeConnection(URI uri) throws URIConnectionException {
   String scheme=uri.getScheme();

   if (scheme==null) return null;

   scheme=scheme.toLowerCase();
    
   return new com.adb.xion.net.nativehttp.URIConnection(uri);
  }
 }

 public static class Downloader implements DaemonThread.Code {
  private static LinkedList queue;

  public boolean started;

  public Downloader() {
   queue=new LinkedList();
  }

  private String uri2file(URI uri) {
   if (uri.getQuery()==null) {
    String host=uri.getAuthority();
    String path=uri.getPath();

    String name=HTTPSnifferDefs.SITES_DIR+"/"+host+"/";

    if (!path.equals("/")) {
     name+=path;
    }

    if (name.endsWith("/")) {
     name+=HTTPSnifferDefs.HTML_INDEX;
    }

    int i=name.lastIndexOf('/');
    String dirname=name.substring(0, i);
  
    new File(dirname).mkdirs();
    return name;
   } else return null;
  }

  public void download_content(URI uri) throws Throwable {
   try {
    String filename=uri2file(uri);

    if (filename!=null) {
     byte data[]=ContentFetcher.fetch(uri);

     if (data!=null) {
      FileOutputStream fos=new FileOutputStream(filename,false);
      fos.write(data);
      fos.close();
     }
    }
   } catch(Throwable t) {}
  }

  public void run(Object args[]) {
   try {
    started=true;

    while(drunning) {
     synchronized(queue) {
      while(queue.isEmpty()) queue.wait();
       
      URI uri=(URI)queue.removeFirst();

      download_content(uri);
     }
    }
   } catch(Throwable t) {}
  }
  
  public static void download(URI uri) {
   synchronized(queue) {
    queue.addLast(uri);   
    queue.notify();
   }
  }

  public static void stop() {
   drunning=false; 
   queue.notify();
  }

 }

 private static Downloader downloader;
 private static ArrayList org_handlers;
 private static URIHandler uh;

 private static boolean initialized;
 private static boolean drunning;
 private static boolean httpsniffer;
 private static boolean logheaders;
 private static boolean logcontent;

 private static Hashtable urimap;

 private static Field freqhdr;
 private static Field fresphdr;

 private static Method getall;

 static {
  try {
   org_handlers=null;
   uh=null;

   initialized=false;
   httpsniffer=false;
   drunning=false;

   logheaders=false;
   logcontent=false;

   new File(HTTPSnifferDefs.SNIFF_DIR).mkdirs();
   new File(HTTPSnifferDefs.SITES_DIR).mkdirs();

   Class c=Class.forName("tv.osmosys.net.HttpRequest");
   freqhdr=c.getDeclaredField("requestHdrs");
   freqhdr.setAccessible(true);

   fresphdr=c.getDeclaredField("responseHdrs");
   fresphdr.setAccessible(true);
 
   c=Class.forName("tv.osmosys.net.Headers");
   getall=c.getDeclaredMethod("getAll",new Class[0]);
   getall.setAccessible(true);   

   urimap=new Hashtable();

   start_downloader();

   uh=new URIHandler();  

   c=Class.forName("com.adb.xion.net.URIConnectionFactory");
   Field f=c.getDeclaredField("handlers");
   f.setAccessible(true);
 
   org_handlers=(ArrayList)f.get(null);

   f.set(null,new ArrayList());

   URIConnectionFactory.getInstance().registerURIConnectionHandler(uh);

   initialized=true;
  } catch(Throwable t) {}
 }

 public static void start_downloader() {
  try {
   drunning=true;

   downloader=new Downloader();
   DaemonThread.start(downloader,null);

   while(!downloader.started) Utils.sleep(10); 
  } catch(Throwable t) {}
 }

 public static void downloader_stop() {
  Downloader.stop();
 }

 public static boolean start(boolean hdrlog,boolean contentlog) {
  boolean res=false;

  if ((!initialized)||(httpsniffer)) return res;

  httpsniffer=true;

  logheaders=hdrlog;
  logcontent=contentlog;

  return true;
 }

 public static boolean stop() {
  boolean res=false;

  if ((!initialized)||(!httpsniffer)) return res;

  httpsniffer=false;

  logheaders=false;
  logcontent=false;

  return true;
 }

 public static boolean cleanup() {
  boolean res=false;

  if (org_handlers==null) return res;

  try {
   httpsniffer=false;

   Class c=Class.forName("com.adb.xion.net.URIConnectionFactory");
   Field f=c.getDeclaredField("handlers");
   f.setAccessible(true);
 
   f.set(null,org_handlers);

   if (drunning) {
    Downloader.stop();
   }

   org_handlers=null;
   uh=null;

   logheaders=false;
   logcontent=false;

   initialized=false;

   res=true;
  } catch(Throwable t) {}

  return res;
 }

 public static void enable() {
  httpsniffer=true;
 }

 public static void disable() {
  httpsniffer=false;
 }

 public static String[][] get_reqhdrs(HttpRequest httpreq) {
  String shdrs[][]=null;

  try {
   if (httpreq!=null) {
    Object hdrs=freqhdr.get(httpreq);
    if (hdrs!=null) {
     shdrs=(String[][])getall.invoke(hdrs,new Object[0]);
    }
   }
  } catch(Throwable t) {}

  return shdrs;
 }

 public static String[][] get_resphdrs(HttpRequest httpreq) {
  String shdrs[][]=null;

  try {
   if (httpreq!=null) {
    Object hdrs=fresphdr.get(httpreq);
    if (hdrs!=null) {
     shdrs=(String[][])getall.invoke(hdrs,new Object[0]);
    }
   }
  } catch(Throwable t) {}

  return shdrs;
 }

 private static void write_log(PrintWriter file,String line) {
  file.println(line);
 }

 private static void write_hdrs(PrintWriter file,String[][] hdrs) {
  for(int i=0;i<hdrs.length;i++) {
   String stab[]=hdrs[i];

   String name=stab[0];
   String val=stab[1];

   String s=Utils.padded_string(name,HTTPSnifferDefs.HDR_PAD)+" = "+val;
      
   write_log(file,s);
  }
 }

 private static String get_time() {
  Calendar cal=Calendar.getInstance();
  return cal.getTime().toString();
 }

 public static String mapuri(String srcuri,String dsturi) {
  String olduri=null;

  synchronized(urimap) {
   olduri=(String)urimap.get(srcuri);

   if (dsturi!=null) {
    urimap.put(srcuri,dsturi);
   } else {
    urimap.remove(srcuri);
   }
  }

  return olduri;
 }

 public static void print_urimap() throws Throwable {
  synchronized(urimap) {
   Enumeration e=urimap.keys();
   int idx=0;

   while(e.hasMoreElements()) {
    String srcuri=(String)e.nextElement();
    String dsturi=(String)urimap.get(srcuri);

    String s="["+Utils.hex_value(idx,2)+"] "+Utils.padded_string(srcuri,HTTPSnifferDefs.URI_PAD)+" -> "+dsturi;
    Output.text(s);
    idx++;
   }
  }
 }

 public static void log(URI uri,HttpRequest httpreq,byte postdata[]) {
  if (!httpsniffer) return;

  try {
   FileWriter fw=new FileWriter(HTTPSnifferDefs.LOG_FILE,true);
   PrintWriter pw=new PrintWriter(fw);

   String s="## "+Utils.padded_string(uri.getHost(),HTTPSnifferDefs.HOST_PAD);

   s+=" - - ["+Utils.padded_string(get_time(),HTTPSnifferDefs.TIME_PAD)+"]";
   s+=" \""+httpreq.getRequestMethod()+" "+uri.toRequestURI()+"\"";
   s+=" "+httpreq.getResponseCode()+" "+httpreq.getContentLength();

   write_log(pw,s);

   if (logheaders) {
    if (httpreq.getRequestMethod().equals("POST")) {
     if (postdata!=null) {     
      write_log(pw,new String(postdata));
     }
    }

    String[][] reqhdrs=get_reqhdrs(httpreq);
    write_hdrs(pw,reqhdrs);

    write_log(pw,"<--");

    String[][] resphdrs=get_resphdrs(httpreq);
    write_hdrs(pw,resphdrs);
   }   

   pw.flush();

   pw.close();
   fw.close();

   if (logcontent) {
    if (httpreq.getRequestMethod().equals("GET")&&(httpreq.getResponseCode()==200)) {
     Downloader.download(uri);
    }
   }
  } catch(Throwable t) {}
 }
}
