/* * 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 java.lang; import jtaint.OrigStringUtil; import jtaint.SafeTaint; import jtaint.Taint; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Comparator; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; public final class SafeString implements Comparable, SafeCharSequence { private final OrigString str; private final SafeTaint taint; public SafeString() { str = new OrigString(); taint = new SafeTaint(); } public SafeString(SafeString s) { str = new OrigString(s.str); taint = (SafeTaint) s.taint.clone(); } public SafeString(String s) { str = new OrigString(s.toCharArray()); Taint t = s.@internal@taint(); BitSet b; if (t == null) b = new BitSet(); else b = t.asBitSet(); taint = new SafeTaint(b, s.length()); } public SafeString(char[] value) { str = new OrigString(value); taint = new SafeTaint(false, value.length); } public SafeString(char value[], int offset, int count) { str = new OrigString(value,offset, count); taint = new SafeTaint(false, count); } //[ifJava5+] public SafeString(int[] codePoints, int offset, int count) { str = new OrigString(codePoints, offset, count); taint = new SafeTaint(false, str.length()); } //[fiJava5+] public SafeString(byte ascii[], int hibyte, int offset, int count) { str = new OrigString(ascii, hibyte, offset, count); taint = new SafeTaint(false, str.length()); } public SafeString(byte ascii[], int hibyte) { str = new OrigString(ascii, hibyte); taint = new SafeTaint(false, str.length()); } public SafeString(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException { str = new OrigString(bytes, offset, length, OrigStringUtil.toOrig(charsetName)); taint = new SafeTaint(false, str.length()); } //[ifJava6] public SafeString(byte bytes[], int offset, int length, Charset charset) { str = new OrigString(bytes, offset, length, charset); taint = new SafeTaint(false, str.length()); } //[fiJava6] public SafeString(byte bytes[], String charsetName) throws UnsupportedEncodingException { str = new OrigString(bytes, OrigStringUtil.toOrig(charsetName)); taint = new SafeTaint(false, str.length()); } //[ifJava6] public SafeString(byte bytes[], Charset charset) { str = new OrigString(bytes, charset); taint = new SafeTaint(false, str.length()); } //[fiJava6] public SafeString(byte bytes[], int offset, int length) { str = new OrigString(bytes,offset,length); taint = new SafeTaint(false, str.length()); } public SafeString(byte bytes[]) { str = new OrigString(bytes); taint = new SafeTaint(false, str.length()); } public SafeString(SafeStringBuffer buffer) { this(buffer.toSafeString()); } //[ifJava5+] public SafeString(SafeStringBuilder builder) { this(builder.toSafeString()); } //[fiJava5+] public int length() { return str.length(); } //[ifJava6] public boolean isEmpty() { return str.isEmpty(); } //[fiJava6] public char charAt(int index) { return str.charAt(index); } //[ifJava5+] public int codePointAt(int index) { return str.codePointAt(index); } public int codePointBefore(int index) { return str.codePointBefore(index); } public int codePointCount(int beginIndex, int endIndex) { return str.codePointCount(beginIndex, endIndex); } public int offsetByCodePoints(int index, int codePointOffset) { return str.offsetByCodePoints(index, codePointOffset); } //[fiJava5+] public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { str.getChars(srcBegin, srcEnd, dst, dstBegin); } public void getBytes(int srcBegin, int srcEnd, byte dst[], int dstBegin) { str.getBytes(srcBegin, srcEnd, dst, dstBegin); } public byte[] getBytes(String charsetName) throws UnsupportedEncodingException { return str.getBytes(OrigStringUtil.toOrig(charsetName)); } //[ifJava6] public byte[] getBytes(Charset charset) { return str.getBytes(charset); } //[fiJava6] public byte[] getBytes() { return str.getBytes(); } public boolean equals(Object anObject) { SafeString sfs; if (anObject == this) return true; if (anObject == null || !(anObject instanceof SafeString)) return false; sfs = (SafeString) anObject; return str.equals(sfs.str); } public boolean contentEquals(SafeStringBuffer sb) { return str.contentEquals(new OrigStringBuffer(OrigStringUtil.toOrig(sb.toString()))); } //[ifJava5+] public boolean contentEquals(SafeCharSequence cs) { return str.contentEquals(cs.toSafeString().str); } //[fiJava5+] public boolean equalsIgnoreCase(SafeString anotherString) { return str.equalsIgnoreCase(anotherString.str); } public int compareTo(SafeString anotherString) { return str.compareTo(anotherString.str); } public int compareTo(Object o) { if (o instanceof SafeString) return compareTo((SafeString)o); return -1; } public int compareToIgnoreCase(SafeString anotherString) { return str.compareToIgnoreCase(anotherString.str); } public boolean regionMatches(int toffset, SafeString other, int ooffset, int len) { return str.regionMatches(toffset, other.str, ooffset, len); } public boolean regionMatches(boolean ignoreCase, int toffset, SafeString other, int ooffset, int len) { return str.regionMatches(ignoreCase, toffset, other.str, ooffset, len); } public boolean startsWith(SafeString prefix, int toffset) { return str.startsWith(prefix.str, toffset); } public boolean startsWith(SafeString prefix) { return str.startsWith(prefix.str); } public boolean endsWith(SafeString suffix) { return str.endsWith(suffix.str); } public int hashCode() { return str.hashCode(); } public int indexOf(int ch) { return str.indexOf(ch); } public int indexOf(int ch, int fromIndex) { return str.indexOf(ch, fromIndex); } public int lastIndexOf(int ch) { return str.lastIndexOf(ch); } public int lastIndexOf(int ch, int fromIndex) { return str.lastIndexOf(ch, fromIndex); } public int indexOf(SafeString sfs) { return str.indexOf(sfs.str); } public int indexOf(SafeString sfs, int fromIndex) { return str.indexOf(sfs.str, fromIndex); } public int lastIndexOf(SafeString sfs) { return str.lastIndexOf(sfs.str); } public int lastIndexOf(SafeString sfs, int fromIndex) { return str.lastIndexOf(sfs.str, fromIndex); } public SafeString substring(int beginIndex) { return substring(beginIndex, str.length()); } public SafeString substring(int beginIndex, int endIndex) { OrigString os = str.substring(beginIndex, endIndex); SafeTaint st = ((SafeTaint) taint.clone()).subset(beginIndex, endIndex); return new SafeString(os, st); } public CharSequence subSequence(int beginIndex, int endIndex) { return substring(beginIndex, endIndex); } public SafeString concat(SafeString sfs) { OrigString os = str.concat(sfs.str); SafeTaint st = ((SafeTaint)taint.clone()).append(sfs.taint); return new SafeString(os, st); } public SafeString replace(char oldChar, char newChar) { char[] v = str.toCharArray(); SafeTaint st = (SafeTaint) taint.clone(); for (int i = 0; i < v.length; i++) { if (v[i] == oldChar) { v[i] = newChar; st.clear(i); } } return new SafeString(new OrigString(v), st); } public boolean matches(SafeString regex) { return str.matches(regex.str); } //[ifJava5+] public boolean contains(SafeCharSequence s) { return str.contains(s.toSafeString().origString()); } //[fiJava5+] public SafeString replaceFirst(SafeString regex, SafeString replacement) { /* This is complicated -- we have to rely on the taint propagation * characteristics of the 'String' class here. First we translate * the current SafeString instance to a String, then we perform all * the regular expression operations, and finally we convert back to * a SafeString */ String s = toString(); return new SafeString(s.replaceFirst(regex.toString(), replacement.toString())); } public SafeString replaceAll(SafeString regex, SafeString replacement) { String s = toString(); return new SafeString(s.replaceAll(regex.toString(), replacement.toString())); } //[ifJava5] public SafeString replace(SafeCharSequence target, SafeCharSequence replacement) { String s = toString(); return new SafeString(s.replace(target.toSafeString().toString(), replacement.toSafeString().toString())); } //[fiJava5] public SafeString[] split(SafeString regex, int limit) { String s = toString(); SafeString[] ret; String[] tmp; tmp = s.split(regex.toString(), limit); ret = new SafeString[tmp.length]; for (int i = 0; i < ret.length; i++) ret[i] = new SafeString(tmp[i]); return ret; } public SafeString[] split(SafeString regex) { return split(regex, 0); } //[ifJava5+] private SafeString changeCase(Locale locale, OrigString os, boolean isUpper) throws IllegalArgumentException { BitSet b = new BitSet(); int olen = length(), rlen = os.length(); int ocount, rcount; int i, j; for (i = 0, j = 0; i < olen && j < rlen; i+=ocount, j+=rcount) { int ochar = codePointAt(i); int rchar; boolean tainted = false, changed = false; ocount = Character.charCount(ochar); if (isUpper) rchar = toUpperCaseEx(i, locale); else rchar = toLowerCaseEx(i, locale); if (!String.@internal@isError(rchar)) { rcount = Character.charCount(rchar); if (os.codePointAt(j) != rchar) throw new IllegalArgumentException("conversion error"); } else { char[] rtmp; if (isUpper) { rtmp = toUpperCaseCharArray(i, locale); rcount = rtmp.length; } else { rtmp = toLowerCaseCharArray(i, locale); rcount = rtmp.length; } for (int m = 0; m < rtmp.length; m++) if (os.charAt(j+m) != rtmp[m]) throw new IllegalArgumentException("conversion error"); } if (ocount != rcount) changed = true; for (int k = 0; !changed && k < ocount; k++) if (charAt(i+k) != os.charAt(j+k)) changed = true; for (int k = 0 ;k < ocount; k++) if (taint.get(i+k)) tainted = true; if (!tainted) continue; if (!changed) { for (int m = 0; m < ocount; m++) b.set(j+m, taint.get(i+m)); } else { for (int l = 0; l < rcount; l++) b.set(j+l); } } if (i != length() || j != os.length()) throw new IllegalArgumentException("toXCase conversion error: Lang " + locale.getLanguage()); return new SafeString(os, new SafeTaint(b, os.length())); } //[fiJava5+] //[ifJava4] private SafeString changeCase(Locale locale, OrigString os, boolean isUpper) throws IllegalArgumentException { if (!isUpper) return new SafeString(os, taint()); BitSet b = new BitSet(); int olen = length(), rlen = os.length(); int rcount, i, j; for (i = 0, j = 0; i < olen && j < rlen; i++, j+=rcount) { char ochar = charAt(i); char[] rchars = new char[] { toUpperCaseEx(ochar) }; boolean tainted = taint.get(i), changed = false; if ("tr".equals(locale.getLanguage()) && (ochar == 'i' || ochar == '\u0131')) { if (ochar == 'i') rchars[0] = '\u0130'; else rchars[0] = 'I'; rcount = 1; } else if (!String.@internal@isError(rchars[0])) { rcount = 1; } else { rchars = toUpperCaseCharArray(ochar); rcount = rchars.length; } for (int m = 0; m < rcount; m++) { if (os.charAt(j+m) != rchars[m]) throw new IllegalArgumentException("conversion error"); if (tainted) b.set(j+m); } } if (i != length() || j != os.length()) throw new IllegalArgumentException("toXCase conversion error: Lang " + locale.getLanguage()); return new SafeString(os, new SafeTaint(b, os.length())); } //[fiJava4] public SafeString toLowerCase(Locale locale) throws IllegalArgumentException { return changeCase(locale, str.toLowerCase(locale), false); } public SafeString toLowerCase() throws IllegalArgumentException { return toLowerCase(Locale.getDefault()); } public SafeString toUpperCase(Locale locale) throws IllegalArgumentException { return changeCase(locale, str.toUpperCase(locale), true); } public SafeString toUpperCase() throws IllegalArgumentException { return toUpperCase(Locale.getDefault()); } public SafeString trim() { OrigString os = str.trim(); int index = str.indexOf(os); return new SafeString(os, ((SafeTaint)taint.clone()).subset(index, index + os.length())); } public String toString() { String s = str.toString(); return new String(s, new Taint(taint.asBitSet(), s.length())); } public SafeString toSafeString() { return this; } public char[] toCharArray() { return str.toCharArray(); } public boolean isTainted() { return taint.isTainted(); } public SafeString(OrigString os, SafeTaint st) { this.str = new OrigString(os); this.taint = (SafeTaint) st.clone(); } public SafeTaint taint() { return (SafeTaint) taint.clone(); } public OrigString origString() { return new OrigString(str); } public boolean verify(String s) throws IllegalArgumentException { if (!str.equals(OrigStringUtil.toOrig(s)) || !taint.verify(s.@internal@taint())) return false; return true; } //[ifJava5+] public int toLowerCaseEx(int index, Locale l) { return ConditionalSpecialCasing.toLowerCaseEx(toString(), index, l); } public int toUpperCaseEx(int index, Locale l) { return ConditionalSpecialCasing.toUpperCaseEx(toString(), index, l); } public char[] toLowerCaseCharArray(int index, Locale l) { return ConditionalSpecialCasing.toLowerCaseCharArray(toString(), index, l); } public char[] toUpperCaseCharArray(int index, Locale l) { return ConditionalSpecialCasing.toUpperCaseCharArray(toString(), index, l); } //[fiJava5+] //[ifJava4] public static char toUpperCaseEx(char c) { return Character.toUpperCaseEx(c); } public static char[] toUpperCaseCharArray(char c) { return Character.toUpperCaseCharArray(c); } //[fiJava4] public static boolean isError(int c) { return String.@internal@isError(c); } public static SafeString valueOf(Object obj) { return new SafeString ((obj == null) ? "null" : obj.toString()); } public static SafeString valueOf(char data[]) { return new SafeString(data); } public static SafeString valueOf(char data[], int offset, int count) { return new SafeString(data, offset, count); } public static SafeString copyValueOf(char data[], int offset, int count) { return new SafeString(data, offset, count); } public static SafeString copyValueOf(char data[]) { return copyValueOf(data, 0, data.length); } public static SafeString valueOf(boolean b) { return new SafeString(b ? "true" : "false"); } public static SafeString valueOf(char c) { char data[] = {c}; return new SafeString(data); } public static SafeString valueOf(int i) { return new SafeString(Integer.toString(i, 10)); } public static SafeString valueOf(long l) { return new SafeString(Long.toString(l, 10)); } public static SafeString valueOf(float f) { return new SafeString(Float.toString(f)); } public static SafeString valueOf(double d) { return new SafeString(Double.toString(d)); } }