All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openqa.selenium.server.InjectionHelper Maven / Gradle / Ivy

The newest version!
package org.openqa.selenium.server;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.mortbay.http.HttpRequest;
import org.mortbay.http.HttpResponse;
import org.mortbay.log.LogFactory;
import org.mortbay.util.IO;

public class InjectionHelper {
    static Log log = LogFactory.getLog(InjectionHelper.class);
    private static boolean failOnError = true;
    private static boolean browserSideLogEnabled = true;
    private static boolean INJECT_SCRIPT_TAGS = true;
    private static boolean tryToInjectInHead = false;
    private static String injectionHtml = "/core/scripts/injection.html";
    private static HashMap> jsStateInitializersBySessionId = new HashMap>();
    private static HashMap sessionIdToUniqueId = new HashMap();
    
    private static HashMap contentTransformations = new HashMap();
    private static List userJsInjectionFiles = new LinkedList();

    public static void setBrowserSideLogEnabled(boolean browserSideLogEnabled) {
        InjectionHelper.browserSideLogEnabled = browserSideLogEnabled; 
    }
    
    public static void setInjectScriptTags(boolean injectScriptTags) {
	    InjectionHelper.INJECT_SCRIPT_TAGS = injectScriptTags;
    }

    public static void setTryToInjectInHead(boolean tryToInjectInHead) {
        InjectionHelper.tryToInjectInHead = tryToInjectInHead;
    }

    public static void setInjectionHtml(String injectionHtml) {
        InjectionHelper.injectionHtml = injectionHtml;
    }

    public static void saveJsStateInitializer(String sessionId, String uniqueId, String jsVarName, String jsStateInitializer) {
        // when a new uniqueId is seen for a given sessionId, that means the page has 
        // reloaded and the old state should be discarded
        if (sessionIdToUniqueId.containsKey(sessionId) && !sessionIdToUniqueId.get(sessionId).equals(uniqueId)) {
            jsStateInitializersBySessionId.remove(sessionId);
            sessionIdToUniqueId.put(sessionId, uniqueId);
        }
        if (log.isDebugEnabled()) {
            log.debug("Saving JavaScript state for session " + sessionId + "/" + uniqueId + " " + jsVarName + ": " + jsStateInitializer); 
        }
        if (!jsStateInitializersBySessionId.containsKey(sessionId)) {
            jsStateInitializersBySessionId.put(sessionId, new HashMap());
        }
        HashMap h = jsStateInitializersBySessionId.get(sessionId);
        StringBuffer sb = new StringBuffer("if (uniqueId!='");
        sb.append(uniqueId)
            .append("') {")
            .append(jsStateInitializer)
            .append("}");
        h.put(jsVarName, sb.toString());
    }
    
    public static String restoreJsStateInitializer(String sessionId, String uniqueId) {
        if (!jsStateInitializersBySessionId.containsKey(sessionId)) {
            return "";
        }
        HashMap h = jsStateInitializersBySessionId.get(sessionId);
        if (h.isEmpty()) {
            return "";
        }
        StringBuffer sb = new StringBuffer();
        for (Map.Entry entry: h.entrySet()) {
            final String jsVarName = entry.getKey();
            final String jsStateInitializer = entry.getValue();
            sb.append(jsStateInitializer)
            .append('\n');
            if (log.isDebugEnabled()) {
                log.debug("Restoring JavaScript state for session " + sessionId + "/" + uniqueId 
                        + ": key=" + jsVarName + ": " + jsStateInitializer); 
            }
        }
        return sb.toString();
    }
    
