/*
 * Scone - The Web Enhancement Framework
 * Copyright (C) 2009 Harald Weinreich, Volkert Buchmann, Frank Wollenweber, Torsten Haß
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package scone.proxy;


import java.io.IOException;
import java.net.URL;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;

import scone.util.tokenstream.HtmlTagToken;
import scone.util.tokenstream.SconePipe;
import scone.util.tokenstream.Token;
import scone.util.tokenstream.TokenInputStream;
import scone.util.tokenstream.TokenOutputStream;

import com.ibm.wbi.RequestEvent;
import com.ibm.wbi.RequestRejectedException;
import com.ibm.wbi.protocol.http.DocumentInfo;
import com.ibm.wbi.protocol.http.HttpRequestEditor;
import com.ibm.wbi.protocol.http.HttpRequestHeader;
import com.ibm.wbi.protocol.http.HttpResponseHeader;


/**
 * This plugin enables the proxy to run as a web server
 */

public class ServerSide extends scone.Plugin {

    public static final String COPYRIGHT = "(C) Harald Weinreich & Volkert Buchmann";

    // the name and port under which WBI is reacheable
    String myName = null;
    String myPortString = "0";
    int myPort = 0;
    
    // the name and port of the server we mimic
    String remoteName = null;
    String remotePortString = "0";
    int remotePort = 0;
    
    int requestPriority = 0;
    int responsePriority = 0;
    
    Hashtable ignoreExtensions = new Hashtable();
    Hashtable ignoreTags = new Hashtable();

    public int getRequirements() {
        return NOREQUIREMENTS;
    }
	
    /**
     * setup the plugin
     */
    public void init() {
        try {
            // read out properties
			
            String dummy = getProperties().get("Ignore these types").toLowerCase();
            StringTokenizer st = new StringTokenizer(dummy);

            while (st.hasMoreTokens()) {
                ignoreExtensions.put(st.nextToken(), "i");
            }
			
            dummy = getProperties().get("Ignore these tags").toLowerCase();
            st = new StringTokenizer(dummy);
            while (st.hasMoreTokens()) {
                ignoreTags.put(st.nextToken(), "i");
            }
           			
            myName = getProperties().get("local host");
            myPortString = getProperties().get("local port");
            myPort = Integer.parseInt(myPortString);
			
            remoteName = getProperties().get("remote host");
            remotePortString = getProperties().get("remote port");
            remotePort = Integer.parseInt(remotePortString);
			
            requestPriority = Integer.parseInt(getProperties().get("Request Editor Priority"));
            responsePriority = Integer.parseInt(getProperties().get("Response Editor Priority"));
	
            // this Editor will rewrite the URLs in the request
            RewriteRequestEditor rewriteRequestEditor = new RewriteRequestEditor();

            rewriteRequestEditor.setup("ServerSideRewriteRequestEditor", "%true%", requestPriority); 
            addMeg(rewriteRequestEditor);
		    
            // this Editor will rewrite the URLs in the response
            RewriteResponseEditor rewriteResponseEditor = new RewriteResponseEditor();

            rewriteResponseEditor.setup("ServerSideRewriteResponseEditor ", HTDOCCONDITION, responsePriority);
            addMeg(rewriteResponseEditor);
		    
        } catch (Exception exc) {
            System.err.println(exc);
        }
    }
   
    /**
     * This class rewrites URLs in the client's request
     */
    class RewriteRequestEditor extends HttpRequestEditor {

        /**
         * handle the request
         */
        public void handleRequest(RequestEvent requestEvent)throws RequestRejectedException, IOException {
            // quit if no remoteName is specified!
            if (remoteName == null || remoteName.length() == 0) {
                throw new RequestRejectedException();
            } else {

                DocumentInfo documentInfo = (DocumentInfo) requestEvent.getRequestInfo();
                HttpRequestHeader httpRequest = documentInfo.getHttpRequestHeader();
            
                // Get request URL
                String requestUrl = httpRequest.getUrl();

                // Server was missing: look for Server in Host: parameter
                if (!requestUrl.startsWith("http://")
                        && httpRequest.get("Host") != null) {
                    requestUrl = "http://" + httpRequest.get("Host")
                            + requestUrl;
                }
            
                // Try to rewrite the request URL
                try {
                    URL reqUrl = new URL(requestUrl);

                    // do we have to change anything?
                    if (!(reqUrl.getHost().equals(myName)
                            && (myPort == 0 || myPort == reqUrl.getPort()))) {
                        throw new RequestRejectedException();
                    }
                    // are any WBI ressources requested? then we do not want to interfer!
                    if (reqUrl.getFile().startsWith("/_wbi")) {
                        throw new RequestRejectedException();
                    }
                    URL newUrl = null;

                    if (remotePort == 0) {  // Do not change port...
                        newUrl = new URL(reqUrl.getProtocol(), remoteName, reqUrl.getPort(), reqUrl.getFile() + (reqUrl.getRef() != null ? "#" + reqUrl.getRef() : ""));
                    } else {  // Change Server and Port
                        newUrl = new URL(reqUrl.getProtocol(), remoteName, remotePort, reqUrl.getFile() + (reqUrl.getRef() != null ? "#" + reqUrl.getRef() : ""));
                    }
		
                    // Set Request to new Url
                    httpRequest.setUrl(newUrl.toString());

                } catch (Exception exception) {}

                return;
            }
        }
    }


