/*************************************************************************
* Copyright 2009-2012 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
*
* This file may incorporate work covered under the following copyright
* and permission notice:
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2008, Regents of the University of California
* All rights reserved.
*
* Redistribution and use of this software in source and binary forms,
* with or without modification, are permitted provided that the
* following conditions are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE
* THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL,
* COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE,
* AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
* IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA,
* SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY,
* WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION,
* REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO
* IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT
* NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS.
************************************************************************/
package com.eucalyptus.objectstorage.util;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.crypto.Cipher;
import org.apache.log4j.Logger;
import org.bouncycastle.util.encoders.Base64;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBufferIndexFinder;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.xbill.DNS.Name;
import org.xbill.DNS.TextParseException;
import com.eucalyptus.component.ComponentId;
import com.eucalyptus.component.auth.SystemCredentials;
import com.eucalyptus.crypto.Ciphers;
import com.eucalyptus.crypto.Crypto;
import com.eucalyptus.http.MappingHttpRequest;
import com.eucalyptus.http.MappingHttpResponse;
import com.eucalyptus.objectstorage.ObjectStorage;
import com.eucalyptus.objectstorage.entities.ObjectStorageGlobalConfiguration;
import com.eucalyptus.objectstorage.exceptions.ObjectStorageException;
import com.eucalyptus.objectstorage.exceptions.s3.InvalidAddressingHeaderException;
import com.eucalyptus.objectstorage.exceptions.s3.S3Exception;
import com.eucalyptus.objectstorage.msgs.HeadObjectResponseType;
import com.eucalyptus.objectstorage.msgs.ObjectStorageCommonResponseType;
import com.eucalyptus.objectstorage.msgs.ObjectStorageDataResponseType;
import com.eucalyptus.objectstorage.msgs.ObjectStorageErrorMessageType;
import com.eucalyptus.storage.config.ConfigurationCache;
import com.eucalyptus.storage.msgs.s3.CorsMatchResult;
import com.eucalyptus.storage.msgs.s3.CorsRule;
import com.eucalyptus.util.EucalyptusCloudException;
import com.eucalyptus.util.FUtils;
import com.eucalyptus.util.Internets;
import com.eucalyptus.util.dns.DomainNames;
import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.net.InetAddresses;
import edu.ucsb.eucalyptus.msgs.BaseMessage;
import edu.ucsb.eucalyptus.msgs.EucalyptusErrorMessageType;
import edu.ucsb.eucalyptus.msgs.ExceptionResponseType;
public class OSGUtil {
private static Logger LOG = Logger.getLogger(OSGUtil.class);
private static final Splitter hostSplitter = Splitter.on(':').limit(2);
private static final Function<String,Pattern> cnamePatternBuilder = new Function<String, Pattern>( ) {
@Override
public Pattern apply( @Nullable final String list ) {
return buildPatternFromWildcardList( Strings.nullToEmpty( list ) );
}
private Pattern buildPatternFromWildcardList( @Nonnull final String list ) {
final Splitter splitter =
Splitter.on( CharMatcher.WHITESPACE.or( CharMatcher.anyOf( ",;|" ) ) ).trimResults( ).omitEmptyStrings( );
final Splitter wildSplitter = Splitter.on( '*' );
final Joiner wildJoiner = Joiner.on( ".*");
final StringBuilder builder = new StringBuilder( );
builder.append( "(?i)(-|" );
for ( final String cnameWildcard : splitter.split( list ) ) {
builder.append( wildJoiner.join( Iterables.transform( wildSplitter.split( cnameWildcard ), Pattern::quote ) ) );
builder.append( '|' );
}
builder.append( "-)" );
return Pattern.compile( builder.toString( ) );
}
};
private static final Function<String,Pattern> memoizedCnamePatternBuilder = FUtils.memoizeLast( cnamePatternBuilder );
public static BaseMessage convertErrorMessage(ExceptionResponseType errorMessage) {
Throwable ex = errorMessage.getException();
String correlationId = errorMessage.getCorrelationId();
BaseMessage errMsg = null;
if ((errMsg = convertException(correlationId, ex)) == null) {
errMsg = errorMessage;
}
return errMsg;
}
public static BaseMessage convertErrorMessage(EucalyptusErrorMessageType errorMessage) {
Throwable ex = errorMessage.getException();
String correlationId = errorMessage.getCorrelationId();
BaseMessage errMsg = null;
if ((errMsg = convertException(correlationId, ex)) == null) {
errMsg = errorMessage;
}
return errMsg;
}
private static BaseMessage convertException(String correlationId, Throwable ex) {
BaseMessage errMsg;
if (ex instanceof S3Exception){
S3Exception e = (S3Exception) ex;
errMsg =
new ObjectStorageErrorMessageType(e.getMessage(), e.getCode(), e.getStatus(), e.getResourceType(), e.getResource(), correlationId,
Internets.localHostAddress(), e.getLogData(), e.getRequestMethod());
} else if (ex instanceof ObjectStorageException) {
ObjectStorageException e = (ObjectStorageException) ex;
errMsg =
new ObjectStorageErrorMessageType(e.getMessage(), e.getCode(), e.getStatus(), e.getResourceType(), e.getResource(), correlationId,
Internets.localHostAddress(), e.getLogData());
} else {
return null;
}
errMsg.setCorrelationId(correlationId);
return errMsg;
}
public static String URLdecode(String objectKey) throws UnsupportedEncodingException {
return URLDecoder.decode(objectKey, "UTF-8");
}
public static CorsMatchResult matchCorsRules (List<CorsRule> corsRules, String requestOrigin,
String requestMethod) {
// All S3 operations except Preflight (OPTIONS) requests call matchCorsRules with this signature,
// because "Access-Control-Request-Headers" are only present (if at all) in preflight requests.
return matchCorsRules(corsRules, requestOrigin, requestMethod, null);
}
public static CorsMatchResult matchCorsRules (List<CorsRule> corsRules, String requestOrigin,
String requestMethod, List<String> requestHeaders) {
// Only Preflight (OPTIONS) requests call matchCorsRules with this signature.
CorsMatchResult corsMatchResult = new CorsMatchResult();
boolean found = false;
boolean anyOrigin = false;
CorsRule corsRuleMatch = null;
// Predicate for matching origin
Predicate<String> originMatch = new Predicate<String>() {
@Override
public boolean apply(String allowedOrigin) {
String allowedOriginRegex = "\\Q" + allowedOrigin.replace("*", "\\E.*?\\Q") + "\\E";
return Pattern.matches(allowedOriginRegex, requestOrigin);
}
};
// Predicate for matching method
Predicate<String> methodMatch = new Predicate<String>() {
@Override
public boolean apply(String allowedMethod) {
return requestMethod.equals(allowedMethod);
}
};
// Function for generating a pattern from an allowed header
Function<String, Pattern> generatePattern = new Function<String, Pattern>() {
@Override
public Pattern apply(String allowedHeader) {
String allowedHeaderRegex = "\\Q" + allowedHeader.replace("*", "\\E.*?\\Q") + "\\E";
return Pattern.compile(allowedHeaderRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
}
};
for (CorsRule corsRule : corsRules ) {
if (corsRule == null) {
continue;
}
corsRuleMatch = corsRule; // will only be used if we find a match
// Does the origin match any origin's regular expression in the rule?
// Note: AWS matches origins case-sensitively! Even though URL domains
// are typically case-insensitive. Follow AWS's behavior.
List<String> allowedOrigins = corsRule.getAllowedOrigins();
if (allowedOrigins == null || allowedOrigins.isEmpty()) {
continue;
}
String matchingOrigin = Iterables.tryFind(corsRule.getAllowedOrigins(), originMatch).orNull();
// If no matching origin, skip to the next CORS rule
if (matchingOrigin == null) {
continue;
}
// We did find a matching origin. If it's "*", then any origin can access our resources.
// This is a special case we want to flag for easy access.
anyOrigin = (matchingOrigin.equals("*"));
// Does the HTTP verb match any verb in the rule?
// If not, skip to the next CORS rule
List<String> allowedMethods = corsRule.getAllowedMethods();
if (allowedMethods == null || allowedMethods.isEmpty() ||
!Iterables.any(corsRule.getAllowedMethods(), methodMatch)) {
continue;
}
// Yes, the HTTP verb matches a verb in this rule.
// If there are no Access-Control-Request-Headers, or if there are
// no AllowedHeaders in the CORS rule, then skip this check.
// We have matched the current CORS rule. Stop looking through them.
if (requestHeaders == null || requestHeaders.isEmpty() ||
corsRule.getAllowedHeaders() == null || corsRule.getAllowedHeaders().isEmpty()) {
found = true;
break;
}
// Does every request header in the comma-delimited list in
// Access-Control-Request-Headers have a matching entry in the
// allowed headers in the rule?
// Headers are matched case-insensitively.
List<String> allowedHeaders = corsRule.getAllowedHeaders();
List<Pattern> allowedHeaderPatternList = new ArrayList<Pattern>(allowedHeaders.size());
// Predicate for matching request header with allowed headers
Predicate<String> headerMatch = new Predicate<String>() {
@Override
public boolean apply(String requestHeader) {
for (int idx = 0; idx < allowedHeaders.size(); idx++) {
Pattern allowedHeaderPattern;
// Only generate the pattern if we haven't already
if (idx >= allowedHeaderPatternList.size()) {
allowedHeaderPattern = generatePattern.apply(allowedHeaders.get(idx));
allowedHeaderPatternList.add(allowedHeaderPattern);
} else {
allowedHeaderPattern = allowedHeaderPatternList.get(idx);
}
Matcher matcher = allowedHeaderPattern.matcher(requestHeader);
if (matcher.matches()) {
return true; // stop looking through the allowed headers for this request header
} else {
continue; // try matching request header with allowed header until a match is found
}
}
return false; // No allowed header matches this request header, so this rule fails to match
}
};
// If request headers match allowed headers, we have matched the current CORS rule.
// Stop looking through them.
if (Iterables.all(requestHeaders, headerMatch)) {
found = true;
break;
}
} // end for each CORS rule
if (found) {
corsMatchResult.setCorsRuleMatch(corsRuleMatch);
corsMatchResult.setAnyOrigin(anyOrigin);
}
return corsMatchResult;
}
public static void addCorsResponseHeaders (MappingHttpResponse mappingHttpResponse) throws S3Exception {
if (mappingHttpResponse != null &&
mappingHttpResponse.getMessage() instanceof ObjectStorageCommonResponseType)
{
addCorsResponseHeaders((HttpResponse) mappingHttpResponse, (ObjectStorageCommonResponseType) mappingHttpResponse.getMessage());
}
}
public static void addCorsResponseHeaders (HttpResponse httpResponse, ObjectStorageCommonResponseType response) throws S3Exception {
if (response.getAllowedOrigin() == null) {
// If no allowed origin header then we know there are no CORS headers at all
return;
}
httpResponse.setHeader(HttpHeaders.Names.ACCESS_CONTROL_ALLOW_ORIGIN, response.getAllowedOrigin());
if (response.getAllowedMethods() != null) {
httpResponse.setHeader(HttpHeaders.Names.ACCESS_CONTROL_ALLOW_METHODS, response.getAllowedMethods());
}
if (response.getExposeHeaders() != null) {
httpResponse.setHeader(HttpHeaders.Names.ACCESS_CONTROL_EXPOSE_HEADERS, response.getExposeHeaders());
}
if (response.getMaxAgeSeconds() != null) {
httpResponse.setHeader(HttpHeaders.Names.ACCESS_CONTROL_MAX_AGE, response.getMaxAgeSeconds());
}
if (response.getAllowCredentials() != null) {
httpResponse.setHeader(HttpHeaders.Names.ACCESS_CONTROL_ALLOW_CREDENTIALS, response.getAllowCredentials());
}
if (response.getVary() != null) {
httpResponse.setHeader(HttpHeaders.Names.VARY, response.getVary());
}
} // end addCorsResponseHeaders()
public static String[] getTarget(String operationPath) {
operationPath = operationPath.replaceAll("^/{2,}", "/"); // If its in the form "/////bucket/key", change it to "/bucket/key"
if (operationPath.startsWith("/")) { // If its in the form "/bucket/key", change it to "bucket/key"
operationPath = operationPath.substring(1);
}
String[] parts = operationPath.split("/", 2); // Split into a maximum of two parts [bucket, key]
if (parts != null) {
if (parts.length == 1 && Strings.isNullOrEmpty(parts[0])) { // Splitting empty string will lead one part, check if the part is empty
return null;
} else if (parts.length == 2 && Strings.isNullOrEmpty(parts[1])) { // Splitting "bucket/" will lead to two parts where the second one is empty,
// send only bucket
return new String[] {parts[0]};
}
}
return parts;
}
/**
* Returns index where bytesToFind begins in the buffer
*/
public static class ByteMatcherBeginningIndexFinder implements ChannelBufferIndexFinder {
private byte[] toMatch;
public ByteMatcherBeginningIndexFinder(byte[] bytesToFind) {
this.toMatch = bytesToFind;
}
@Override
public boolean find(ChannelBuffer channelBuffer, int i) {
channelBuffer.markReaderIndex();
try {
int matchedCount = 0;
// Check basic params, like length
if (i + this.toMatch.length > channelBuffer.readableBytes()) {
return false;
}
// Match byte for byte
for (int j = i; j - i < this.toMatch.length; j++) {
if (channelBuffer.getByte(j) != this.toMatch[j - i]) {
return false;
}
matchedCount++;
}
return matchedCount == toMatch.length;
} catch (IndexOutOfBoundsException e) {
return false;
} finally {
channelBuffer.resetReaderIndex();
}
}
}
public static int findFirstMatchInBuffer(ChannelBuffer buffer, int start, byte[] bytesToFind) {
return buffer.indexOf(start, buffer.readableBytes(), new ByteMatcherBeginningIndexFinder(bytesToFind));
}
public static int findLastMatchInBuffer(ChannelBuffer buffer, int start, byte[] bytesToFind) {
return buffer.indexOf(buffer.readableBytes(), start, new ByteMatcherBeginningIndexFinder(bytesToFind));
}
// Encrypt data using the cloud public key
public static String encryptWithComponentPublicKey(Class<? extends ComponentId> componentClass, String data) throws EucalyptusCloudException {
try {
PublicKey clcPublicKey = SystemCredentials.lookup(componentClass).getCertificate().getPublicKey();
Cipher cipher = Ciphers.RSA_PKCS1.get();
cipher.init(Cipher.ENCRYPT_MODE, clcPublicKey, Crypto.getSecureRandomSupplier().get());
return new String(Base64.encode(cipher.doFinal(data.getBytes("UTF-8"))));
} catch (Exception e) {
throw new EucalyptusCloudException("Unable to encrypt data: " + e.getMessage(), e);
}
}
// Decrypt data encrypted with the Cloud public key
public static String decryptWithComponentPrivateKey(Class<? extends ComponentId> componentClass, String data) throws EucalyptusCloudException {
PrivateKey clcPrivateKey = SystemCredentials.lookup(componentClass).getPrivateKey();
try {
Cipher cipher = Ciphers.RSA_PKCS1.get();
cipher.init(Cipher.DECRYPT_MODE, clcPrivateKey, Crypto.getSecureRandomSupplier().get());
return new String(cipher.doFinal(Base64.decode(data)));
} catch (Exception ex) {
throw new EucalyptusCloudException("Unable to decrypt data with cloud private key", ex);
}
}
public static void addCopiedHeadersToResponse(HttpResponse httpResponse, ObjectStorageDataResponseType osgResponse) {
if (osgResponse instanceof HeadObjectResponseType && osgResponse.getContentDisposition() != null
&& !"".equals(osgResponse.getContentDisposition())) {
httpResponse.addHeader("Content-Disposition", osgResponse.getContentDisposition());
}
if (osgResponse.getContentEncoding() != null && !"".equals(osgResponse.getContentEncoding())) {
httpResponse.addHeader(HttpHeaders.Names.CONTENT_ENCODING, osgResponse.getContentEncoding());
}
if (osgResponse.getCacheControl() != null && !"".equals(osgResponse.getCacheControl())) {
httpResponse.addHeader(HttpHeaders.Names.CACHE_CONTROL, osgResponse.getCacheControl());
}
if (osgResponse.getExpires() != null && !"".equals(osgResponse.getExpires())) {
httpResponse.addHeader(HttpHeaders.Names.EXPIRES, osgResponse.getExpires());
}
}
/**
* Utility method to determine if the inbound request is for uploading data using the OSG PUT mechanism
*
* @param request
* @return true if the request is of type S3 PUT object or upload part. false for all other requests
*/
public static boolean isPUTDataRequest(HttpRequest request, Set<String> servicePaths) {
MappingHttpRequest mappingRequest = null;
// put data and upload part only
if (request.getMethod().getName().equals(ObjectStorageProperties.HTTPVerb.PUT.toString()) && request instanceof MappingHttpRequest
&& !isBucketOp((mappingRequest = (MappingHttpRequest) request), servicePaths)
&& (mappingRequest.getParameters() == null || mappingRequest.getParameters().isEmpty()
|| mappingRequest.getParameters().containsKey("progressbar_label") // See EUCA-13210
|| mappingRequest.getParameters().containsKey(ObjectStorageProperties.SubResource.uploadId.toString())
|| mappingRequest.getParameters().containsKey(ObjectStorageProperties.SubResource.partNumber.toString())
|| mappingRequest.getParameters().containsKey(ObjectStorageProperties.SubResource.uploadId.toString().toLowerCase())
|| mappingRequest.getParameters().containsKey(ObjectStorageProperties.SubResource.partNumber.toString().toLowerCase()))) {
return true;
}
return false;
}
/**
* Utility method to determine if the inbound request is for updating the metadata of buckets or objects
*
* @param request
* @return true if the request is of type S3 PUT acl, versioning, lifecycle, tagging, notification, initiate mpu - POST uploads, complete mpu - POST
* uploadId, mult delete - POST delete. false for all other requests
*/
public static boolean isPUTMetadataRequest(HttpRequest request, Set<String> servicePaths) {
MappingHttpRequest mappingRequest = null;
String contentType = null;
// put metadata only - any bucket operation should be considered as metadata
if (request.getMethod().getName().equals(ObjectStorageProperties.HTTPVerb.PUT.toString()) && request instanceof MappingHttpRequest
&& (isBucketOp((mappingRequest = (MappingHttpRequest) request), servicePaths)
|| (mappingRequest.getParameters() != null && !mappingRequest.getParameters().isEmpty()
&& !mappingRequest.getParameters().containsKey("progressbar_label") // See EUCA-13210
&& !mappingRequest.getParameters().containsKey(ObjectStorageProperties.SubResource.uploadId.toString())
&& !mappingRequest.getParameters().containsKey(ObjectStorageProperties.SubResource.partNumber.toString())
&& !mappingRequest.getParameters().containsKey(ObjectStorageProperties.SubResource.uploadId.toString().toLowerCase())
&& !mappingRequest.getParameters().containsKey(ObjectStorageProperties.SubResource.partNumber.toString().toLowerCase())))) {
return true;
}
// post for initiate mpu, complete mpu and multi delete
if (request.getMethod().getName().equals(ObjectStorageProperties.HTTPVerb.POST.toString())
&& ((contentType = request.getHeader(HttpHeaders.Names.CONTENT_TYPE)) == null || !contentType.startsWith("multipart/form-data;"))
&& request instanceof MappingHttpRequest && (mappingRequest = ((MappingHttpRequest) request)).getParameters() != null
&& !mappingRequest.getParameters().isEmpty()) {
return true;
}
return false;
}
/**
* Utility method to determine if the inbound request is for uploading form data using the OSG POST mechanism
*
* @param request
* @return true if the request of type S3 form POST. false for all other requests
*/
public static boolean isFormPOSTRequest(HttpRequest request) {
String contentType = null;
// post form upload only
if (request.getMethod().getName().equals(ObjectStorageProperties.HTTPVerb.POST.toString())
&& (contentType = request.getHeader(HttpHeaders.Names.CONTENT_TYPE)) != null && contentType.startsWith("multipart/form-data;")) {
return true;
}
return false;
}
/**
* A name can be a bucket name if it is an object storage subdomain or if it
* is not a system subdomain (bucket cname)
*/
public static boolean isBucketName( final Name name, final boolean allowCname ) {
final Optional<Name> systemDomain = DomainNames.systemDomainFor( ObjectStorage.class, name );
return systemDomain.isPresent( ) || (allowCname && isAllowedBucketHost( name ));
}
public static String getBucketFromHostHeader(
final MappingHttpRequest httpRequest
) throws InvalidAddressingHeaderException, TextParseException {
String hostBucket = null;
String targetHost = httpRequest.getHeader(HttpHeaders.Names.HOST);
if (!Strings.isNullOrEmpty(targetHost)) {
final String host = Iterables.getFirst(hostSplitter.split(targetHost), targetHost);
if ( host != null ) {
final Name hostDnsName = DomainNames.absolute(Name.fromString(host));
final Optional<Name> systemDomain = DomainNames.systemDomainFor(ObjectStorage.class, hostDnsName);
if (systemDomain.isPresent()) {
// dns-style request
hostBucket = hostDnsName.relativize(systemDomain.get()).toString();
if (hostBucket.length() == 0) {
throw new InvalidAddressingHeaderException(null, "Invalid Host header: " + targetHost);
}
} else if ( isAllowedBucketHost( hostDnsName, host ) ) {
hostBucket = host;
}
}
}
return hostBucket == null ? null : hostBucket.toLowerCase( );
}
public static boolean isAllowedBucketHost(
@Nonnull final Name name
) {
return isAllowedBucketHost( name, name.relativize( Name.root ).toString( ) );
}
public static boolean isAllowedBucketHost(
@Nonnull final Name name,
@Nonnull final String nameText
) {
return !InetAddresses.isInetAddress( nameText ) &&
!DomainNames.isSystemSubdomain( name ) &&
!isReservedBucketCname( nameText );
}
public static boolean isReservedBucketCname( @Nonnull final String nameText ) {
final String reservedCnamePatternList =
ConfigurationCache.getConfiguration( ObjectStorageGlobalConfiguration.class ).getBucket_reserved_cnames( );
return memoizedCnamePatternBuilder.apply( reservedCnamePatternList ).matcher( nameText ).matches( );
}
public static boolean isBucketOp(MappingHttpRequest httpRequest, Set<String> servicePaths) {
try {
String hostBucket = null;
String[] target = null;
String path = getOperationPath(httpRequest, servicePaths);
if ((hostBucket = getBucketFromHostHeader(httpRequest)) != null) {
path = "/" + hostBucket + path;
}
return (path.length() > 0 && (target = getTarget(path)) != null && target.length == 1);
} catch (Exception e) {
LOG.warn("Considering the request to be non-bucket op, unable to identify bucket and object in request due to", e);
}
return false;
}
/**
* Removes the service path for processing the bucket/key split.
*
* @param httpRequest
* @return
*/
public static String getOperationPath(MappingHttpRequest httpRequest, Set<String> servicePaths) {
for (String pathCandidate : servicePaths) {
if (httpRequest.getServicePath().startsWith(pathCandidate)) {
String opPath = httpRequest.getServicePath().replaceFirst(pathCandidate, "");
if (!Strings.isNullOrEmpty(opPath) && !opPath.startsWith("/")) {
// The service path was not demarked with a /, e.g. /services/objectstorageblahblah -> blahblah
// So, don't remove the service path because that changes the semantics.
break;
} else {
return opPath;
}
}
}
return httpRequest.getServicePath();
}
}