    /**
     * re-read selenium js.  Don't maintain it indefinitely for now since then we would need to
     * restart the server to see changes.  Once the selenium js is firm, this should change.
     */
    public static void init() {
        String key = "__SELENIUM_JS__";
        
        StringBuffer sb = new StringBuffer();
        if (!INJECT_SCRIPT_TAGS) { // DGF experiment with using script tags
            try {
                appendFileContent(sb, "/core/scripts/xmlextras.js");
                appendFileContent(sb, "/core/lib/cssQuery/cssQuery-p.js");
                appendFileContent(sb, "/core/scripts/htmlutils.js");
                appendFileContent(sb, "/core/scripts/ui-element.js");
                appendFileContent(sb, "/core/scripts/selenium-browserdetect.js");
                appendFileContent(sb, "/core/scripts/selenium-browserbot.js");
                appendFileContent(sb, "/core/scripts/find_matching_child.js");
                appendFileContent(sb, "/core/scripts/selenium-api.js");
                appendFileContent(sb,
                        "/core/scripts/selenium-commandhandlers.js");
                appendFileContent(sb, "/core/scripts/selenium-executionloop.js");
                appendFileContent(sb, "/core/scripts/selenium-remoterunner.js");
                appendFileContent(sb, "/core/scripts/selenium-logging.js");
                appendFileContent(sb, "/core/xpath/util.js");
                appendFileContent(sb, "/core/xpath/xmltoken.js");
                appendFileContent(sb, "/core/xpath/dom.js");
                appendFileContent(sb, "/core/xpath/xpath.js");
                appendFileContent(sb, "/core/xpath/javascript-xpath-0.1.11.js");
                appendFileContent(sb, "/core/scripts/user-extensions.js");
            } catch (Exception e) {
                if (failOnError) {
                    throw new RuntimeException(e);
                }
                log.info("failOnError is false, ignoring problems: "
                        + e.getMessage());
                log.debug("Ignored exception", e);
            }
        }
        contentTransformations.put(key, sb.toString());
    }
    
    private static void writeScriptTags(OutputStream os) throws IOException {
    	// DGF script tags are SLOWER than regular injection! (remember, we disable the browser cache entirely)
        writeScriptTag(os, "/core/scripts/xmlextras.js");
        writeScriptTag(os, "/core/lib/cssQuery/cssQuery-p.js");
        writeScriptTag(os, "/core/scripts/htmlutils.js");
        writeScriptTag(os, "/core/scripts/ui-element.js");
        writeScriptTag(os, "/core/scripts/selenium-browserdetect.js");
        writeScriptTag(os, "/core/scripts/selenium-browserbot.js");
        writeScriptTag(os, "/core/scripts/find_matching_child.js");
        writeScriptTag(os, "/core/scripts/selenium-api.js");
        writeScriptTag(os, "/core/scripts/selenium-commandhandlers.js");
        writeScriptTag(os, "/core/scripts/selenium-executionloop.js");
        writeScriptTag(os, "/core/scripts/selenium-remoterunner.js");
        writeScriptTag(os, "/core/scripts/selenium-logging.js");
        writeScriptTag(os, "/core/xpath/util.js");
        writeScriptTag(os, "/core/xpath/xmltoken.js");
        writeScriptTag(os, "/core/xpath/dom.js");
        writeScriptTag(os, "/core/xpath/xpath.js");
        writeScriptTag(os, "/core/xpath/javascript-xpath-0.1.11.js");
        writeScriptTag(os, "/core/scripts/user-extensions.js");
    }
    
    private static void writeScriptTag(OutputStream os, String url) throws IOException {
        os.write("\n".getBytes());
    }
    
	private static void appendFileContent(StringBuffer sb, String url) throws IOException {
        InputStream in = new ClassPathResource(url).getInputStream();
        if (in==null) {
            if (!url.endsWith("user-extensions.js")) {
                throw new RuntimeException("couldn't find " + url);
            }
        }
        else {
            byte[] buf = new byte[8192];
            while (true) {
                int len = in.read(buf, 0, 8192);
                if (len==-1) {
                    break;
                }
                sb.append(new String(buf, 0, len));
            }
        }
    }

