001: /* Copyright 2004 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal.serialize;
007:
008: import java.io.BufferedReader;
009: import java.io.File;
010: import java.io.FileWriter;
011: import java.io.IOException;
012: import java.io.InputStream;
013: import java.io.InputStreamReader;
014:
015: import org.apache.commons.httpclient.Header;
016: import org.apache.commons.httpclient.HttpClient;
017: import org.apache.commons.httpclient.HttpStatus;
018: import org.apache.commons.httpclient.methods.GetMethod;
019: import org.apache.commons.logging.Log;
020: import org.apache.commons.logging.LogFactory;
021: import org.jasig.portal.properties.PropertiesManager;
022: import org.jasig.portal.services.HttpClientManager;
023: import org.jasig.portal.utils.CommonUtils;
024:
025: /**
026: * Appends PROXY_REWRITE_PREFIX string in front of all the references to images
027: * that are on a remote location that start with http://. This allows the
028: * browser to load the resources without triggering a warning about mixed
029: * content. For example instead of http://www.abc.com/image.gif the URI will be
030: * rewritten to https://[portaladdress]/PROXY_REWRITE_PREFIX/www.abc.com/image.gif
031: *
032: * This class also does the proxy rewrite in the following exceptional situations:
033: *
034: * 1. If the return code pointing to the image is 3XX (the image reference,
035: * references is a mapping to a different location) In this case the final
036: * destination address in which the image or the resource is located is e and
037: * then the rewrite points to this location.
038: *
039: * 2. If the content of a channel is an include javascript file the file is
040: * rewritten to a location on a local virtual host and at the same time the
041: * image or other resources references are rewritten.
042: * HttpURLConnection.HTTP_MOVED_PERM
043: *
044: * @author <a href="mailto:kazemnaderi@yahoo.ca">Kazem Naderi</a>
045: * @version $Revision: 42566 $
046: * @since uPortal 2.2
047: */
048:
049: public class ProxyWriter {
050:
051: private static final Log log = LogFactory.getLog(ProxyWriter.class);
052:
053: /**
054: * True if allow rewriting certain elements for proxying.
055: */
056: protected boolean _proxying;
057:
058: /**
059: * The list of elements which src attribute is rewritten with proxy.
060: */
061:
062: // Only image content should be proxied
063: private static final String[] _proxiableElements = { "image",
064: "img", "input" };
065:
066: /*
067: * If enabled the references to images or any external browser loadable resources will be proxied.
068: */
069: private static boolean PROXY_ENABLED = PropertiesManager
070: .getPropertyAsBoolean("org.jasig.portal.serialize.ProxyWriter.resource_proxy_enabled");
071:
072: /*
073: * The URI of location on virtual host on the same server as portal. This URI is used for rewriting proxied files.
074: */
075: private static String PROXIED_FILES_URI = PropertiesManager
076: .getProperty("org.jasig.portal.serialize.ProxyWriter.proxy_files_uri");
077:
078: /*
079: * The path of location on virtual host on the same server as portal. This path is used for rewriting proxied files.
080: */
081: private static String PROXIED_FILES_PATH = PropertiesManager
082: .getProperty("org.jasig.portal.serialize.ProxyWriter.proxy_files_path");
083:
084: /*
085: * The prefix used for proxying
086: */
087: private static final String PROXY_REWRITE_PREFIX = PropertiesManager
088: .getProperty("org.jasig.portal.serialize.ProxyWriter.resource_proxy_rewrite_prefix");
089:
090: /*
091: * The local domain that does not do redirection
092: */
093: private static final String PROXY_REWRITE_NO_REDIRECT_DOMAIN = PropertiesManager
094: .getProperty("org.jasig.portal.serialize.ProxyWriter.no_redirect_domain");
095:
096: /**
097: * Examines whether or not the proxying should be done and if so handles different situations by delegating
098: * the rewrite to other methods n the class.
099: * @param name
100: * @param localName
101: * @param url
102: * @return value
103: */
104: protected static String considerProxyRewrite(final String name,
105: final String localName, final String url) {
106: if (PROXY_ENABLED
107: && (name.equalsIgnoreCase("src") || name
108: .equalsIgnoreCase("archive"))
109: && url.indexOf("http://") != -1) {
110:
111: // capture any resource redirect and set the value to the real
112: // address while proxying it
113: final String skip_protocol = url.substring(7);
114: final String domain_only = skip_protocol.substring(0,
115: skip_protocol.indexOf("/"));
116: /**
117: * Capture 3xx return codes - specifically, if 301/302, then go to
118: * the redirected URL - note, this in turn may also be redirected.
119: * Note - do as little network connecting as possible. So as a
120: * start, assume PROXY_REWRITE_NO_REDIRECT_DOMAIN domain images will
121: * not be redirected, so skip these ones.
122: */
123: if (PROXY_REWRITE_NO_REDIRECT_DOMAIN.length() == 0
124: || !domain_only
125: .endsWith(PROXY_REWRITE_NO_REDIRECT_DOMAIN)) {
126: String work_url = url;
127: while (true) {
128: final HttpClient client = HttpClientManager
129: .getNewHTTPClient();
130: final GetMethod get = new GetMethod(work_url);
131:
132: try {
133: final int responseCode = client
134: .executeMethod(get);
135: if (responseCode != HttpStatus.SC_MOVED_PERMANENTLY
136: && responseCode != HttpStatus.SC_MOVED_TEMPORARILY) {
137: // if there is a script element with a src attribute
138: // the src should be rewritten
139: if (localName.equalsIgnoreCase("script")) {
140: return reWrite(work_url, get);
141: } else {
142: // handle normal proxies
143: for (int i = 0; i < _proxiableElements.length; i++) {
144: if (localName
145: .equalsIgnoreCase(_proxiableElements[i])) {
146: work_url = PROXY_REWRITE_PREFIX
147: + work_url.substring(7);
148: break;
149: }
150: }
151: }
152: return work_url;
153: }
154:
155: /* At this point we will have a redirect directive */
156: final Header location = get
157: .getResponseHeader("location");
158: if (location != null) {
159: work_url = location.getValue();
160: } else {
161: return url;
162: }
163:
164: // According to httpClient documentation we have to read the body
165: final InputStream in = get
166: .getResponseBodyAsStream();
167: try {
168: final byte buff[] = new byte[4096];
169: while (in.read(buff) > 0) {
170: }
171: ;
172: } finally {
173: in.close();
174: }
175: } catch (IOException ioe) {
176: return url;
177: } finally {
178: get.releaseConnection();
179: }
180: }
181: }
182: }
183: return url;
184: }
185:
186: /**
187: * This method rewrites included javascript files and replaces the references in these files
188: * to images' sources to use proxy.
189: *
190: * @param scriptUri: The string representing the address of script
191: * @return value: The new address of the script file which image sources have been rewritten
192: */
193: private static String reWrite(final String scriptUri,
194: final GetMethod get) {
195: final String fileName = fileNameGenerator(scriptUri);
196: final String filePath = PROXIED_FILES_PATH + fileName;
197: try {
198: final File outputFile = new File(filePath);
199: if (!outputFile.exists()
200: || (System.currentTimeMillis()
201: - outputFile.lastModified() > 1800 * 1000)) {
202: try {
203: final BufferedReader in = new BufferedReader(
204: new InputStreamReader(get
205: .getResponseBodyAsStream()));
206: try {
207: final FileWriter out = new FileWriter(
208: outputFile);
209: try {
210: String line;
211: while ((line = in.readLine()) != null) {
212: out.write(processLine(line) + "\t\n");
213: }
214: } finally {
215: out.close();
216: }
217: } finally {
218: in.close();
219: }
220: } catch (Exception e) {
221: log.error(
222: "ProxyWriter::rewrite():Failed to rewrite the file for: "
223: + scriptUri, e);
224: outputFile.delete();
225: return scriptUri;
226: } // end catch
227: }
228:
229: // Now make sure that we can read the modified version
230: final String newScriptPath = PROXIED_FILES_URI + fileName;
231: final HttpClient client = HttpClientManager
232: .getNewHTTPClient();
233: final GetMethod getTest = new GetMethod(newScriptPath);
234:
235: try {
236: final int rc = client.executeMethod(getTest);
237: if (rc != HttpStatus.SC_OK) {
238: log.error("ProxyWriter::rewrite(): The file "
239: + filePath
240: + " is written but cannot be reached at "
241: + newScriptPath);
242: return scriptUri;
243: } else {
244: return PROXY_REWRITE_PREFIX
245: + PROXIED_FILES_URI.substring(7) + fileName;
246: }
247: } finally {
248: getTest.releaseConnection();
249: }
250:
251: } catch (IOException e) {
252: log.error(
253: "ProxyWriter::rewrite(): Failed to read the file at : "
254: + filePath, e);
255: return scriptUri;
256: }
257: }
258:
259: /**
260: * This method uses a URI and creates an HTML file name by simply omitting some characters from the URI.
261: * The purpose of using the address for the file name is that the file names will be unique and map to addresses.
262: * @param addr: is the address of the file
263: * @newName: is the name built form the address
264: */
265: private static String fileNameGenerator(String addr) {
266: String newName = CommonUtils.replaceText(addr, "/", "");
267: newName = CommonUtils.replaceText(newName, "http:", "");
268: newName = CommonUtils.replaceText(newName, "www.", "");
269: newName = CommonUtils.replaceText(newName, ".", "");
270: newName = CommonUtils.replaceText(newName, "?", "");
271: newName = CommonUtils.replaceText(newName, "&", "");
272:
273: return newName.substring(0, Math.min(16, newName.length()))
274: + ".html";
275: }
276:
277: /**
278: * This method parses a line recursively and replaces all occurrences of image references
279: * with a proxied reference.
280: * @param line - is the portion of the line or the whole line to be processed.
281: * @return line - is the portion of the line or the line that has been processed.
282: */
283: private static String processLine(String line) throws Exception {
284: try {
285: if (line.indexOf(" src") != -1
286: && line.indexOf("http://") != -1) {
287: String srcValue = extractURL(line);
288: String srcNewValue = createProxyURL(srcValue);
289: line = CommonUtils.replaceText(line, srcValue,
290: srcNewValue);
291: int firstPartIndex = line.lastIndexOf(srcNewValue)
292: + srcNewValue.length();
293: String remaining = line.substring(firstPartIndex);
294: return line.substring(0, firstPartIndex) + " "
295: + processLine(remaining);
296: } else {
297: return line;
298: }
299: } catch (Exception e) {
300:
301: log.error("Failed to process a line : " + line, e);
302: throw e;
303: }
304: }
305:
306: /**
307: *
308: * This method takes a String (line) and parses out the value of src attribute
309: * in that string.
310: * @param line - String
311: * @return srcValue - String
312: */
313: private static String extractURL(String line) {
314: int URLStartIndex = 0;
315: int URLEndIndex = 0;
316: //need this to make sure only image paths are pointed to and not href.
317: int srcIndex = line.indexOf(" src");
318: if (line.indexOf("https://", srcIndex) != -1) {
319: return "";
320: }
321: if (line.indexOf("http://", srcIndex) != -1) {
322: URLStartIndex = line.indexOf("http", srcIndex);
323: } else {
324: return "";
325: }
326:
327: URLEndIndex = line.indexOf(" ", URLStartIndex);
328: String srcValue = line.substring(URLStartIndex, URLEndIndex);
329: return srcValue;
330: }
331:
332: /**
333: *
334: * This method receives an image source URL and modified
335: * it to be proxied.
336: * @param srcValue - String
337: * @return srcNewValue - String
338: */
339: private static String createProxyURL(String srcValue) {
340: String srcNewValue = "";
341: if (srcValue.indexOf("https://") != -1) {
342: return srcValue;
343: } else if (srcValue.indexOf("http://") != -1) {
344: srcNewValue = CommonUtils.replaceText(srcValue, "http://",
345: PROXY_REWRITE_PREFIX);
346: } else {
347: srcNewValue = "";
348: }
349: return srcNewValue;
350: }
351:
352: }
|