org.owasp.fileio.Encoder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-file-io Show documentation
Show all versions of java-file-io Show documentation
The OWASP Java File I/O Security Project provides an easy to use library for validating and sanitizing filenames, directory paths, and uploaded files.
The newest version!
/**
* This file is part of the Open Web Application Security Project (OWASP) Java File IO Security project. For details, please see
* https://www.owasp.org/index.php/OWASP_Java_File_I_O_Security_Project.
*
* Copyright (c) 2014 - The OWASP Foundation
*
* This API is published by OWASP under the Apache 2.0 license. You should read and accept the LICENSE before you use, modify, and/or redistribute this software.
*
* @author Jeff Williams Aspect Security - Original ESAPI author
* @author August Detlefsen CodeMagi - Java File IO Security Project lead
* @created 2014
*/
package org.owasp.fileio;
import org.owasp.fileio.util.Utils;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.owasp.fileio.codecs.Codec;
import org.owasp.fileio.codecs.HTMLEntityCodec;
import org.owasp.fileio.codecs.PercentCodec;
/**
* Reference implementation of the Encoder interface. This implementation takes a whitelist approach to encoding, meaning that everything not specifically identified in a list of "immune" characters
* is encoded.
*/
public class Encoder {
private static volatile Encoder singletonInstance;
public final static char[] CHAR_ALPHANUMERICS = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
public static final Set ALPHANUMERICS;
static {
ALPHANUMERICS = Utils.arrayToSet(Encoder.CHAR_ALPHANUMERICS);
}
private boolean restrictMultiple = true;
private boolean restrictMixed = true;
public boolean isRestrictMultiple() {
return restrictMultiple;
}
public void setRestrictMultiple(boolean restrictMultiple) {
this.restrictMultiple = restrictMultiple;
}
public boolean isRestrictMixed() {
return restrictMixed;
}
public void setRestrictMixed(boolean restrictMixed) {
this.restrictMixed = restrictMixed;
}
public static Encoder getInstance() {
if (singletonInstance == null) {
synchronized (Encoder.class) {
if (singletonInstance
== null) {
singletonInstance = new Encoder();
}
}
}
return singletonInstance;
}
// Codecs
private List codecs = new ArrayList();
private HTMLEntityCodec htmlCodec = new HTMLEntityCodec();
private PercentCodec percentCodec = new PercentCodec();
/**
* Instantiates a new DefaultEncoder with the default codecs
*/
public Encoder() {
codecs.add(htmlCodec);
codecs.add(percentCodec);
}
/**
* Instantiates a new DefaultEncoder with the default codecs
* @param codecs A List of Codecs to use
*/
public Encoder(List codecs) {
this.codecs = codecs;
}
/**
* {@inheritDoc}
*/
public String canonicalize(String input) {
if (input == null) {
return null;
}
// Issue 231 - These are reverse boolean logic in the Encoder interface, so we need to invert these values - CS
return canonicalize(input, restrictMultiple, restrictMixed);
}
/**
* {@inheritDoc}
*/
public String canonicalize(String input, boolean strict) {
return canonicalize(input, strict, strict);
}
/**
* {@inheritDoc}
*/
public String canonicalize(String input, boolean restrictMultiple, boolean restrictMixed) {
if (input == null) {
return null;
}
String working = input;
Codec codecFound = null;
int mixedCount = 1;
int foundCount = 0;
boolean clean = false;
while (!clean) {
clean = true;
// try each codec and keep track of which ones work
Iterator i = codecs.iterator();
while (i.hasNext()) {
Codec codec = (Codec) i.next();
String old = working;
working = codec.decode(working);
if (!old.equals(working)) {
if (codecFound != null && codecFound != codec) {
mixedCount++;
}
codecFound = codec;
if (clean) {
foundCount++;
}
clean = false;
}
}
}
// do strict tests and handle if any mixed, multiple, nested encoding were found
if (foundCount >= 2 && mixedCount > 1) {
if (restrictMultiple || restrictMixed) {
//TODO: throw new ValidationException("Input validation failure", "Multiple (" + foundCount + "x) and mixed encoding (" + mixedCount + "x) detected in " + input);
} else {
//TODO: logger.warning(Logger.SECURITY_FAILURE, "Multiple (" + foundCount + "x) and mixed encoding (" + mixedCount + "x) detected in " + input);
}
} else if (foundCount >= 2) {
if (restrictMultiple) {
//TODO: throw new ValidationException("Input validation failure", "Multiple (" + foundCount + "x) encoding detected in " + input);
} else {
//TODO: logger.warning(Logger.SECURITY_FAILURE, "Multiple (" + foundCount + "x) encoding detected in " + input);
}
} else if (mixedCount > 1) {
if (restrictMixed) {
//TODO: throw new ValidationException("Input validation failure", "Mixed encoding (" + mixedCount + "x) detected in " + input);
} else {
//TODO: logger.warning(Logger.SECURITY_FAILURE, "Mixed encoding (" + mixedCount + "x) detected in " + input);
}
}
return working;
}
}