/*
* Copyright 2009-2012 Michael Dalton
*
* Licensed 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 jtaint;
import java.util.BitSet;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
public final class StringUtil
{
private static String doSubstring(String orig, int beginIndex, int endIndex,
String result)
{
Taint t;
if (orig == result || !orig.@internal@isTainted()
|| result.@internal@isTainted())
return result;
t = orig.@internal@taint().subset(beginIndex, endIndex);
if (t.isTainted())
return new String(result, t);
return result;
}
public static String substring(String orig, int beginIndex, int endIndex,
String result)
{
try {
return doSubstring(orig, beginIndex, endIndex, result);
} catch (Throwable th) {
Log.error(th);
return result;
}
}
private static String doConcat(String orig, String other, String result)
{
Taint t;
if ((!orig.@internal@isTainted() && !other.@internal@isTainted())
|| result.@internal@isTainted())
return result;
t = TaintUtil.append(orig.@internal@taint(), orig.length(),
other.@internal@taint(), other.length());
if (t != null)
return new String(result, t);
return result;
}
public static String concat(String orig, String other, String result)
{
try {
return doConcat(orig, other, result);
} catch (Throwable th) {
Log.error(th);
return result;
}
}
public static String doConcat(String[] s, int len, String result) {
boolean tainted = false;
for (int i = 0; i < len; i++)
if (s[i].@internal@isTainted())
tainted = true;
if (!tainted) return result;
Taint t = null;
for (int slen = 0, i = 0; i < len; slen += s[i].length(), i++)
t = TaintUtil.append(t,slen,s[i].@internal@taint(),s[i].length());
if (t.length() != result.length())
throw new RuntimeException("Taint length corruption");
if (t != null)
return new String(result, t);
return result;
}
public static String concat(String[] s, int len, String result) {
try {
return doConcat(s, len, result);
} catch (Throwable th) {
Log.error(th);
return result;
}
}
private static String doReplace(String orig, char oldChar, String result)
{
Taint t;
if (orig == result || !orig.@internal@isTainted()
|| result.@internal@isTainted())
return result;
t = orig.@internal@taint();
for (int i = 0; i < orig.length(); i++)
if (orig.charAt(i) == oldChar)
t.clear(i);
if (t.isTainted())
return new String(result, t);
return result;
}
public static String replace(String orig, char oldChar, char newChar,
String result)
{
try {
return doReplace(orig, oldChar, result);
} catch (Throwable th) {
Log.error(th);
return result;
}
}
private static String doTrim(String orig, String result)
{
Taint old;
int begin, end;
if (orig == result || !orig.@internal@isTainted()
|| result.@internal@isTainted())
return result;
old = orig.@internal@taint();
begin = orig.indexOf(result);
end = begin + result.length();
if (result.length() > 0 && (begin != orig.lastIndexOf(result)
|| begin < 0))
throw new RuntimeException("trim ?");
Taint t = old.subset(begin, end);
if (t.isTainted())
return new String(result, t);
return result;
}
public static String trim(String orig, String result)
{
try {
return doTrim(orig, result);
} catch (Throwable th) {
Log.error(th);
return result;
}
}
/** Propagate taint across toLowerCase, toUpperCase calls.
* @see java.lang.String
* @see java.lang.ConditionalSpecialCasing
* @see http://www.unicode.org/Public/UNIDATA/SpecialCasing.txt
* XXX java/lang/ConditionalSpecialCasing:toLowerCaseCharArray doesn't
* return the 'normal' lowercase conversion if it can't find a special
* case match, so we add the check ourselves (and call Character.toChars).
*/
//[ifJava5+]
private static String doChangeCase(String orig, Locale locale,
boolean isUpper, String result)
{
Taint t, old;
int olen = orig.length(),
rlen = result.length();
int ocount, rcount;
int i, j;
if (orig == result || !orig.@internal@isTainted()
|| result.@internal@isTainted())
return result;
t = new Taint(false, result.length());
old = orig.@internal@taint();
for (i = 0, j = 0; i < olen && j < rlen; i += ocount, j += rcount) {
int ochar = orig.codePointAt(i);
int rchar;
boolean tainted = false,
changed = false;
ocount = Character.charCount(ochar);
if (isUpper)
rchar = String.@internal@toUpperCaseEx(orig, i, locale);
else
rchar = String.@internal@toLowerCaseEx(orig, i, locale);
if (!String.@internal@isError(rchar))
rcount = Character.charCount(rchar);
else {
if (isUpper)
rcount = String.@internal@toUpperCaseCharArray(orig, i,
locale).length;
else {
char[] c = String.@internal@toLowerCaseCharArray(orig, i,
locale);
if (c != null)
rcount = c.length;
else {
c = new char[2];
rcount = Character.toChars(rchar, c, 0);
}
}
}
if (ocount != rcount) changed = true;
for (int k = 0; !changed && k < ocount; k++)
if (orig.charAt(i+k) != result.charAt(j+k))
changed = true;
for (int k = 0 ;k < ocount; k++)
if (old.get(i+k)) tainted = true;
if (!tainted)
continue;
if (!changed) {
for (int m = 0; m < ocount; m++)
if (old.get(i + m))
t.set(j + m);
} else {
for (int l = 0; l < rcount; l++)
t.set(j + l);
}
}
if (i != orig.length() || j != result.length())
throw new RuntimeException("toXCase conversion error: Lang "
+ locale.getLanguage() + " upper " + isUpper
+ " input " + orig + " ouput " + result);
return new String(result, t);
}
public static String toUpperCase(String orig, Locale locale, String result)
{
try {
return doChangeCase(orig, locale, true, result);
} catch (Throwable th) {
Log.error(th);
return result;
}
}
public static String toLowerCase(String orig, Locale locale, String result)
{
try {
return doChangeCase(orig, locale, false, result);
} catch (Throwable th) {
Log.error(th);
return result;
}
}
//[fiJava5+]
/* There is no ConditionalSpecialCasing class in JRE 1.4 environments, so
* we require toUpperCase to perform the same special condition tests as
* java/lang/String
*/
//[ifJava4]
public static String toLowerCase(String orig, Locale locale, String result)
{
try {
if (orig == result || !orig.@internal@isTainted()
|| result.@internal@isTainted())
return result;
if (orig.length() != result.length())
throw new RuntimeException("Unexpected toLowerCase - input: "
+ orig + " output: " + result);
return new String(result, orig.@internal@taint());
} catch (Throwable th) {
Log.error(th);
return result;
}
}
public static String toUpperCase(String orig, Locale locale, String result)
{
try {
Taint t, old;
int olen = orig.length(),
rlen = result.length();
int rcount, i, j;
if (orig == result || !orig.@internal@isTainted()
|| result.@internal@isTainted())
return result;
t = new Taint(false, result.length());
old = orig.@internal@taint();
for (i = 0, j = 0; i < olen && j < rlen; i++, j += rcount) {
char ochar = orig.charAt(i),
rchar = String.@internal@toUpperCaseEx(ochar);
boolean tainted = old.get(i),
changed = false;
if ("tr".equals(locale.getLanguage())
&& (ochar == 'i' || ochar == '\u0131'))
rcount = 1;
else if (!String.@internal@isError(rchar))
rcount = 1;
else
rcount =
String.@internal@toUpperCaseCharArray(ochar).length;
if (!tainted)
continue;
for (int l = 0; l < rcount; l++)
t.set(j + l);
}
if (i != orig.length() || j != result.length())
throw new RuntimeException("toUpper conversion error: Lang "
+ locale.getLanguage() + " input " + orig +
" ouput " + result);
return new String(result, t);
} catch (Throwable th) {
Log.error(th);
return result;
}
}
//[fiJava4]
/* The following String methods do not require further instrumentation:
* replaceAll, replaceFirst, split - all derived from the Pattern and
* Matcher class methods of the same name. These methods use
* StringBuilder.append when dealing with untrusted input subsequences,
* and thus propagation is already done by our StringBuilder taint
* propagation support. Similarly, Matcher.appendReplacement() and
* Matcher.appendTail() also does not require any further
* instrumentation other than our pre-existing changes to StringBuilder.
*/
public static Taint stringToTaint(char[] v, int count) {
try {
if (v.length <= count)
throw new RuntimeException("stringToTaint called on untainted"
+ "String: v.length:" +
v.length + " count:" + count);
char [] u = new char[v.length - count];
System.arraycopy(v, count, u, 0, v.length - count);
return new Taint(u, count);
} catch (Throwable th) {
Log.error(th);
return null;
}
}
public static char[] taintToString(String s, Taint t)
{
try {
int slen = s.length(),
tlen = t.length(),
tclen = (tlen + 15)/16;
if (slen != tlen)
throw new IllegalArgumentException("invalid taint: s len"
+ slen + " tclen " +
tclen + " taint len " +
t.length());
char[] v = new char[slen + tclen];
s.getChars(0, slen, v, 0);
t.getTaintAsChars(v, slen);
return v;
} catch (Throwable th) {
Log.error(th);
return s.toCharArray();
}
}
/* Create an untainted String */
public static String toUntainted(String str) {
try {
if (str == null || !str.@internal@isTainted()) return str;
return new String(str.toCharArray());
} catch (Throwable e) {
Log.error(e);
return str;
}
}
/* Create a tainted String */
public static String toTainted(String str) {
try {
if (str == null) return str;
return new String(str, new Taint(true, str.length()));
} catch (Throwable e) {
Log.error(e);
return str;
}
}
/* Create a tainted String array */
public static String[] toTainted(String[] a) {
try {
if (a == null) return a;
String[] r = new String[a.length];
for (int i = 0; i < a.length; i++)
r[i] = toTainted(a[i]);
return r;
} catch (Throwable e) {
Log.error(e);
return a;
}
}
/* Create a tainted StringBuffer */
public static StringBuffer toTainted(StringBuffer sb) {
try {
if (sb == null)
return sb;
return new StringBuffer(toTainted(sb.toString()));
} catch (Throwable e) {
Log.error(e);
return sb;
}
}
/* Create a tainted StringBuilder */
//[ifJava5+]
public static StringBuilder toTainted(StringBuilder sb) {
try {
if (sb == null)
return sb;
return new StringBuilder(toTainted(sb.toString()));
} catch (Throwable e) {
Log.error(e);
return sb;
}
}
//[fiJava5+]
/* Create a Map of tainted Strings -> tainted String arrays */
public static Map toTainted(Map in) {
try {
String key;
String[] value;
HashMap m = new HashMap();
if (in == null) return in;
Set entries = in.entrySet();
Map.Entry[] e = (Map.Entry[])
entries.toArray(new Map.Entry[entries.size()]);
for (int i = 0; i < e.length; i++) {
key = toTainted((String) e[i].getKey());
value = toTainted((String[]) e[i].getValue());
m.put(key, value);
}
return m;
} catch (Throwable e) {
Log.error(e);
return in;
}
}
/* Create a Hashtable of tainted strings -> tainted String arrays */
public static Hashtable toTainted(Hashtable in) {
try {
String key;
String[] value;
Hashtable ht = new Hashtable();
if (in == null) return in;
Set entries = in.entrySet();
Map.Entry[] e = (Map.Entry[])
entries.toArray(new Map.Entry[entries.size()]);
for (int i = 0; i < e.length; i++) {
key = toTainted((String) e[i].getKey());
value = toTainted((String[]) e[i].getValue());
ht.put(key, value);
}
return ht;
} catch (Throwable e) {
Log.error(e);
return in;
}
}
static final class TaintedEnumeration implements Enumeration {
Enumeration e;
public TaintedEnumeration(Enumeration e) {
this.e = e;
}
public boolean hasMoreElements() { return e.hasMoreElements(); }
public Object nextElement() {
return toTainted((String)e.nextElement());
}
}
/* Create an Enumeration of tainted Strings */
public static Enumeration toTainted(Enumeration e) {
try {
return new TaintedEnumeration(e);
} catch (Throwable th) {
Log.error(th);
return e;
}
}
}