001/* 002 * Copyright 2008-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2019 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.matchingrules; 022 023 024 025import com.unboundid.asn1.ASN1OctetString; 026import com.unboundid.util.StaticUtils; 027import com.unboundid.util.ThreadSafety; 028import com.unboundid.util.ThreadSafetyLevel; 029 030 031 032/** 033 * This class provides an implementation of a matching rule that uses 034 * case-sensitive matching that also treats multiple consecutive (non-escaped) 035 * spaces as a single space. 036 */ 037@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 038public final class CaseExactStringMatchingRule 039 extends AcceptAllSimpleMatchingRule 040{ 041 /** 042 * The singleton instance that will be returned from the {@code getInstance} 043 * method. 044 */ 045 private static final CaseExactStringMatchingRule INSTANCE = 046 new CaseExactStringMatchingRule(); 047 048 049 050 /** 051 * The name for the caseExactMatch equality matching rule. 052 */ 053 public static final String EQUALITY_RULE_NAME = "caseExactMatch"; 054 055 056 057 /** 058 * The name for the caseExactMatch equality matching rule, formatted in all 059 * lowercase characters. 060 */ 061 static final String LOWER_EQUALITY_RULE_NAME = 062 StaticUtils.toLowerCase(EQUALITY_RULE_NAME); 063 064 065 066 /** 067 * The OID for the caseExactMatch equality matching rule. 068 */ 069 public static final String EQUALITY_RULE_OID = "2.5.13.5"; 070 071 072 073 /** 074 * The name for the caseExactOrderingMatch ordering matching rule. 075 */ 076 public static final String ORDERING_RULE_NAME = "caseExactOrderingMatch"; 077 078 079 080 /** 081 * The name for the caseExactOrderingMatch ordering matching rule, formatted 082 * in all lowercase characters. 083 */ 084 static final String LOWER_ORDERING_RULE_NAME = 085 StaticUtils.toLowerCase(ORDERING_RULE_NAME); 086 087 088 089 /** 090 * The OID for the caseExactOrderingMatch ordering matching rule. 091 */ 092 public static final String ORDERING_RULE_OID = "2.5.13.6"; 093 094 095 096 /** 097 * The name for the caseExactSubstringsMatch substring matching rule. 098 */ 099 public static final String SUBSTRING_RULE_NAME = "caseExactSubstringsMatch"; 100 101 102 103 /** 104 * The name for the caseExactSubstringsMatch substring matching rule, 105 * formatted in all lowercase characters. 106 */ 107 static final String LOWER_SUBSTRING_RULE_NAME = 108 StaticUtils.toLowerCase(SUBSTRING_RULE_NAME); 109 110 111 112 /** 113 * The OID for the caseExactSubstringsMatch substring matching rule. 114 */ 115 public static final String SUBSTRING_RULE_OID = "2.5.13.7"; 116 117 118 119 /** 120 * The serial version UID for this serializable class. 121 */ 122 private static final long serialVersionUID = -6336492464430413364L; 123 124 125 126 /** 127 * Creates a new instance of this case exact string matching rule. 128 */ 129 public CaseExactStringMatchingRule() 130 { 131 // No implementation is required. 132 } 133 134 135 136 /** 137 * Retrieves a singleton instance of this matching rule. 138 * 139 * @return A singleton instance of this matching rule. 140 */ 141 public static CaseExactStringMatchingRule getInstance() 142 { 143 return INSTANCE; 144 } 145 146 147 148 /** 149 * {@inheritDoc} 150 */ 151 @Override() 152 public String getEqualityMatchingRuleName() 153 { 154 return EQUALITY_RULE_NAME; 155 } 156 157 158 159 /** 160 * {@inheritDoc} 161 */ 162 @Override() 163 public String getEqualityMatchingRuleOID() 164 { 165 return EQUALITY_RULE_OID; 166 } 167 168 169 170 /** 171 * {@inheritDoc} 172 */ 173 @Override() 174 public String getOrderingMatchingRuleName() 175 { 176 return ORDERING_RULE_NAME; 177 } 178 179 180 181 /** 182 * {@inheritDoc} 183 */ 184 @Override() 185 public String getOrderingMatchingRuleOID() 186 { 187 return ORDERING_RULE_OID; 188 } 189 190 191 192 /** 193 * {@inheritDoc} 194 */ 195 @Override() 196 public String getSubstringMatchingRuleName() 197 { 198 return SUBSTRING_RULE_NAME; 199 } 200 201 202 203 /** 204 * {@inheritDoc} 205 */ 206 @Override() 207 public String getSubstringMatchingRuleOID() 208 { 209 return SUBSTRING_RULE_OID; 210 } 211 212 213 214 /** 215 * {@inheritDoc} 216 */ 217 @Override() 218 public boolean valuesMatch(final ASN1OctetString value1, 219 final ASN1OctetString value2) 220 { 221 // Try to use a quick, no-copy determination if possible. If this fails, 222 // then we'll fall back on a more thorough, but more costly, approach. 223 final byte[] value1Bytes = value1.getValue(); 224 final byte[] value2Bytes = value2.getValue(); 225 if (value1Bytes.length == value2Bytes.length) 226 { 227 for (int i=0; i< value1Bytes.length; i++) 228 { 229 final byte b1 = value1Bytes[i]; 230 final byte b2 = value2Bytes[i]; 231 232 if (((b1 & 0x7F) != (b1 & 0xFF)) || 233 ((b2 & 0x7F) != (b2 & 0xFF))) 234 { 235 return normalize(value1).equals(normalize(value2)); 236 } 237 else if (b1 != b2) 238 { 239 if ((b1 == ' ') || (b2 == ' ')) 240 { 241 return normalize(value1).equals(normalize(value2)); 242 } 243 else 244 { 245 return false; 246 } 247 } 248 } 249 250 // If we've gotten to this point, then the values must be equal. 251 return true; 252 } 253 else 254 { 255 return normalizeInternal(value1, false, (byte) 0x00).equals( 256 normalizeInternal(value2, false, (byte) 0x00)); 257 } 258 } 259 260 261 262 /** 263 * {@inheritDoc} 264 */ 265 @Override() 266 public ASN1OctetString normalize(final ASN1OctetString value) 267 { 268 return normalizeInternal(value, false, (byte) 0x00); 269 } 270 271 272 273 /** 274 * {@inheritDoc} 275 */ 276 @Override() 277 public ASN1OctetString normalizeSubstring(final ASN1OctetString value, 278 final byte substringType) 279 { 280 return normalizeInternal(value, true, substringType); 281 } 282 283 284 285 /** 286 * Normalizes the provided value for use in either an equality or substring 287 * matching operation. 288 * 289 * @param value The value to be normalized. 290 * @param isSubstring Indicates whether the value should be normalized as 291 * part of a substring assertion rather than an 292 * equality assertion. 293 * @param substringType The substring type for the element, if it is to be 294 * part of a substring assertion. 295 * 296 * @return The appropriately normalized form of the provided value. 297 */ 298 private static ASN1OctetString normalizeInternal(final ASN1OctetString value, 299 final boolean isSubstring, 300 final byte substringType) 301 { 302 final byte[] valueBytes = value.getValue(); 303 if (valueBytes.length == 0) 304 { 305 return value; 306 } 307 308 final boolean trimInitial; 309 final boolean trimFinal; 310 if (isSubstring) 311 { 312 switch (substringType) 313 { 314 case SUBSTRING_TYPE_SUBINITIAL: 315 trimInitial = true; 316 trimFinal = false; 317 break; 318 319 case SUBSTRING_TYPE_SUBFINAL: 320 trimInitial = false; 321 trimFinal = true; 322 break; 323 324 default: 325 trimInitial = false; 326 trimFinal = false; 327 break; 328 } 329 } 330 else 331 { 332 trimInitial = true; 333 trimFinal = true; 334 } 335 336 // Count the number of duplicate spaces in the value, and determine whether 337 // there are any non-space characters. Also, see if there are any non-ASCII 338 // characters. 339 boolean containsNonSpace = false; 340 boolean lastWasSpace = trimInitial; 341 int numDuplicates = 0; 342 for (final byte b : valueBytes) 343 { 344 if ((b & 0x7F) != (b & 0xFF)) 345 { 346 return normalizeNonASCII(value, trimInitial, trimFinal); 347 } 348 349 if (b == ' ') 350 { 351 if (lastWasSpace) 352 { 353 numDuplicates++; 354 } 355 else 356 { 357 lastWasSpace = true; 358 } 359 } 360 else 361 { 362 containsNonSpace = true; 363 lastWasSpace = false; 364 } 365 } 366 367 if (! containsNonSpace) 368 { 369 return new ASN1OctetString(" "); 370 } 371 372 if (lastWasSpace && trimFinal) 373 { 374 numDuplicates++; 375 } 376 377 378 // Create a new byte array to hold the normalized value. 379 lastWasSpace = trimInitial; 380 int targetPos = 0; 381 final byte[] normalizedBytes = new byte[valueBytes.length - numDuplicates]; 382 for (int i=0; i < valueBytes.length; i++) 383 { 384 if (valueBytes[i] == ' ') 385 { 386 if (lastWasSpace || (trimFinal && (i == (valueBytes.length - 1)))) 387 { 388 // No action is required. 389 } 390 else 391 { 392 // This condition is needed to handle the special case in which 393 // there are multiple spaces at the end of the value. 394 if (targetPos < normalizedBytes.length) 395 { 396 normalizedBytes[targetPos++] = ' '; 397 lastWasSpace = true; 398 } 399 } 400 } 401 else 402 { 403 normalizedBytes[targetPos++] = valueBytes[i]; 404 lastWasSpace = false; 405 } 406 } 407 408 409 return new ASN1OctetString(normalizedBytes); 410 } 411 412 413 414 /** 415 * Normalizes the provided value a string representation, properly handling 416 * any non-ASCII characters. 417 * 418 * @param value The value to be normalized. 419 * @param trimInitial Indicates whether to trim off all leading spaces at 420 * the beginning of the value. 421 * @param trimFinal Indicates whether to trim off all trailing spaces at 422 * the end of the value. 423 * 424 * @return The normalized form of the value. 425 */ 426 private static ASN1OctetString normalizeNonASCII(final ASN1OctetString value, 427 final boolean trimInitial, 428 final boolean trimFinal) 429 { 430 final StringBuilder buffer = new StringBuilder(value.stringValue()); 431 432 int pos = 0; 433 boolean lastWasSpace = trimInitial; 434 while (pos < buffer.length()) 435 { 436 final char c = buffer.charAt(pos++); 437 if (c == ' ') 438 { 439 if (lastWasSpace || (trimFinal && (pos >= buffer.length()))) 440 { 441 buffer.deleteCharAt(--pos); 442 } 443 else 444 { 445 lastWasSpace = true; 446 } 447 } 448 else 449 { 450 lastWasSpace = false; 451 } 452 } 453 454 // It is possible that there could be an extra space at the end. If that's 455 // the case, then remove it. 456 if (trimFinal && (buffer.length() > 0) && 457 (buffer.charAt(buffer.length() - 1) == ' ')) 458 { 459 buffer.deleteCharAt(buffer.length() - 1); 460 } 461 462 return new ASN1OctetString(buffer.toString()); 463 } 464}