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