/* Copyright 2004 The JA-SIG Collaborative. All rights reserved.
* See license distributed with this file and
* available online at http://www.uportal.org/license.html
*/
package org.jasig.portal.serialize;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import org.jasig.portal.properties.PropertiesManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.portal.utils.AddressTester;
import org.jasig.portal.utils.CommonUtils;
/**
* This Class allows appending PROXY_REWRITE_PREFIX String in front of all the references to images, javascript files, etc..
* that are on a remote location. This allows the browser while portal is running in https assume that these resources
* are secure resources(are referenced by https rather than http). This is because the resource URI insteadof
* http://www.abc.com/image.gif will be rewriten as as https://[portal address]/PROXY_REWRITE_PREFIX/www.abc.com/image.gif
* This class does the proxy rewrite in the following exceptional situations as well:
* 1. If the return code poting to the image is 3XX (the image refrence, refrences is a mapping to a diffrent location)
* In this case the final destination iddress n which the image or the resource is located is e
* and then the rewrite points to this location.
* 2. If the content of a channel is an include javascript file the file is rewriten to a location on a local virtual host
* and at the same time the image or other resources references are rewritten.
*
*
* @author <a href="mailto:kazemnaderi@yahoo.ca">Kazem Naderi</a>
* @version $Revision: 1.11.4.1 $
* @since uPortal 2.2
*/
public class ProxyWriter {
private static final Log log = LogFactory.getLog(ProxyWriter.class);
/**
* True if allow rewriting certain elements for proxying.
*/
protected boolean _proxying;
/**
* The list of elemnets which src attribute is rewritten with proxy.
*/
private static final String[] _proxiableElements = {"image","img","script","input","applet","iframe"};
/*
* If enabled the references to images or any external browser loadable resources will be proxied.
*/
private static boolean PROXY_ENABLED=PropertiesManager.getPropertyAsBoolean("org.jasig.portal.serialize.ProxyWriter.resource_proxy_enabled");
/*
* The URI of location on virtual host on the same server as portal. This URI is used for rewriting proxied files.
*/
private static String PROXIED_FILES_URI=PropertiesManager.getProperty("org.jasig.portal.serialize.ProxyWriter.proxy_files_uri");
/*
* The path of location on virtual host on the same server as portal. This path is used for rewriting proxied files.
*/
private static String PROXIED_FILES_PATH=PropertiesManager.getProperty("org.jasig.portal.serialize.ProxyWriter.proxy_files_path");
/*
* The prefix used for proxying
*/
private static final String PROXY_REWRITE_PREFIX=PropertiesManager.getProperty("org.jasig.portal.serialize.ProxyWriter.resource_proxy_rewrite_prefix");
/*
* The local domain that does not do redirection
*/
private static final String PROXY_REWRITE_NO_REDIRECT_DOMAIN = PropertiesManager.
getProperty("org.jasig.portal.serialize.ProxyWriter.no_redirect_domain");
/**
* Examines whther or not the proxying should be done and if so handles differnt situations by delegating
* the rewrite to other methods n the class.
* @param name
* @param localName
* @param value
* @return value
*/
protected static String considerProxyRewrite(String name, String localName, String value)
{
if (PROXY_ENABLED && (name.equalsIgnoreCase("src") || name.equalsIgnoreCase("archive")) &&
value.indexOf("http://")!=-1)
{
//capture any resource redirect and set the value to the real address while proxying it
value = capture3XXCodes(value);
//if there is a script element with a src attribute the src should be rewriten
if(localName.equalsIgnoreCase("script")){
value = reWrite(value);
return value;
}
//handle normal proxies
for (int i=0; i<_proxiableElements.length; i++)
{
if (localName.equalsIgnoreCase(_proxiableElements[i]))
{
value = PROXY_REWRITE_PREFIX + value.substring(7);
break;
}
}
}
return value;
}
/**
* Capture 3xx return codes - specifically, if 301/302, then go tp the
* redirected url - note, this in turn may also be redirected.
* Note - do as little network connecting as possible. So as a start, assume
* "ubc.ca" domain images will not be redirected so skip these ones.
* @param value
* @return value
*/
private static String capture3XXCodes(String value){
try{
String skip_protocol = value.substring(7);
String domain_only = skip_protocol.substring(0,skip_protocol.indexOf("/"));
String work_value = value;
if (PROXY_REWRITE_NO_REDIRECT_DOMAIN.length() > 0 && !domain_only.endsWith(PROXY_REWRITE_NO_REDIRECT_DOMAIN)) {
while (true) {
AddressTester tester = new AddressTester(work_value, true);
try {
int responseCode = tester.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_MOVED_PERM &&
responseCode != HttpURLConnection.HTTP_MOVED_TEMP) {
log.debug(
"ProxyWriter::capture3XXCodes(): could not get deeper in getting the image.");
return work_value;
}
/* At this point we will have a redirect directive */
HttpURLConnection httpUrlConnect = (HttpURLConnection) tester.getConnection();
httpUrlConnect.connect();
work_value = httpUrlConnect.getHeaderField("Location"); ;
} finally {
tester.disconnect();
}
}
}
return value;
} catch(Exception e) {
log.error("Failed to rewrite the value", e);
return value;
}
}
/**
* This method rewrites include javascript files and replaces the refrences in these files
* to images' sources to use proxy.
* @param scriptUri: The string representing the address of script
* @return value: The new address of the script file which image sources have been rewritten
*/
private static String reWrite(String scriptUri){
String filePath = null;
String fileName = null;
try{
fileName = fileNameGenerator(scriptUri);
filePath = PROXIED_FILES_PATH + fileName;
File outputFile = new File(filePath);
if (!outputFile.exists() || (System.currentTimeMillis()-outputFile.lastModified() > 1800 * 1000))
{
try{
AddressTester tester = new AddressTester(scriptUri);
try {
if (!tester.URLAvailable()){
log.error("ProxyWriter::rewrite(): The adress " + scriptUri + " is not available. ");
return scriptUri;
}
HttpURLConnection httpUrlConnect = (HttpURLConnection) tester.getConnection();
httpUrlConnect.connect();
BufferedReader in = new BufferedReader(new InputStreamReader(httpUrlConnect.getInputStream()));
try {
FileWriter out = new FileWriter(outputFile);
try {
String line;
while ((line = in.readLine()) != null) {
out.write(processLine(line) + "\t\n");
}
} finally {
out.close();
}
} finally {
in.close();
}
} finally {
tester.disconnect();
}
} catch(Exception e) {
log.error(
"ProxyWriter::rewrite():Failed to rewrite the file for: " +
scriptUri, e);
outputFile.delete();
return scriptUri;
} //end catch
}
String newScriptPath = PROXIED_FILES_URI + fileName;
AddressTester tester = new AddressTester(newScriptPath);
try {
if (!tester.URLAvailable()) {
log.error(
"ProxyWriter::rewrite(): The file " + filePath +
" is written but cannot be reached at " +
newScriptPath);
return scriptUri;
} else {
return PROXY_REWRITE_PREFIX + PROXIED_FILES_URI.substring(7) +
fileName;
}
} finally {
tester.disconnect();
}
} catch(Exception e) {
log.error("Failed to read the file at : " + filePath, e);
return scriptUri;
}
}
/**
* This method uses a URI and creates an html file name by simply omiting some characters from the URI.
* The purpose of using the address for the file name is that the file names will be unique and map to addresses.
* @param addr: is the address of the file
* @newName: is the name built form the address
*/
private static String fileNameGenerator(String addr)
{
String newName = CommonUtils.replaceText(addr, "/", "");
newName = CommonUtils.replaceText(newName, "http:", "");
newName = CommonUtils.replaceText(newName, "www.", "");
newName = CommonUtils.replaceText(newName, ".", "");
newName = CommonUtils.replaceText(newName, "?", "");
newName = CommonUtils.replaceText(newName, "&", "");
return newName.substring(0, Math.min(16, newName.length())) + ".html";
}
/**
* This method parses a line recursivley and replaces all occurances of image references
* with a proxied reference.
* @param line - is the portion of the line or the whole line to be processed.
* @return line - is the portion of the line or the line that has been processed.
*/
private static String processLine(String line) throws Exception
{
try {
if (line.indexOf(" src") != -1 && line.indexOf("http://") != -1) {
String srcValue = extractURL(line);
String srcNewValue = createProxyURL(srcValue);
line = CommonUtils.replaceText(line, srcValue, srcNewValue);
int firstPartIndex = line.lastIndexOf(srcNewValue) + srcNewValue.length();
String remaining = line.substring(firstPartIndex);
return line.substring(0,firstPartIndex) + " " + processLine(remaining);
} else {
return line;
}
} catch(Exception e) {
log.error("Failed to process a line : " + line, e);
throw e;
}
}
/**
*
* This method takes a String (line) and parses out the value of src attribute
* in that string.
* @param line - String
* @return srcValue - String
*/
private static String extractURL(String line)
{
int URLStartIndex = 0;
int URLEndIndex = 0;
//need this to make sure only image paths are pointed to and not href.
int srcIndex = line.indexOf(" src");
if (line.indexOf("https://", srcIndex) != -1) {
return "";
}
if (line.indexOf("http://", srcIndex) != -1) {
URLStartIndex = line.indexOf("http", srcIndex);
} else {
return "";
}
URLEndIndex = line.indexOf(" ", URLStartIndex);
String srcValue = line.substring(URLStartIndex, URLEndIndex);
return srcValue;
}
/**
*
* This method receives an image source URL and modified
* it to be proxied.
* @param srcValue - String
* @return srcNewValue - String
*/
private static String createProxyURL(String srcValue)
{
String srcNewValue = "";
if (srcValue.indexOf("https://") != -1) {
return srcValue;
} else if (srcValue.indexOf("http://") != -1) {
srcNewValue = CommonUtils.replaceText(srcValue, "http://",
PROXY_REWRITE_PREFIX);
} else {
srcNewValue = "";
}
return srcNewValue;
}
}