blob: 9a1403e794102ad8fc0b2d6c800c346244f2991c [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.apache.xml.security.utils.resolver.implementations;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URISyntaxException;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;
import org.apache.xml.security.signature.XMLSignatureInput;
import org.apache.xml.security.utils.XMLUtils;
import org.apache.xml.security.utils.resolver.ResourceResolverContext;
import org.apache.xml.security.utils.resolver.ResourceResolverException;
import org.apache.xml.security.utils.resolver.ResourceResolverSpi;
/**
* A simple ResourceResolver for HTTP requests. This class handles only 'pure'
* HTTP URIs which means without a fragment. The Fragment handling is done by the
* {@link ResolverFragment} class.
* <BR>
* If the user has a corporate HTTP proxy which is to be used, the usage can be
* switched on by setting properties for the resolver:
* <PRE>
* resourceResolver.setProperty("http.proxy.host", "proxy.company.com");
* resourceResolver.setProperty("http.proxy.port", "8080");
*
* // if we need a password for the proxy
* resourceResolver.setProperty("http.proxy.username", "proxyuser3");
* resourceResolver.setProperty("http.proxy.password", "secretca");
* </PRE>
*
* @see <A HREF="http://www.javaworld.com/javaworld/javatips/jw-javatip42_p.html">Java Tip 42: Write Java apps that work with proxy-based firewalls</A>
* @see <A HREF="https://docs.oracle.com/javase/8/docs/technotes/guides/net/properties.html">JDK docs for network properties</A>
* @see <A HREF="http://metalab.unc.edu/javafaq/javafaq.html#proxy">The JAVA FAQ Question 9.5: How do I make Java work with a proxy server?</A>
*/
public class ResolverDirectHTTP extends ResourceResolverSpi {
private static final org.slf4j.Logger LOG =
org.slf4j.LoggerFactory.getLogger(ResolverDirectHTTP.class);
/** Field properties[] */
private static final String[] properties = {
"http.proxy.host", "http.proxy.port",
"http.proxy.username", "http.proxy.password",
"http.basic.username", "http.basic.password"
};
/** Field HttpProxyHost */
private static final int HttpProxyHost = 0;
/** Field HttpProxyPort */
private static final int HttpProxyPort = 1;
/** Field HttpProxyUser */
private static final int HttpProxyUser = 2;
/** Field HttpProxyPass */
private static final int HttpProxyPass = 3;
/** Field HttpProxyUser */
private static final int HttpBasicUser = 4;
/** Field HttpProxyPass */
private static final int HttpBasicPass = 5;
private final Map<String, String> resolverProperties;
public ResolverDirectHTTP() {
resolverProperties = Collections.emptyMap();
}
public ResolverDirectHTTP(Map<String, String> resolverProperties) {
this.resolverProperties =
Collections.unmodifiableMap(resolverProperties != null ? resolverProperties : Collections.emptyMap());
}
/**
* {@inheritDoc}
*/
@Override
public XMLSignatureInput engineResolveURI(ResourceResolverContext context)
throws ResourceResolverException {
try {
// calculate new URI
URI uriNew = getNewURI(context.uriToResolve, context.baseUri);
URL url = uriNew.toURL();
URLConnection urlConnection = openConnection(url, context);
// check if Basic authentication is required
String auth = urlConnection.getHeaderField("WWW-Authenticate");
if (auth != null && auth.startsWith("Basic")) {
// do http basic authentication
String user =
getProperty(context, ResolverDirectHTTP.properties[ResolverDirectHTTP.HttpBasicUser]);
String pass =
getProperty(context, ResolverDirectHTTP.properties[ResolverDirectHTTP.HttpBasicPass]);
if (user != null && pass != null) {
urlConnection = openConnection(url, context);
String password = user + ":" + pass;
String encodedPassword = XMLUtils.encodeToString(password.getBytes(StandardCharsets.ISO_8859_1));
// set authentication property in the http header
urlConnection.setRequestProperty("Authorization",
"Basic " + encodedPassword);
}
}
String mimeType = urlConnection.getHeaderField("Content-Type");
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream inputStream = urlConnection.getInputStream()) {
byte[] buf = new byte[4096];
int read = 0;
int summarized = 0;
while ((read = inputStream.read(buf)) >= 0) {
baos.write(buf, 0, read);
summarized += read;
}
LOG.debug("Fetched {} bytes from URI {}", summarized, uriNew.toString());
XMLSignatureInput result = new XMLSignatureInput(baos.toByteArray());
result.setSecureValidation(context.secureValidation);
result.setSourceURI(uriNew.toString());
result.setMIMEType(mimeType);
return result;
}
} catch (URISyntaxException | IOException | IllegalArgumentException ex) {
throw new ResourceResolverException(ex, context.uriToResolve, context.baseUri, "generic.EmptyMessage");
}
}
private URLConnection openConnection(URL url, ResourceResolverContext context) throws IOException {
String proxyHostProp =
getProperty(context, ResolverDirectHTTP.properties[ResolverDirectHTTP.HttpProxyHost]);
String proxyPortProp =
getProperty(context, ResolverDirectHTTP.properties[ResolverDirectHTTP.HttpProxyPort]);
String proxyUser =
getProperty(context, ResolverDirectHTTP.properties[ResolverDirectHTTP.HttpProxyUser]);
String proxyPass =
getProperty(context, ResolverDirectHTTP.properties[ResolverDirectHTTP.HttpProxyPass]);
Proxy proxy = null;
if (proxyHostProp != null && proxyPortProp != null) {
int port = Integer.parseInt(proxyPortProp);
proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHostProp, port));
}
URLConnection urlConnection;
if (proxy != null) {
urlConnection = url.openConnection(proxy);
if (proxyUser != null && proxyPass != null) {
String password = proxyUser + ":" + proxyPass;
String authString = "Basic " + XMLUtils.encodeToString(password.getBytes(StandardCharsets.ISO_8859_1));
urlConnection.setRequestProperty("Proxy-Authorization", authString);
}
} else {
urlConnection = url.openConnection();
}
return urlConnection;
}
/**
* We resolve http URIs <I>without</I> fragment...
*
* @param context
* @return true if can be resolved
*/
public boolean engineCanResolveURI(ResourceResolverContext context) {
if (context.uriToResolve == null) {
LOG.debug("quick fail, uri == null");
return false;
}
if (context.uriToResolve.isEmpty() || context.uriToResolve.charAt(0) == '#') {
LOG.debug("quick fail for empty URIs and local ones");
return false;
}
LOG.debug("I was asked whether I can resolve {}", context.uriToResolve);
if (context.uriToResolve.startsWith("http:") ||
context.baseUri != null && context.baseUri.startsWith("http:")) {
LOG.debug("I state that I can resolve {}", context.uriToResolve);
return true;
}
LOG.debug("I state that I can't resolve {}", context.uriToResolve);
return false;
}
private static URI getNewURI(String uri, String baseURI) throws URISyntaxException {
URI newUri = null;
if (baseURI == null || "".equals(baseURI)) {
newUri = new URI(uri);
} else {
newUri = new URI(baseURI).resolve(uri);
}
// if the URI contains a fragment, ignore it
if (newUri.getFragment() != null) {
return new URI(newUri.getScheme(), newUri.getSchemeSpecificPart(), null);
}
return newUri;
}
private String getProperty(ResourceResolverContext context, String propertyName) {
// First check the properties defined on this Resolver.
if (resolverProperties.containsKey(propertyName)) {
return resolverProperties.get(propertyName);
}
// Otherwise defer to the passed in properties
return context.getProperties().get(propertyName);
}
}