    public static long injectJavaScript(HttpRequest request, HttpResponse response, InputStream in, OutputStream out, String debugURL) throws IOException {
	    if (!contentTransformations.containsKey("__SELENIUM_JS__")) {
	        init();   
        }
        
        int len = 102400;
        byte[] buf = new byte[len];
        len = readStream(in, buf, len);
        if (len == -1) {
            return -1;
        }
        int lengthOfBOM = getBOMLength(buf); 
        String data = new String(buf, lengthOfBOM, len);

        boolean isKnownToBeHtml = HtmlIdentifier.shouldBeInjected(request.getPath(), response.getContentType(), data);

        String url = response.getHttpRequest().getRequestURL().toString();
        if (debugURL.equals(url)) {
            log.info("debug URL seen");
        }
       
        if (!isKnownToBeHtml) {
            out.write(buf, 0, len);
        }
//        else if (lengthOfBOM>0) {
//            out.write(buf, 0, lengthOfBOM);
//        }
        String sessionId = SeleniumDriverResourceHandler.getLastSessionId();

        long bytesCopied = len;

        if (log.isDebugEnabled()) {
            log.debug(url + " (InjectionHelper looking)");
        }
        if (!isKnownToBeHtml) {
            bytesCopied += ModifiedIO.copy(in, out);
        }
        else {
            if (log.isDebugEnabled()) {
                log.debug("injecting...");
            }
            response.removeField("Content-Length"); // added js will make it wrong, lead to page getting truncated
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            if (INJECT_SCRIPT_TAGS) {
            	writeScriptTags(baos);
            }
            InputStream jsIn = new ClassPathResource(InjectionHelper.injectionHtml).getInputStream();
            contentTransformations.put("@SESSION_ID@", sessionId);
            
            writeDataWithUserTransformations("", jsIn, baos);
            jsIn.close();
            baos.write(setSomeJsVars(sessionId));
            for (String filename : userJsInjectionFiles) {
                jsIn = new FileInputStream(filename);
                IO.copy(jsIn, baos); 
            }

            int headIndex;
            if (tryToInjectInHead) {
                headIndex = data.toLowerCase().indexOf("");
            } else {
                headIndex = -1;
            }

            if (headIndex != -1) {
                data = data.substring(0, headIndex + 6) + baos.toString() + data.substring(headIndex + 6);
            } else {
                data = baos.toString() + data;
            }

            bytesCopied += writeDataWithUserTransformations(data, in, out);
        }

        return bytesCopied;
    }

    private static int getBOMLength(byte[] buf) {
        if ((buf!=null) && (buf.length>=3) && (buf[0]==(byte) -17) && (buf[1]==(byte) -69) && (buf[2]==(byte) -65)) {
// jeez, what was that, you may be asking?  This comparison is quite wacky.  When I look at the same data hexdumped 
//            from a file on disk, the bytes are EF BB BF,  so I think I could be comparing against 0xef, 0xbb, and 0xbf.
//            But that doesn't work.  Here are some interesting evaluations from the Display view in my eclipse:
//
//            buf[0]
//                 (byte) -17
//            buf[1]
//                 (byte) -69
//            buf[2]
//                 (byte) -65
//            buf[3]
//                 (byte) 10
//            (int)(new String(buf)).charAt(0)
//                 (int) 239
//            (int)(new String(buf)).charAt(1)
//                 (int) 187
//            (int)(new String(buf)).charAt(2)
//                 (int) 191
//            (new String(buf)).charAt(2)
//                 (char) ¿
//            (int)(new String(buf)).charAt(3)
//                 (int) 10
//
//            what I would really like would be to recognize any BOM (cf http://en.wikipedia.org/wiki/Byte_Order_Mark).   I could easily set up
//            the appropriate comparisons if I knew how to translate from the hex form to some appropriate analogue for a Java comparison.
            return 3;
        }
        return 0; // there was no BOM
    }

    /**
     * read bufLen bytes into buf (unless EOF is seen first) from in.
     * @param in
     * @param buf
     * @param bufLen
     * @return number of bytes read
     * @throws IOException
     */
    private static int readStream(InputStream in, byte[] buf, int bufLen) throws IOException {
        int offset = 0;
        do {
            int bytesRead = in.read(buf, offset, bufLen - offset);
            if (bytesRead==-1) {
                break;
            }
            offset += bytesRead;
        } while (offset < bufLen);
        int bytesReadTotal = offset;
        return bytesReadTotal;
    }

