/*
* This file is part of the Wayback archival access software
* (http://archive-access.sourceforge.net/projects/wayback/).
*
* Licensed to the Internet Archive (IA) by one or more individual
* contributors.
*
* The IA licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.archive.wayback.replay.swf;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.zip.DataFormatException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.httpclient.URIException;
import org.archive.url.UsableURI;
import org.archive.url.UsableURIFactory;
import org.archive.wayback.ReplayRenderer;
import org.archive.wayback.ResultURIConverter;
import org.archive.wayback.core.CaptureSearchResult;
import org.archive.wayback.core.CaptureSearchResults;
import org.archive.wayback.core.Resource;
import org.archive.wayback.core.WaybackRequest;
import org.archive.wayback.exception.BadContentException;
import org.archive.wayback.exception.BetterRequestException;
import org.archive.wayback.exception.WaybackException;
import org.archive.wayback.replay.HttpHeaderOperation;
import org.archive.wayback.replay.HttpHeaderProcessor;
import org.archive.wayback.util.url.UrlOperations;
import com.flagstone.transform.DoAction;
import com.flagstone.transform.EventHandler;
import com.flagstone.transform.Movie;
import com.flagstone.transform.MovieTag;
import com.flagstone.transform.action.Action;
import com.flagstone.transform.action.GetUrl;
import com.flagstone.transform.action.Push;
import com.flagstone.transform.action.Table;
import com.flagstone.transform.button.DefineButton2;
import com.flagstone.transform.coder.DecoderRegistry;
/**
* ReplayRenderer which passes embedded URLs inside flash (SWF) format content
* through a ResultURIConverter, allowing them to be rewritten.
*
* @author brad
*
*/
public class SWFReplayRenderer implements ReplayRenderer {
private HttpHeaderProcessor httpHeaderProcessor;
/**
* @param httpHeaderProcessor
* to use for rewriting original HTTP headers
*/
public SWFReplayRenderer(HttpHeaderProcessor httpHeaderProcessor) {
this.httpHeaderProcessor = httpHeaderProcessor;
}
public void renderResource(HttpServletRequest httpRequest,
HttpServletResponse httpResponse, WaybackRequest wbRequest,
CaptureSearchResult result, Resource resource,
ResultURIConverter uriConverter, CaptureSearchResults results)
throws ServletException, IOException, WaybackException {
renderResource(httpRequest, httpResponse, wbRequest, result, resource,
resource, uriConverter, results);
}
@Override
public void renderResource(HttpServletRequest httpRequest,
HttpServletResponse httpResponse, WaybackRequest wbRequest,
CaptureSearchResult result, Resource httpHeadersResource,
Resource payloadResource, ResultURIConverter uriConverter,
CaptureSearchResults results) throws ServletException, IOException,
WaybackException {
try {
// copy HTTP response code:
HttpHeaderOperation.copyHTTPMessageHeader(httpHeadersResource, httpResponse);
// load and process original headers:
Map<String, String> headers = HttpHeaderOperation.processHeaders(
httpHeadersResource, result, uriConverter, httpHeaderProcessor);
// The URL of the resource, for resolving embedded relative URLs:
URL url = null;
try {
url = new URL(result.getOriginalUrl());
} catch (MalformedURLException e1) {
e1.printStackTrace();
throw new IOException(e1.getMessage());
}
// the date to associate with the embedded, rewritten URLs:
String datespec = result.getCaptureTimestamp();
SWFUrlRewriter rw = new SWFUrlRewriter(uriConverter, url, datespec);
// OK, try to read the input movie:
Movie movie = getRobustMovie(RobustMovieDecoder.DECODE_RULE_NULLS);
try {
movie.decodeFromStream(payloadResource);
} catch (DataFormatException e1) {
throw new BadContentException(e1.getLocalizedMessage());
}
Movie outMovie = new Movie(movie);
List<MovieTag> inTags = movie.getObjects();
ArrayList<MovieTag> outTags = new ArrayList<MovieTag>();
for (MovieTag tag : inTags) {
outTags.add(rewriteTag(rw, tag));
}
outMovie.setObjects(outTags);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
outMovie.encodeToStream(baos);
} catch (DataFormatException e) {
throw new BadContentException(e.getLocalizedMessage());
}
// put the new corrected length:
headers.put(HttpHeaderOperation.HTTP_LENGTH_HEADER,
String.valueOf(baos.size()));
// send the new headers:
HttpHeaderOperation.sendHeaders(headers, httpResponse);
// and copy the stored up byte-stream:
baos.writeTo(httpResponse.getOutputStream());
} catch (Exception e) {
// Redirect to identity if there are any issues
throw new BetterRequestException(UrlOperations.computeIdentityUrl(wbRequest));
}
}
private Movie getRobustMovie(int decodeRule) {
Movie movie = new Movie();
DecoderRegistry registry = DecoderRegistry.getDefault();
RobustMovieDecoder decoder = new RobustMovieDecoder();
decoder.setDelegate(registry.getMovieDecoder());
decoder.setDecodeRule(decodeRule);
registry.setMovieDecoder(decoder);
movie.setRegistry(registry);
return movie;
}
private MovieTag rewriteTag(SWFUrlRewriter rw, MovieTag tag) {
if (tag instanceof DoAction) {
DoAction doAction = (DoAction) tag;
doAction.setActions(rewriteActions(rw, doAction.getActions()));
} else if (tag instanceof DefineButton2) {
DefineButton2 defButton2 = (DefineButton2) tag;
defButton2.setEvents(rewriteEventHandlers(rw,
defButton2.getEvents()));
}
return tag;
}
private List<EventHandler> rewriteEventHandlers(SWFUrlRewriter rw,
List<EventHandler> handlers) {
ArrayList<EventHandler> newActions = new ArrayList<EventHandler>();
for (EventHandler handler : handlers) {
handler.setActions(rewriteActions(rw, handler.getActions()));
newActions.add(handler);
}
return newActions;
}
private List<Action> rewriteActions(SWFUrlRewriter rw, List<Action> actions) {
ArrayList<Action> newActions = new ArrayList<Action>();
for (Action action : actions) {
if (action instanceof Table) {
Table table = (Table) action;
table.setValues(rewriteStringValues(rw, table.getValues()));
newActions.add(table);
} else if (action instanceof Push) {
Push push = (Push) action;
newActions.add(new Push(rewriteObjectValues(rw,
push.getValues())));
} else if (action instanceof GetUrl) {
GetUrl getUrl = (GetUrl) action;
newActions.add(new GetUrl(rewriteString(rw, getUrl.getUrl()),
getUrl.getTarget()));
} else {
newActions.add(action);
}
}
return newActions;
}
private List<Object> rewriteObjectValues(SWFUrlRewriter rw,
List<Object> values) {
ArrayList<Object> nvals = new ArrayList<Object>();
for (int i = 0; i < values.size(); i++) {
Object orig = values.get(i);
if (orig instanceof String) {
nvals.add(rewriteString(rw, (String) orig));
} else {
nvals.add(orig);
}
}
return nvals;
}
private List<String> rewriteStringValues(SWFUrlRewriter rw,
List<String> values) {
ArrayList<String> nvals = new ArrayList<String>();
for (int i = 0; i < values.size(); i++) {
nvals.add(rewriteString(rw, values.get(i)));
}
return nvals;
}
private String rewriteString(SWFUrlRewriter rw, String original) {
if (original.startsWith("http://")) {
// System.err.format("Rewrite(%s)\n",original);
return rw.rewrite(original);
}
return original;
}
private class SWFUrlRewriter {
UsableURI baseUrl = null;
ResultURIConverter converter;
String datespec;
public SWFUrlRewriter(ResultURIConverter converter, URL baseUrl,
String datespec) {
this.datespec = datespec;
this.converter = converter;
try {
this.baseUrl = UsableURIFactory
.getInstance(baseUrl.toExternalForm());
} catch (URIException e) {
e.printStackTrace();
}
}
public String rewrite(String url) {
try {
String resolved = url;
if (baseUrl != null) {
resolved = UsableURIFactory.getInstance(baseUrl, url).toString();
}
return converter.makeReplayURI(datespec, resolved);
} catch (URIException e) {
e.printStackTrace();
}
return url;
}
}
}