package org.mapfish.print.processor.http;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.mapfish.print.ExceptionUtils;
import org.mapfish.print.RegexpUtil;
import org.mapfish.print.config.Configuration;
import org.mapfish.print.http.AbstractMfClientHttpRequestFactoryWrapper;
import org.mapfish.print.http.MfClientHttpRequestFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This processor maps https requests to http requests for certain hosts. The port number can also be mapped since that is usually
* required.
* <p>Example: </p>
* <pre><code>
* - !useHttpForHttps
* hosts: [localhost, www.camptocamp.com]
* portMapping:
* 443 : 80
* 8443 : 8443
* </code></pre>
*
* <p>Can be applied conditionally using matchers, like in {@link RestrictUrisProcessor}
* (<a href="processors.html#!restrictUris">!restrictUris</a>).</p>
* [[examples=http_processors,osm_custom_params]]
*/
public final class UseHttpForHttpsProcessor extends AbstractClientHttpRequestFactoryProcessor {
private static final int HTTPS_STANDARD_PORT = 443;
private static final int HTTP_STANDARD_PORT = 80;
private static final int JAVA_HTTPS_STANDARD_PORT = 8443;
private static final int JAVA_HTTP_STANDARD_PORT = 8080;
private static final Pattern HTTP_AUTHORITY_PORT_EXTRACTOR = Pattern.compile("(.*@)?.*:(\\d+)");
private static final Pattern HTTP_AUTHORITY_HOST_EXTRACTOR = Pattern.compile("(.*@)?([^:]*)(:\\d+)?");
private Map<Integer, Integer> portMapping = Maps.newHashMap();
private List<Pattern> hosts = Lists.newArrayList();
/**
* Constructor.
*/
protected UseHttpForHttpsProcessor() {
this.portMapping.put(HTTPS_STANDARD_PORT, HTTP_STANDARD_PORT);
this.portMapping.put(JAVA_HTTPS_STANDARD_PORT, JAVA_HTTP_STANDARD_PORT);
}
@Override
protected void extraValidation(final List<Throwable> validationErrors, final Configuration configuration) {
super.extraValidation(validationErrors, configuration);
if (this.hosts.isEmpty()) {
validationErrors.add(new IllegalArgumentException("No hosts are registered"));
}
}
@Override
public MfClientHttpRequestFactory createFactoryWrapper(final ClientHttpFactoryProcessorParam clientHttpFactoryProcessorParam,
final MfClientHttpRequestFactory requestFactory) {
return new AbstractMfClientHttpRequestFactoryWrapper(requestFactory, matchers, false) {
@Override
protected ClientHttpRequest createRequest(final URI uri,
final HttpMethod httpMethod,
final MfClientHttpRequestFactory requestFactory) throws IOException {
if (uri.getScheme() != null && uri.getScheme().equals("https")) {
try {
URI httpUri = uri;
if (uri.getHost() == null && uri.getAuthority() != null) {
final Matcher matcher = HTTP_AUTHORITY_HOST_EXTRACTOR.matcher(uri.getAuthority());
if (matcher.matches()) {
final String host = matcher.group(2);
if (matchingHost(host)) {
httpUri = updatePortAndSchemeInAuthority(uri);
}
}
} else {
if (matchingHost(uri.getHost())) {
httpUri = updatePortAndScheme(uri);
}
}
return requestFactory.createRequest(httpUri, httpMethod);
} catch (URISyntaxException e) {
throw ExceptionUtils.getRuntimeException(e);
}
}
return requestFactory.createRequest(uri, httpMethod);
}
};
}
/**
* Set the https port to http port mapping.
*
* @param portMapping the mappings to add.
*/
public void setPortMapping(final Map<Integer, Integer> portMapping) {
this.portMapping.putAll(portMapping);
}
/**
* Set the patterns to use for selecting the hosts to apply the https -> http mapping to.
* <ul>
* <li>If the host starts and ends with / then it is compiled as a regular expression</li>
* <li>Otherwise the hosts must exactly match</li>
* </ul>
*
* @param hosts hosts to match. Can be regular expressions
*/
public void setHosts(final List<String> hosts) {
this.hosts.clear();
for (String host : hosts) {
this.hosts.add(RegexpUtil.compilePattern(host));
}
}
private boolean matchingHost(final String host) {
for (Pattern hostPattern : UseHttpForHttpsProcessor.this.hosts) {
if (hostPattern.matcher(host).matches()) {
return true;
}
}
return false;
}
private URI updatePortAndScheme(final URI uri) throws URISyntaxException {
URI httpUri;
int port = uri.getPort();
if (UseHttpForHttpsProcessor.this.portMapping.containsKey(port)) {
port = UseHttpForHttpsProcessor.this.portMapping.get(port);
}
httpUri = new URI("http", uri.getUserInfo(), uri.getHost(), port,
uri.getPath(),
uri.getQuery(), uri.getFragment());
return httpUri;
}
private URI updatePortAndSchemeInAuthority(final URI uri) throws URISyntaxException {
URI httpUri;
String authority = uri.getAuthority();
Matcher matcher = HTTP_AUTHORITY_PORT_EXTRACTOR.matcher(uri.getAuthority());
if (matcher.matches()) {
int port = Integer.parseInt(matcher.group(2));
authority = authority.substring(0, matcher.start(2));
if (UseHttpForHttpsProcessor.this.portMapping.containsKey(port)) {
port = UseHttpForHttpsProcessor.this.portMapping.get(port);
}
authority = authority + port;
}
httpUri = new URI("http", authority, uri.getPath(), uri.getQuery(),
uri.getFragment());
return httpUri;
}
}