// IUT d'Orsay 2002/2003 - reseaux 2eme annee
// application pour tracer des sessions d'un protocole d'application
// durr@lri.fr version 2

/* 
                  Cette application permet d'afficher tout le trafic
        entre un serveur et un client pour pratiquement tout protocole
        d'application.  Elle écoute un numéro de port pour la
        connexion vers le client et ouvre un connexion vers le
        serveur.

	          L'ensemble des messages échangés sera affiché en
        format HTML.  Les données du client vers le serveur seront
        affichées en rouge, les données du serveur vers le client en
        bleu.
	
                  Si un des acteurs ferme la connexion, alors cette
        application ferme l'autre connexion aussi et recommence à
        accepter de nouvelles connexions.  

------- Exemple de session sans ManInTheMiddle

	serveur                                             client
	+----+                                              +--------+
        | -- |                                              | +----+ |
        | == |<-------------------------------------------- | |    | |
        |  o | port 80                                      | +----+ |
        |    |                                              |        |
        |    |                                              |    === |
        +----+                                              +--------+

------- Exemple de session avec ManInTheMiddle

	serveur    java ManInTheMiddle 8080 serveur 80      client
	+----+                +-----+------+                +--------+
        | -- |                |save | clear|                | +----+ |
        | == |                +-----+------+                | |    | |
        |  o | <------------- | GET / HTTP.|                | +----+ |
        |    | port 80        |            | <------------- |        |
        |    |                | 200 ok..   | port 8080      |    === |
        +----+                +------------+                +--------+
                                                      le client doit etre
                                                      configure pour se 
                                                      connecter sur le port
                                                      8080 de la machine
                                                      executant ManInTheMiddle
*/

import java.io.*;
import java.net.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.html.*;

/** Copie d'un flot d'entree vers un flot de sortie.  Une copie
    sera envoye vers le CopyListener en precisant la couleur donnee
    initialement.  Lors ce que le flot d'entree est ferme, le
    copyListener est averti, en utilisant le nom de l'origine 
    donne initialement.
*/
class Copy extends Thread {
    CopyListener  listen;
    String        color;
    InputStream   in;
    OutputStream  out;
    String        origin;

    public Copy(CopyListener listen, String color,
		InputStream in, OutputStream out, String origin) {
	this.listen = listen;
	this.color  = color;
	this.in     = in;
	this.out    = out;
	this.origin = origin;
    }

    /** Le read est bloquant, c'est pourquoi il faut utiliser
        un Thread.  On essaye de lire le plus de caracteres
	d'un seul coup, pour diminuer le nombre de mises a jour
	de l'affichage a l'ecran.
	*/
    public void run() {
	byte[] b = new byte[1024];
	int len;
	try {
	    while ((len = in.read(b))!=-1) {
		listen.print(color, b, len);
		out.write(b, 0, len);
	    }
	} catch (IOException e) {
	    listen.close();
	    return;
	}
	listen.close(origin);
    }
}

/** Un CopyListener peut afficher une copie avec print et etre
    averti de la fermeture des flots, soit avec soit sans message.
    */
interface CopyListener {
    public void close(String origin); 
    public void close(); 
    public void print(String color, byte b[], int len);
}

class ManInTheMiddle implements CopyListener {
    static String help = 
	"Usage: java ManInTheMiddle client_port server server_port\n"+
	"par exemple :\n"+
	"     client_port = votre numéro de login\n"+
	"     server     = net-gw\n"+
	"     server_port = 80\n";

    Socket client, server;

    public void close() {
	try {
	    client.close();
	    server.close();
	} catch (IOException e) {}
    }

    // le document HTML cree est dans doc, et affiche par text.
    String      doc = "";
    JEditorPane text;

    // traduit un caractere en HTML
    public String translation(byte b) {
	switch (b) {
	case '\n':return "<br>";
	case '\r':return "<!cr>"; // pour éviter les ^M en fin de ligne
	case '<': return "&lt;";
	case '>': return "&gt;";
	case '&': return "&amp;";
	case ' ': return "&nbsp;";
	}
	if (0<=b && b<32)
	    return "<b>^"+(char)('@'+b)+"</b>";
	if (b<0)
	    return"<b>#"+Integer.toHexString(256+b)+"</b>";
	else
	    return ""+(char)b;
    }
    
    synchronized public void print(String color, byte b[], int len) {
	String tmp = color;
	for (int i=0; i<len; i++)
	    tmp+=translation(b[i]);
	concat(tmp);
    }

    public void concat(String html) {
	try {
	    doc += html;
	    text.setText(doc);
	} catch (Exception e) {}
    }

    synchronized
	public void close(String origin) {
	concat("<h3>Connexion ferm&eacute;e par le "+origin+"</h3>");
	close();
    }

    public ManInTheMiddle(int client_port, String nom_server, int server_port) 
    throws IOException {
	ServerSocket s    = new ServerSocket(client_port);
	
	// créer l'interface
	JFrame frame     = new JFrame("ManInTheMiddle");
	text             = new JEditorPane("text/html", doc);
	JButton save     = new JButton("Save as html");
	JButton clear    = new JButton("Clear");
	Panel   panel    = new Panel(new GridLayout(1,2));
	save.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
		    try {
			JFileChooser file = new JFileChooser();
			int returnVal = file.showSaveDialog(text);
			if(returnVal == JFileChooser.APPROVE_OPTION) {
			    FileWriter fout = new FileWriter(file.getSelectedFile());
			    fout.write(doc,0,doc.length());
			    fout.close();
//			    System.exit(0);
			}
		    } catch (IOException ex) {
			System.err.println(ex);
		    }
		}});

	clear.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
		    try {
				text.setText(doc = "");
		    } catch (Exception ex) {
			System.err.println(ex);
		    }
		}
	});

	frame.addWindowListener(new WindowAdapter() {
		public void windowClosing(WindowEvent e) {
		    System.exit(0);
		}
	    });

	text.setEditable(false);
	frame.setSize(450,300);
	frame.setLocation(100,100);
	frame.getContentPane().setLayout(new BorderLayout());
	panel.add(save);
	panel.add(clear);
	frame.getContentPane().add(panel, 
				   BorderLayout.NORTH);
	frame.getContentPane().add(new JScrollPane(text), 
				   BorderLayout.CENTER);
	frame.show();

	while (true) {
	    // attend qu'un nouveau client se connecte
	    concat("<h3>&eacute;coute sur le port "+client_port+"</h3>");
	    client = s.accept();
	    concat("<h3>se connecte sur "+nom_server+":"+server_port+"</h3>");
	    server = new Socket(nom_server, server_port);
	    Copy client_copy = new Copy(this, "<font color=\"red\">", 
					  client.getInputStream(), 
					  server.getOutputStream(), "client");

	    Copy server_copy = new Copy(this, "<font color=\"blue\">", 
					server.getInputStream(), 
					client.getOutputStream(), "server");
	    
	    server_copy.start();
	    client_copy.start();
	    
	    try{
		server_copy.join();
	    } catch (InterruptedException e) {}
	    
	    try{
		client_copy.join();
	    } catch (InterruptedException e) {}
	}
    }

    public static void main(String args[]) throws Exception {
	if (args.length!=3) {
	    System.err.println(help);
            System.exit(1);
	}
	int client_port   = Integer.parseInt(args[0]);
	String nom_server = args[1];
	int server_port   = Integer.parseInt(args[2]);

	new ManInTheMiddle(client_port, nom_server, server_port);
    }
}
