/*************************************************************************
* Copyright 2009-2013 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.metadata;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import com.google.common.collect.Lists;
/**
* Instantiates and holds a set of validation rules for bucket names.
*
* The rules were retrieved from - http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html on March 26, 2014
*
* @author Wes.Wannemacher@eucalyptus.com
*/
public class BucketNameValidatorRepo {
private static final Validator<String> extendedValidator = setupExtended();
private static final Validator<String> dnsCompliantValidator = setupDnsCompliant();
private static final Logger LOG = Logger.getLogger(BucketNameValidatorRepo.class);
private static final Pattern IP_MATCHER = Pattern.compile("^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$");
public static Validator<String> getBucketNameValidator(final String restrictionValue) {
if (restrictionValue != null && "extended".equalsIgnoreCase(restrictionValue)) {
return extendedValidator;
} else if (restrictionValue != null && "dns-compliant".equalsIgnoreCase(restrictionValue)) {
return dnsCompliantValidator;
} else {
return new Validator<String>() {
@Override
public boolean check(String value) {
LOG.error("the value " + restrictionValue + " is not valid, must be either 'extended' or 'dns-compliant'. No validation "
+ "will be done on the specified bucket name (may result in errors).");
if (value != null && value.length() > 0) {
return true;
}
return false;
}
};
}
}
private static Validator<String> setupExtended() {
IteratingValidator<String> extended = new IteratingValidator<String>();
List<Validator<String>> checks = Lists.newArrayListWithExpectedSize(3);
checks.add(new Validator<String>() {
// not null // sanity
@Override
public boolean check(String bucketName) {
return bucketName != null && bucketName.length() > 1;
}
});
checks.add(new Validator<String>() {
// not more than 255 characters
@Override
public boolean check(String bucketName) {
return bucketName.length() <= 255;
}
});
checks.add(new Validator<String>() {
// any combination of uppercase letters, lowercase letters,
// numbers, periods (.), dashes (-) and underscores (_)
@Override
public boolean check(String bucketName) {
for (char ch : bucketName.toCharArray()) {
boolean isBad = (!isLowerOrNumber(ch) && !(ch >= 'A' && ch <= 'Z') && ch != '.' && ch != '-' && ch != '_');
if (isBad) {
return false;
}
}
return true;
}
});
extended.setValidators(checks);
return extended;
}
private static Validator<String> setupDnsCompliant() {
IteratingValidator<String> dnsCompliant = new IteratingValidator<String>();
List<Validator<String>> checks = Lists.newArrayListWithExpectedSize(5);
checks.add(extendedValidator);
checks.add(new Validator<String>() {
// Bucket names must be at least 3 and no more than 63 characters long.
@Override
public boolean check(String bucketName) {
return bucketName.length() >= 3 && bucketName.length() <= 63;
}
});
checks.add(new Validator<String>() {
// make sure the name does not start or end with a period
@Override
public boolean check(String bucketName) {
if (bucketName.charAt(0) == '.' || bucketName.charAt(bucketName.length() - 1) == '.') {
return false;
}
return true;
}
});
checks.add(new Validator<String>() {
// Bucket names must be a series of one or more labels. Adjacent labels
// are separated by a single period (.). Bucket names can contain lowercase
// letters, numbers, and dashes. Each label must start and end with a
// lowercase letter or a number.
@Override
public boolean check(String value) {
List<String> labels;
if (value.contains(".")) {
// using StringTokenizer so that we can check for consecutive periods, plus
// rumor has it that StringTokenizer may be faster than String.split[*]
// * [citation needed]
StringTokenizer tokenizer = new StringTokenizer(value, ".", true);
labels = Lists.newArrayList();
String lastTok = "";
while (tokenizer.hasMoreElements()) {
String curTok = tokenizer.nextToken();
if (!".".equals(curTok)) {
labels.add(curTok);
} else if (".".equals(lastTok) && ".".equals(curTok)) {
return false;
}
lastTok = curTok;
}
} else {
labels = Lists.newArrayListWithExpectedSize(1);
labels.add(value);
}
for (String label : labels) {
char[] asChars = label.toCharArray();
// Each label must start and end with a
// lowercase letter or a number.
if (!isLowerOrNumber(asChars[0]) || !isLowerOrNumber(asChars[asChars.length - 1])) {
return false;
}
// Bucket names can contain lowercase
// letters, numbers, and dashes.
for (char ch : asChars) {
if (!(isLowerOrNumber(ch) || ch == '-')) {
return false;
}
}
}
return true;
}
});
checks.add(new Validator<String>() {
// Bucket names must not be formatted as an IP address
// (e.g., 192.168.5.4).
@Override
public boolean check(String value) {
return !IP_MATCHER.matcher(value).matches();
}
});
dnsCompliant.setValidators(checks);
return dnsCompliant;
}
private static class IteratingValidator<T> implements Validator<T> {
private List<Validator<T>> validators;
@Override
public boolean check(T bucketName) {
// if no checks have been configured, it's probably best to consider the check a "pass"
if (getValidators().size() < 1) {
return true;
}
for (Validator<T> checker : getValidators()) {
if (!checker.check(bucketName)) {
return false;
}
}
return true;
}
public List<Validator<T>> getValidators() {
if (validators == null) {
return Lists.newArrayList();
}
return validators;
}
public void setValidators(List<Validator<T>> validators) {
this.validators = validators;
}
}
private static boolean isLowerOrNumber(char c) {
if ((c >= 'a' && c <= 'z') // lowercase
|| (c >= '0' && c <= '9')) { // number
return true;
} else {
return false;
}
}
}