    private static long writeDataWithUserTransformations(String data, InputStream in, OutputStream out) throws IOException {
        long bytesWritten = 0;
        byte[] buf = new byte[8192];
        while (true) {
            for (String beforeRegexp : contentTransformations.keySet()) {
                String after = contentTransformations.get(beforeRegexp);
                if (after==null) {
                    log.warn("no transformation seen for key " + beforeRegexp);
                }
                else {
                    try {
                        data = data.replaceAll(beforeRegexp, after);
                    }
                    catch (IllegalArgumentException e) {
                        // bad regexp or bad back ref in the 'after'.  
                        // Do a straight substitution instead.
                        // (This logic needed for injection.html's __SELENIUM_JS__
                        // replacement to work.)
                        data = data.replace(beforeRegexp, after);       
                    }
                }
            }
            out.write(data.getBytes());
            int len = in.read(buf);
            if (len == -1) {
                break;
            }
            bytesWritten += len;
            data = new String(buf, 0, len);
        }

        return bytesWritten;
    }

    private static byte[] setSomeJsVars(String sessionId) {
        StringBuffer moreJs = new StringBuffer();
        if (InjectionHelper.browserSideLogEnabled) {
            moreJs.append("debugMode = true;\n");
        }
        moreJs.append("injectedSessionId = \"")
            .append(sessionId)
            .append("\";\n");
        return makeJsChunk(moreJs.toString());
    }

    // This logic may be useful on some browsers which don't support load listeners.
//    private static String usurpOnUnloadHook(String data, String string) {
//        Pattern framesetAreaRegexp = Pattern.compile("(<\\s*frameset.*?>)", Pattern.CASE_INSENSITIVE);
//        Matcher framesetMatcher = framesetAreaRegexp.matcher(data);
//        if (!framesetMatcher.find()) {
//            System.out.println("WARNING: looked like a frameset, but couldn't retrieve the frameset area");
//            return data;
//        }
//        String onloadRoutine = "selenium_frameRunTest()";
//        String frameSetText = framesetMatcher.group(1);
//        Pattern onloadRegexp = Pattern.compile("onload='(.*?)'", Pattern.CASE_INSENSITIVE);
//        Matcher onloadMatcher = onloadRegexp.matcher(frameSetText);
//        if (!onloadMatcher.find()) {
//            onloadRegexp = Pattern.compile("onload=\"(.*?)\"", Pattern.CASE_INSENSITIVE); // try double quotes
//            onloadMatcher = onloadRegexp.matcher(frameSetText);
//        }
//        if (onloadMatcher.find()) {
//            String oldOnloadRoutine = onloadMatcher.group(1);
//            frameSetText = onloadMatcher.replaceFirst("");
//            String escapedOldOnloadRoutine = null;
//            try {
//                escapedOldOnloadRoutine = URLEncoder.encode(oldOnloadRoutine, "UTF-8");
//            } catch (UnsupportedEncodingException e) {
//                throw new RuntimeException("could not handle " + oldOnloadRoutine + ": " + e);
//            }
//            onloadRoutine = "selenium_frameRunTest(unescape('" + escapedOldOnloadRoutine  + "'))";
//        }
//        
//        // either there was no existing onload, or it's been stripped out
//        Pattern framesetTagRegexp = Pattern.compile("<\\s*frameset", Pattern.CASE_INSENSITIVE);
//        frameSetText = framesetTagRegexp.matcher(frameSetText).replaceFirst("\n");
        sb.append(js)
        .append("\n\n");
        return sb.toString().getBytes();
    }

    public static boolean addUserContentTransformation(String before, String after ) {
        contentTransformations.put(before, after);
        return true;
    }
    
    public static boolean addUserJsInjectionFile(String fileName) {
        File f = new File(fileName);
        if (!f.canRead()) {
            log.error("cannot read user JavaScript injection file " + fileName);
            return false;
        }
        userJsInjectionFiles.add(fileName);
        return true;
    }

    public static boolean userContentTransformationsExist() {
        return !contentTransformations.isEmpty();
    }

    public static boolean userJsInjectionsExist() {
        return !userJsInjectionFiles.isEmpty();
    }

    public static boolean isFailOnError() {
        return failOnError;
    }

    public static void setFailOnError(boolean failOnError) {
        InjectionHelper.failOnError = failOnError;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy