1 // Copyright (c) 2011, Mike Samuel 2 // All rights reserved. 3 // 4 // Redistribution and use in source and binary forms, with or without 5 // modification, are permitted provided that the following conditions 6 // are met: 7 // 8 // Redistributions of source code must retain the above copyright 9 // notice, this list of conditions and the following disclaimer. 10 // Redistributions in binary form must reproduce the above copyright 11 // notice, this list of conditions and the following disclaimer in the 12 // documentation and/or other materials provided with the distribution. 13 // Neither the name of the OWASP nor the names of its contributors may 14 // be used to endorse or promote products derived from this software 15 // without specific prior written permission. 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 19 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 20 // COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 21 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 22 // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 26 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 // POSSIBILITY OF SUCH DAMAGE. 28 29 package org.owasp.html; 30 31 import javax.annotation.Nullable; 32 33 /** 34 * Locale independent versions of String case-insensitive operations. 35 * <p> 36 * The normal case insensitive operators {@link String#toLowerCase} 37 * and {@link String#equalsIgnoreCase} depend upon the current locale. 38 * They will fold the letters "i" and "I" differently if the locale is 39 * Turkish than if it is English. 40 * <p> 41 * These operations ignore all case folding for non-Roman letters, and are 42 * independent of the current locale. 43 * Lower-casing is exactly equivalent to {@code tr/A-Z/a-z/}, upper-casing to 44 * {@code tr/a-z/A-Z/}, and case insensitive comparison is equivalent to 45 * lower-casing both then comparing by code-unit. 46 * <p> 47 * Because of this simpler case folding, it is the case that for all Strings s 48 * <code> 49 * Strings.toUpperCase(s).equals(Strings.toUpperCase(Strings.toLowerCase(s))) 50 * </code>. 51 * 52 * @author Mike Samuel <mikesamuel (at) gmail.com> 53 */ 54 final class Strings { 55 public static boolean equalsIgnoreCase( 56 @Nullable String a, @Nullable String b) { 57 if (a == null) { return b == null; } 58 if (b == null) { return false; } 59 int length = a.length(); 60 if (b.length() != length) { return false; } 61 for (int i = length; --i >= 0;) { 62 char c = a.charAt(i), d = b.charAt(i); 63 if (c != d) { 64 if (c <= 'z' && c >= 'A') { 65 if (c <= 'Z') { c |= 0x20; } 66 if (d <= 'Z' && d >= 'A') { d |= 0x20; } 67 if (c == d) { continue; } 68 } 69 return false; 70 } 71 } 72 return true; 73 } 74 75 public static boolean regionMatchesIgnoreCase( 76 CharSequence a, int aoffset, CharSequence b, int boffset, int n) { 77 if (aoffset + n > a.length() || boffset + n > b.length()) { return false; } 78 for (int i = n; --i >= 0;) { 79 char c = a.charAt(aoffset + i), d = b.charAt(boffset + i); 80 if (c != d) { 81 if (c <= 'z' && c >= 'A') { 82 if (c <= 'Z') { c |= 0x20; } 83 if (d <= 'Z' && d >= 'A') { d |= 0x20; } 84 if (c == d) { continue; } 85 } 86 return false; 87 } 88 } 89 return true; 90 } 91 92 /** True iff {@code s.equals(String.toLowerCase(s))}. */ 93 public static boolean isLowerCase(CharSequence s) { 94 for (int i = s.length(); --i >= 0;) { 95 char c = s.charAt(i); 96 if (c <= 'Z' && c >= 'A') { 97 return false; 98 } 99 } 100 return true; 101 } 102 103 private static final char[] LCASE_CHARS = new char['Z' + 1]; 104 private static final char[] UCASE_CHARS = new char['z' + 1]; 105 static { 106 for (int i = 0; i < 'A'; ++i) { LCASE_CHARS[i] = (char) i; } 107 for (int i = 'A'; i <= 'Z'; ++i) { LCASE_CHARS[i] = (char) (i | 0x20); } 108 for (int i = 0; i < 'a'; ++i) { UCASE_CHARS[i] = (char) i; } 109 for (int i = 'a'; i <= 'z'; ++i) { UCASE_CHARS[i] = (char) (i & ~0x20); } 110 } 111 public static String toLowerCase(String s) { 112 for (int i = s.length(); --i >= 0;) { 113 char c = s.charAt(i); 114 if (c <= 'Z' && c >= 'A') { 115 char[] chars = s.toCharArray(); 116 chars[i] = LCASE_CHARS[c]; 117 while (--i >= 0) { 118 c = chars[i]; 119 if (c <= 'Z') { 120 chars[i] = LCASE_CHARS[c]; 121 } 122 } 123 return String.valueOf(chars); 124 } 125 } 126 return s; 127 } 128 129 public static String toUpperCase(String s) { 130 for (int i = s.length(); --i >= 0;) { 131 char c = s.charAt(i); 132 if (c <= 'z' && c >= 'a') { 133 char[] chars = s.toCharArray(); 134 chars[i] = UCASE_CHARS[c]; 135 while (--i >= 0) { 136 c = chars[i]; 137 if (c <= 'z') { 138 chars[i] = UCASE_CHARS[c]; 139 } 140 } 141 return String.valueOf(chars); 142 } 143 } 144 return s; 145 } 146 147 private Strings() { /* uninstantiable */ } 148 } 149