    /**
     * this class rewrites URLs in the server's response
     */

    class RewriteResponseEditor extends HtmlTokenEditor {

        Vector atts = new Vector();
   
        public RewriteResponseEditor() {
            atts.addElement("href");
            atts.addElement("src");
            atts.addElement("background");
            atts.addElement("action");
            atts.addElement("lowsrc");
            atts.addElement("data");
            // in applets code und codebase!
        }

        public void handleRequest(SconePipe pipe) {
   
            DocumentInfo documentInfo = (DocumentInfo) pipe.getRequestEvent().getRequestInfo();

            // do some work on the response header
            HttpResponseHeader httpResponse = documentInfo.getHttpResponseHeader();
            // take care of relocations!
            String reloc = rewriteUrl(httpResponse.get("location"));

            if (reloc != null) {
                httpResponse.set("location", reloc);
            }

            // do some evil work on the request header
            HttpRequestHeader httpRequest = documentInfo.getHttpRequestHeader();

            reloc = rewriteUrl(httpRequest.getUrl());
            if (reloc != null) {
                httpRequest.setUrl(reloc);
            }

            Token token = null;
            HtmlTagToken htmlTag = null;
	 
            String url = null;
            String tag = null;

            try {
                // get the token streams
                TokenInputStream in = pipe.getTokenInputStream();
                TokenOutputStream out = pipe.getTokenOutputStream();

                do {
                    // so let's have a look at the tokens :-)
                    token = in.read();
		
                    // if it's null, it is the last one
                    if (token == null) {
                        break;
                    }      
		
                    // if it's a tag, some action is required!
                    if (token instanceof HtmlTagToken) {
                        htmlTag = (HtmlTagToken) token;
                        tag = htmlTag.getTagString().toLowerCase();
		    
                        // ignore tags if they are in the ignore list
                        if (ignoreTags.get(tag) == null) {
		    	
                            // do some generic stuff you might find in a variety of tags
                            url = rewriteUrl(htmlTag.getParam("href"));
                            if (url != null) {
                                htmlTag.setParam("href", url);
                            }		
				    
                            url = rewriteUrl(htmlTag.getParam("src"));
                            if (url != null) {
                                htmlTag.setParam("src", url);
                            }
				    
                            url = rewriteUrl(htmlTag.getParam("background"));
                            if (url != null) {
                                htmlTag.setParam("background", url);
                            }
				    
                            // form action tag...
                            url = rewriteUrl(htmlTag.getParam("action"));
                            if (url != null) {
                                htmlTag.setParam("action", url);
                            }
				    
                            // meta tags...
                            if (tag.equals("meta")) {
                                String content = htmlTag.getParam("content");

                                if (content != null) {
                                    StringTokenizer stringTokenizer = new StringTokenizer(content, ";");
                                    StringBuffer stringBuffer = new StringBuffer();

                                    while (stringTokenizer.hasMoreElements()) {
                                        url = stringTokenizer.nextToken().trim();
                                        if (url.length() > 3
                                                && url.substring(0, 3).toLowerCase().equals("url")) {
                                            stringBuffer.append("URL=" + rewriteUrl(url.substring(4)) + "; ");
                                        } else {
                                            stringBuffer.append(url + "; ");
                                        }
                                    }
                                    htmlTag.setParam("content", stringBuffer.toString());
                                }
                            }
				    
                            // option            
                            if (tag.equals("option")) {
                                String value = htmlTag.getParam("value");

                                if (value != null && value.startsWith("http://")) {
                                    htmlTag.setParam("value", rewriteUrl(value));
                                }
                            }
                        }
                    }
                    // htmlEditor.writeLastToken();
                    out.write(token);
                }while (true);
                in.close();
                out.close();
            } catch (Exception exc) {
                exc.printStackTrace();
            }
        }

        // rewrites a URL
        public String rewriteUrl(String url) {
            if (url == null) {
                return null;
            }
	
            // work only with absolute URLs
            if (url.startsWith("http://")) {
                String protocol = "http://";
                String server = null;
                String rest = url.substring(7);
                int serverlen = rest.indexOf("/");

                // extract the server from the url
                if (serverlen == -1) {
                    server = rest;
                    rest = "/";
                } else {
                    server = rest.substring(0, serverlen);
                    rest = rest.substring(serverlen);
                }
	    
                // Set new server...
                if (remotePort == 0) {
                    if (server.equals(remoteName)
                            || server.equals(remoteName + ":80")) {
                        url = protocol + myName + rest;
                    }
                } else   // with port...
                if (server.equals(remoteName)
                        || server.equals(remoteName + ":" + remotePort)) {
                    url = protocol + myName + ":" + myPort + rest;
                }
            } else { 
                return null;
            }
            return url; 
        }
    }

}
