blob: f3aab7d95976ecfecd29108e4cd815707a6ba8f8 [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.knox.gateway.filter.rewrite.impl;
import org.apache.commons.lang3.StringUtils;
import org.apache.knox.gateway.filter.AbstractGatewayFilter;
import org.apache.knox.gateway.filter.GatewayRequestWrapper;
import org.apache.knox.gateway.filter.rewrite.api.UrlRewriteFilterContentDescriptor;
import org.apache.knox.gateway.filter.rewrite.api.UrlRewriteFilterDescriptor;
import org.apache.knox.gateway.filter.rewrite.api.UrlRewriteRulesDescriptor;
import org.apache.knox.gateway.filter.rewrite.api.UrlRewriteServletContextListener;
import org.apache.knox.gateway.filter.rewrite.api.UrlRewriteServletFilter;
import org.apache.knox.gateway.filter.rewrite.api.UrlRewriteStreamFilterFactory;
import org.apache.knox.gateway.filter.rewrite.api.UrlRewriter;
import org.apache.knox.gateway.filter.rewrite.i18n.UrlRewriteMessages;
import org.apache.knox.gateway.filter.rewrite.spi.UrlRewriteStreamFilter;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.util.MimeTypes;
import org.apache.knox.gateway.util.urltemplate.Parser;
import org.apache.knox.gateway.util.urltemplate.Resolver;
import org.apache.knox.gateway.util.urltemplate.Template;
import org.eclipse.jetty.http.HttpHeader;
import javax.activation.MimeType;
import javax.servlet.FilterConfig;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import static org.apache.knox.gateway.filter.rewrite.impl.UrlRewriteUtil.pickFirstRuleWithEqualsIgnoreCasePathMatch;
public class UrlRewriteRequest extends GatewayRequestWrapper implements Resolver {
private static final UrlRewriteMessages LOG = MessagesFactory.get( UrlRewriteMessages.class );
private static final String[] EMPTY_STRING_ARRAY = new String[]{};
private FilterConfig config;
private UrlRewriter rewriter;
private String urlRuleName;
private String bodyFilterName;
private String headersFilterName;
private UrlRewriteFilterContentDescriptor headersFilterConfig;
private String cookiesFilterName;
/**
* Constructs a request object wrapping the given request.
*
* @param config FilterConfig
* @param request request to wrap
* @throws IllegalArgumentException if the request is null
*/
public UrlRewriteRequest( FilterConfig config, HttpServletRequest request ) {
super( request );
this.config = config;
this.rewriter = UrlRewriteServletContextListener.getUrlRewriter( config.getServletContext() );
this.urlRuleName = config.getInitParameter( UrlRewriteServletFilter.REQUEST_URL_RULE_PARAM );
this.bodyFilterName = config.getInitParameter( UrlRewriteServletFilter.REQUEST_BODY_FILTER_PARAM );
this.headersFilterName = config.getInitParameter( UrlRewriteServletFilter.REQUEST_HEADERS_FILTER_PARAM );
this.headersFilterConfig = getRewriteFilterConfig( headersFilterName, UrlRewriteServletFilter.HEADERS_MIME_TYPE );
this.cookiesFilterName = config.getInitParameter( UrlRewriteServletFilter.REQUEST_COOKIES_FILTER_PARAM );
}
Template getSourceUrl() {
Template urlTemplate;
//KNOX-439[
//StringBuffer urlString = super.getRequestURL();
StringBuffer urlString = new StringBuffer( 128 );
urlString.append( getScheme() );
urlString.append( "://" );
urlString.append( getServerName() );
urlString.append( ":" );
urlString.append( getServerPort() );
urlString.append( super.getRequestURI() );
//]
String queryString = super.getQueryString();
if( queryString != null ) {
urlString.append( '?' );
urlString.append( queryString );
}
try {
urlTemplate = Parser.parseLiteral( urlString.toString() );
} catch( URISyntaxException e ) {
LOG.failedToParseValueForUrlRewrite( urlString.toString() );
// Shouldn't be possible given that the URL is constructed from parts of an existing URL.
urlTemplate = null;
}
return urlTemplate;
}
// Note: Source url was added to the request attributes by the GatewayFilter doFilter method.
Template getTargetUrl() {
boolean rewriteRequestUrl = true;
Template targetUrl;
if( rewriteRequestUrl ) {
targetUrl = (Template)getAttribute( AbstractGatewayFilter.TARGET_REQUEST_URL_ATTRIBUTE_NAME );
if( targetUrl == null ) {
Template sourceUrl = getSourceUrl();
targetUrl = rewriter.rewrite( this, sourceUrl, UrlRewriter.Direction.IN, urlRuleName );
setAttribute( AbstractGatewayFilter.TARGET_REQUEST_URL_ATTRIBUTE_NAME, targetUrl );
}
} else {
targetUrl = (Template)getAttribute( AbstractGatewayFilter.SOURCE_REQUEST_URL_ATTRIBUTE_NAME );
}
return targetUrl;
}
private String[] splitTargetUrl( Template url ) {
if( url == null ) {
return EMPTY_STRING_ARRAY;
} else {
String s = url.toString();
return s.split( "\\?" );
}
}
@Override
public StringBuffer getRequestURL() {
return new StringBuffer( getRequestURI() );
}
//TODO: I think this method is implemented wrong based on the HttpServletRequest.getRequestURI docs.
// It should not include the scheme or authority parts.
@Override
public String getRequestURI() {
String[] split = splitTargetUrl( getTargetUrl() );
if( split.length > 0 ) {
return split[0];
} else {
return "";
}
}
@Override
public String getQueryString() {
String[] split = splitTargetUrl( getTargetUrl() );
if( split.length > 1 ) {
try {
return URLDecoder.decode(split[1], StandardCharsets.UTF_8.name());
} catch ( UnsupportedEncodingException e ) {
LOG.failedToDecodeQueryString(split[1], e);
return split[1];
}
} else {
return null;
}
}
private String rewriteValue( UrlRewriter rewriter, String value, String rule ) {
try {
Template input = Parser.parseLiteral( value );
Template output = rewriter.rewrite( this, input, UrlRewriter.Direction.IN, rule );
value = output.getPattern();
} catch( URISyntaxException e ) {
LOG.failedToParseValueForUrlRewrite( value );
}
return value;
}
@Override
public String getHeader( String name ) {
String value = null;
if (name.equalsIgnoreCase("Host")) {
String uri = getRequestURI();
try {
URL url = new URL(uri);
value = url.getHost();
// by the time the targetUrl is set as a request
// attribute it has already been rewritten just
// just return it from here without additional rewrite
return value;
} catch (MalformedURLException e) {
value = null;
}
}
value = super.getHeader( name );
if( value != null ) {
value = rewriteValue( rewriter, super.getHeader( name ), pickFirstRuleWithEqualsIgnoreCasePathMatch( headersFilterConfig, name ) );
}
return value;
}
@SuppressWarnings("unchecked")
@Override
public Enumeration getHeaders( String name ) {
return new EnumerationRewriter( rewriter, super.getHeaders( name ), pickFirstRuleWithEqualsIgnoreCasePathMatch( headersFilterConfig, name ) );
}
@Override
public List<String> resolve( String name ) {
return Arrays.asList( config.getInitParameter( name ) );
}
private class EnumerationRewriter implements Enumeration<String> {
private UrlRewriter rewriter;
private Enumeration<String> delegate;
private String rule;
private EnumerationRewriter( UrlRewriter rewriter, Enumeration<String> delegate, String rule ) {
this.rewriter = rewriter;
this.delegate = delegate;
this.rule = rule;
}
@Override
public boolean hasMoreElements() {
return delegate.hasMoreElements();
}
@Override
public String nextElement() {
return rewriteValue( rewriter, delegate.nextElement(), rule );
}
}
@Override
public ServletInputStream getInputStream() throws IOException {
ServletInputStream input = super.getInputStream();
if( getContentLength() != 0 ) {
MimeType mimeType = getMimeType();
/* In cases where content type is application/text and content-encoding is gzip */
final String contentEncoding = getHeader(
HttpHeader.CONTENT_ENCODING.asString());
if (!StringUtils.isBlank(contentEncoding) && StringUtils
.containsAny(contentEncoding, "gzip", "compress", "deflate", "br")) {
/* This prevents adding filters based on content-type, which in most cases would not be
* an issue but in cases where request specifies additional encoding this causes issues
* see KNOX-1412
*/
mimeType = MimeTypes
.create("application/" + contentEncoding, getCharacterEncoding());
}
UrlRewriteFilterContentDescriptor filterContentConfig = getRewriteFilterConfig( bodyFilterName, mimeType );
if (filterContentConfig != null) {
String asType = filterContentConfig.asType();
if ( asType != null && asType.trim().length() > 0 ) {
mimeType = MimeTypes.create(asType, getCharacterEncoding());
}
}
final InputStream stream;
UrlRewriteStreamFilter filter = UrlRewriteStreamFilterFactory.create(mimeType, null);
if(filter != null) {
String charset = MimeTypes.getCharset( mimeType, StandardCharsets.ISO_8859_1.name() );
stream = filter.filter(input, charset, rewriter, this, UrlRewriter.Direction.IN, filterContentConfig );
} else {
stream = input;
}
input = new UrlRewriteRequestStream( stream );
}
return input;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader( new InputStreamReader( getInputStream(), getCharacterEncoding() ) );
}
@Override
public int getContentLength() {
// The rewrite might change the content length so return the default of -1 to indicate the length is unknown.
int contentLength = super.getContentLength();
if( contentLength > 0 ) {
contentLength = -1;
}
return contentLength;
}
private UrlRewriteFilterContentDescriptor getRewriteFilterConfig( String filterName, MimeType mimeType ) {
UrlRewriteFilterContentDescriptor filterContentConfig = null;
UrlRewriteRulesDescriptor rewriteConfig = rewriter.getConfig();
if( rewriteConfig != null ) {
UrlRewriteFilterDescriptor filterConfig = rewriteConfig.getFilter( filterName );
if( filterConfig != null ) {
filterContentConfig = filterConfig.getContent( mimeType );
}
}
return filterContentConfig;
}
}