
org.openqa.selenium.server.InjectionHelper Maven / Gradle / Ivy
Go to download
Selenium automates browsers. That's it! What you do with that power is entirely up to you.
package org.openqa.selenium.server;
import org.openqa.jetty.http.HttpRequest;
import org.openqa.jetty.http.HttpResponse;
import org.openqa.jetty.util.IO;
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 java.util.logging.Level;
import java.util.logging.Logger;
public class InjectionHelper {
static Logger log = Logger.getLogger(InjectionHelper.class.getName());
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);
}
log.fine("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');
log.fine("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/sizzle.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.log(Level.FINE, "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/sizzle.js");
writeScriptTag(os, "/core/scripts/atoms.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;
log.fine(url + " (InjectionHelper looking)");
if (!isKnownToBeHtml) {
bytesCopied += ModifiedIO.copy(in, out);
} else {
log.fine("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.warning("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("
© 2015 - 2025 Weber Informatics LLC | Privacy Policy