* E.g. "Foo Bar" and "foo bar" would yield the same result. Both comparable string are equal. * * @param input The input string. * @return The comparable string. * @see 2.4. Comparison */ @Override public final String toComparableString(final CharSequence input) { // An entity that performs comparison of two strings according to this // profile MUST prepare each string as specified in Section 2.2 and MUST // apply the following rules specified in Section 2.1 in the order // shown: // 1. Additional Mapping Rule // 2. Case Mapping Rule // 3. Normalization Rule return stabilize(input, super::enforce); } @Override protected final CharSequence applyWidthMappingRule(final CharSequence input) { // 1. Width Mapping Rule: There is no width mapping rule return input; } @Override protected final CharSequence applyAdditionalMappingRule(final CharSequence input) { // 2. Additional Mapping Rule: The additional mapping rule consists of // the following sub-rules. // a. Map any instances of non-ASCII space to SPACE (U+0020); a // non-ASCII space is any Unicode code point having a general // category of "Zs", naturally with the exception of SPACE // (U+0020). (The inclusion of only ASCII space prevents // confusion with various non-ASCII space code points, many of // which are difficult to reproduce across different input // methods.) final String mapped = WHITESPACE.matcher(input).replaceAll(" "); // b. Remove any instances of the ASCII space character at the // beginning or end of a nickname (e.g., "stpeter " is mapped to // "stpeter"). final String trimmed = mapped.trim(); // c. Map interior sequences of more than one ASCII space character // to a single ASCII space character (e.g., "St Peter" is // mapped to "St Peter"). return trimmed.replaceAll("[ ]+", " "); } @Override protected final CharSequence applyCaseMappingRule(final CharSequence input) { // 3. Case Mapping Rule: Apply the Unicode toLowerCase() operation, as // defined in the Unicode Standard return caseMap(input); } @Override protected final CharSequence applyNormalizationRule(final CharSequence input) { // 4. Normalization Rule: Apply Unicode Normalization Form KC. return Normalizer.normalize(input, Normalizer.Form.NFKC); } @Override protected final CharSequence applyDirectionalityRule(final CharSequence input) { // 5. Directionality Rule: There is no directionality rule. return input; } } precis-1.1.0/src/main/java/rocks/xmpp/precis/OpaqueStringProfile.java 0000664 0000000 0000000 00000006700 14143322121 0025625 0 ustar 00root root 0000000 0000000 /* * The MIT License (MIT) * * Copyright (c) 2015-2017 Christian Schudt * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package rocks.xmpp.precis; import java.text.Normalizer; /** * @author Christian Schudt * @see 4.2. OpaqueString Profile */ final class OpaqueStringProfile extends PrecisProfile { private static final long serialVersionUID = 3063882108629348960L; OpaqueStringProfile() { super(false); } @Override public final String enforce(final CharSequence input) { final String enforced = super.enforce(input); // A password MUST NOT be zero bytes in length. This rule is to be // enforced after any normalization and mapping of code points. if (enforced.isEmpty()) { throw new IllegalArgumentException("String must not be empty after applying the rules."); } return enforced; } @Override protected final CharSequence applyWidthMappingRule(final CharSequence input) { // 1. Width-Mapping Rule: Fullwidth and halfwidth characters MUST NOT // be mapped to their decomposition mappings return input; } @Override protected final CharSequence applyAdditionalMappingRule(final CharSequence input) { // 2. Additional Mapping Rule: Any instances of non-ASCII space MUST be // mapped to ASCII space (U+0020); a non-ASCII space is any Unicode // code point having a Unicode general category of "Zs" (with the // exception of U+0020). return WHITESPACE.matcher(input).replaceAll(" "); } @Override protected final CharSequence applyCaseMappingRule(final CharSequence input) { // 3. Case Mapping Rule: There is no case mapping rule (because mapping // uppercase and titlecase code points to their lowercase // equivalents would lead to false accepts and thus to reduced // security). return input; } @Override protected final CharSequence applyNormalizationRule(final CharSequence input) { // 4. Normalization Rule: Unicode Normalization Form C (NFC) MUST be // applied to all strings. return Normalizer.normalize(input, Normalizer.Form.NFC); } @Override protected final CharSequence applyDirectionalityRule(final CharSequence input) { // 5. Directionality Rule: There is no directionality rule. return input; } } precis-1.1.0/src/main/java/rocks/xmpp/precis/PrecisProfile.java 0000664 0000000 0000000 00000114360 14143322121 0024433 0 ustar 00root root 0000000 0000000 /* * The MIT License (MIT) * * Copyright (c) 2015-2017 Christian Schudt * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package rocks.xmpp.precis; import java.io.Serializable; import java.text.Normalizer; import java.util.Comparator; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.function.Function; import java.util.regex.Pattern; /** * This is the base class for a PRECIS profile. A profile defines a set of rules (width mapping, additional mapping, case mapping, normalization and directionality) and uses one of two string classes, IdentifierClass or FreeformClass, which define the allowed and disallowed characters. *
* There are three basic use cases you can do with this class: *
*
* @author Christian Schudt
* @see 4. String Classes
* @see 5. Profiles
* @see PrecisProfiles
*/
public abstract class PrecisProfile implements Comparator
* This base method first applies the profile rules, then the behavioral rules as per RFC 7564 §7.
*
* @param input The input string.
* @return The output string.
* @throws InvalidCodePointException If the input contains invalid code points (which are disallowed by the underlying Precis String class).
* @see 7. Order of Operations
*/
public String enforce(final CharSequence input) {
return prepare(applyDirectionalityRule(
applyNormalizationRule(
applyCaseMappingRule(
applyAdditionalMappingRule(
applyWidthMappingRule(input))))));
}
/**
* Compares two strings with each other.
*
* @param o1 The first string.
* @param o2 The second string.
* @return 0 if the strings are equal, otherwise the comparison result.
* @throws InvalidCodePointException If the input contains invalid code points (which are disallowed by the underlying Precis String class).
* @see #toComparableString(CharSequence)
*/
@Override
public final int compare(final CharSequence o1, final CharSequence o2) {
return toComparableString(o1).compareTo(toComparableString(o2));
}
/**
* Converts a string to a comparable string. The default comparison method uses the same rules as {@linkplain #enforce(CharSequence) enforcement}.
* However, there are exceptions to this approach, like in the Nickname profile, where comparison uses different rules than enforcement.
*
* @param input The input string.
* @return The comparable string.
* @see #compare(CharSequence, CharSequence)
*/
public String toComparableString(final CharSequence input) {
return enforce(input);
}
/**
* The width mapping rule of a profile specifies whether width mapping
* is performed on the characters of a string, and how the mapping is
* done.
*
* @param input The input string.
* @return The width-mapped string.
* @see 5.2.1. Width Mapping Rule
*/
protected abstract CharSequence applyWidthMappingRule(CharSequence input);
/**
* The additional mapping rule of a profile specifies whether additional
* mappings are performed on the characters of a string, such as:
*
* Mapping of delimiter characters (such as '@', ':', '/', '+',
* and '-')
*
* Mapping of special characters (e.g., non-ASCII space characters to
* ASCII space or control characters to nothing).
*
* @param input The input string.
* @return The mapped string.
* @see 5.2.2. Additional Mapping Rule
*/
protected abstract CharSequence applyAdditionalMappingRule(CharSequence input);
/**
* The case mapping rule of a profile specifies whether case mapping
* (instead of case preservation) is performed on a string and how the
* mapping is applied (e.g., mapping uppercase and titlecase code points
* to their lowercase equivalents).
*
* If case mapping is desired (instead of case preservation), it is
* RECOMMENDED to use the Unicode toLowerCase() operation defined in the
* Unicode Standard [Unicode]. In contrast to the Unicode toCaseFold()
* operation, the toLowerCase() operation is less likely to violate the
* "Principle of Least Astonishment", especially when an application
* merely wishes to convert uppercase and titlecase code points to their
* lowercase equivalents while preserving lowercase code points.
*
* @param input The input string.
* @return The case mapped string.
* @see 5.2.3. Case Mapping Rule
*/
protected abstract CharSequence applyCaseMappingRule(CharSequence input);
/**
* The normalization rule of a profile specifies which Unicode
* normalization form (D, KD, C, or KC) is to be applied.
*
* In accordance with [RFC5198], normalization form C (NFC) is
* RECOMMENDED.
*
* @param input The input string.
* @return The normalized string.
* @see 5.2.4. Normalization Rule
*/
protected abstract CharSequence applyNormalizationRule(CharSequence input);
/**
* The directionality rule of a profile specifies how to treat strings
* containing what are often called "right-to-left" (RTL) characters
* (see Unicode Standard Annex #9 [UAX9]). RTL characters come from
* scripts that are normally written from right to left and are
* considered by Unicode to, themselves, have right-to-left
* directionality. Some strings containing RTL characters also contain
* "left-to-right" (LTR) characters, such as numerals, as well as
* characters without directional properties. Consequently, such
* strings are known as "bidirectional strings".
*
* @param input The input string.
* @return The output string.
* @see 5.2.5. Directionality Rule
*/
protected abstract CharSequence applyDirectionalityRule(CharSequence input);
/**
* Applying the rules for any given PRECIS profile is not necessarily an idempotent
* procedure (e.g., under certain circumstances, such as when Unicode
* Normalization Form KC is used, performing Unicode normalization after
* case mapping can still yield uppercase characters for certain code
* points). Therefore, an implementation SHOULD apply the rules
* repeatedly until the output string is stable; if the output string
* does not stabilize after reapplying the rules three (3) additional
* times after the first application, the implementation SHOULD
* terminate application of the rules and reject the input string as
* invalid.
*
* This method applies the rules once (the first time) and then maximum three additional times.
*
* @param input The input string.
* @param rules The function which applies the rules.
* @return The stable output string.
* @see 7. Order of Operations
*/
protected final String stabilize(final CharSequence input, final FunctionPreparation
* Preparation ensures, that a string contains only valid characters, but usually does not apply any mapping rules.
*
* {@code
* PrecisProfiles.USERNAME_CASE_MAPPED.prepare("UpperCaseUsername");
* }
*
* If the passed string contains any invalid characters, an {@link InvalidCodePointException} is thrown:
*
* {@code
* PrecisProfiles.USERNAME_CASE_MAPPED.prepare("Username\u265A"); // Contains symbol, throws exception.
* }
*
* Enforcement
* Enforcement applies specific rules (e.g. case mapping) to the string for the purpose of determining if the string can be used
* in a given protocol slot.
*
* {@code
* String enforced = PrecisProfiles.USERNAME_CASE_MAPPED.enforce("UpperCaseUsername"); // uppercaseusername
* }
*
* Comparison
* You can just use {@link PrecisProfile#toComparableString(CharSequence)} to check, if two strings compare to each other,
* e.g.:
*
* {@code
* PrecisProfile profile = PrecisProfiles.USERNAME_CASE_MAPPED;
* if (profile.toComparableString("foobar").equals(profile.toComparableString("FooBar"))) {
* // username already exists.
* }
* }
*
* Or you can use {@link PrecisProfile} as a {@link java.util.Comparator}:
*
* {@code
* if (profile.compare("foobar", "FooBar") == 0) {
* // username already exists.
* }
* }
*
* Note that a profile may use different rules during comparison than during enforcement (as the Nickname profile, RFC 8266).
*
* @author Christian Schudt
* @see PrecisProfile
*/
public final class PrecisProfiles {
/**
* The "UsernameCaseMapped Profile" specified in "Preparation, Enforcement, and Comparison of Internationalized Strings
* Representing Usernames and Passwords", RFC 8265.
*
* @see 3.3. UsernameCaseMapped Profile
*/
public final static PrecisProfile USERNAME_CASE_MAPPED = new UsernameProfile(true);
/**
* The "UsernameCasePreserved Profile" specified in "Preparation, Enforcement, and Comparison of Internationalized Strings
* Representing Usernames and Passwords", RFC 8265.
*
* @see 3.4. UsernameCasePreserved Profile
*/
public static final PrecisProfile USERNAME_CASE_PRESERVED = new UsernameProfile(false);
/**
* The "OpaqueString Profile" specified in "Preparation, Enforcement, and Comparison of Internationalized Strings
* Representing Usernames and Passwords", RFC 8265.
*
* @see 4.2. OpaqueString Profile
*/
public static final PrecisProfile OPAQUE_STRING = new OpaqueStringProfile();
/**
* The "Nickname Profile" specified in "Preparation, Enforcement, and Comparison
* of Internationalized Strings Representing Nicknames", RFC 8266.
*
* @see Preparation, Enforcement, and Comparison of Internationalized Strings
* Representing Nicknames
*/
public static final PrecisProfile NICKNAME = new NicknameProfile();
/**
* A profile for preparing and enforcing international domain names.
* While not an official PRECIS profile, this profiles applies the mapping rules described in RFC 5895 2. The General Procedure
* to a domain name.
*
* @see RFC 5895 2. The General Procedure
*/
public static final PrecisProfile IDN = new IDNProfile();
/**
* A profile used to prepare and enforce localparts of XMPP addresses (JIDs), specified in
* RFC 7622
*
* @see RFC 7622 3.3. Localpart
*/
public static final PrecisProfile XMPP_LOCALPART = new XmppLocalpartProfile();
private PrecisProfiles() {
}
}
precis-1.1.0/src/main/java/rocks/xmpp/precis/UsernameProfile.java 0000664 0000000 0000000 00000011002 14143322121 0024752 0 ustar 00root root 0000000 0000000 /*
* The MIT License (MIT)
*
* Copyright (c) 2015-2017 Christian Schudt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package rocks.xmpp.precis;
import java.text.Bidi;
import java.text.Normalizer;
/**
* The base class for user name profiles.
*
* @author Christian Schudt
* @see 3. Usernames
*/
class UsernameProfile extends PrecisProfile {
private static final long serialVersionUID = 848281423907881855L;
private final boolean caseMapped;
UsernameProfile(boolean caseMapped) {
super(true);
this.caseMapped = caseMapped;
}
@Override
public final String prepare(final CharSequence input) {
// 1. Apply the width mapping rule specified in Section 3.4.1. It is
// necessary to apply the rule at this point because otherwise the
// PRECIS "HasCompat" category specified in Section 9.17 of
// [RFC8264] would forbid fullwidth and halfwidth code points.
final CharSequence mapped = applyWidthMappingRule(input);
// 2. Ensure that the string consists only of Unicode code points that conform to the PRECIS IdentifierClass.
return super.prepare(mapped);
}
@Override
public String enforce(final CharSequence input) {
// 1. Case Mapping Rule
// 2. Normalization Rule
// 3. Directionality Rule
final String enforced = super.enforce(input);
// A username MUST NOT be zero bytes in length. This rule is to be
// enforced after any normalization and mapping of code points.
if (enforced.isEmpty()) {
throw new IllegalArgumentException("A username must not be empty.");
}
return enforced;
}
@Override
protected final CharSequence applyWidthMappingRule(final CharSequence input) {
// 1. Width Mapping Rule: Map fullwidth and halfwidth code points to
// their decomposition mappings (see Unicode Standard Annex #11 [UAX11]).
return widthMap(input);
}
@Override
protected final CharSequence applyAdditionalMappingRule(final CharSequence input) {
// 2. Additional Mapping Rule: There is no additional mapping rule.
return input;
}
@Override
protected final CharSequence applyCaseMappingRule(final CharSequence input) {
// 3. Case Mapping Rule: There is no case mapping rule.
// 3. Case Mapping Rule: Map uppercase and titlecase code points to
// their lowercase equivalents, preferably using the Unicode
// toLowerCase() operation as defined in the Unicode Standard [Unicode]
return caseMapped ? caseMap(input) : input;
}
@Override
protected final CharSequence applyNormalizationRule(final CharSequence input) {
// 4. Normalization Rule: Apply Unicode Normalization Form C (NFC) to all strings.
return Normalizer.normalize(input, Normalizer.Form.NFC);
}
@Override
protected final CharSequence applyDirectionalityRule(final CharSequence input) {
// 5. Directionality Rule: Apply the "Bidi Rule" defined in [RFC5893]
// to strings that contain right-to-left code points (i.e., each of
// the six conditions of the Bidi Rule must be satisfied); for
// strings that do not contain right-to-left code points, there is
// no special processing for directionality.
if (Bidi.requiresBidi(input.toString().toCharArray(), 0, input.length())) {
checkBidiRule(input);
}
return input;
}
}
precis-1.1.0/src/main/java/rocks/xmpp/precis/XmppLocalpartProfile.java 0000664 0000000 0000000 00000004427 14143322121 0025776 0 ustar 00root root 0000000 0000000 package rocks.xmpp.precis;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
final class XmppLocalpartProfile extends UsernameProfile {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* From RFC 7622 §
* 3.3.1.
*/
// @formatter:off
private static final char[] LOCALPART_FURTHER_EXCLUDED_CHARACTERS = new char[] {
'"', // U+0022 (QUOTATION MARK) , i.e., "
'&', // U+0026 (AMPERSAND), i.e., &
'\'', // U+0027 (APOSTROPHE), i.e., '
'/', // U+002F (SOLIDUS), i.e., /
':', // U+003A (COLON), i.e., :
'<', // U+003C (LESS-THAN SIGN), i.e., <
'>', // U+003E (GREATER-THAN SIGN), i.e., >
'@', // U+0040 (COMMERCIAL AT), i.e., @
};
// @formatter:on
static {
// Ensure that the char array is sorted as we use Arrays.binarySearch() on it.
Arrays.sort(LOCALPART_FURTHER_EXCLUDED_CHARACTERS);
}
XmppLocalpartProfile() {
super(true);
}
@Override
public String enforce(final CharSequence input) {
int inputLength = input.length();
for (int i = 0; i < inputLength; i++) {
char c = input.charAt(i);
int excludedCharPos = Arrays.binarySearch(LOCALPART_FURTHER_EXCLUDED_CHARACTERS, c);
if (excludedCharPos >= 0) {
throw new InvalidCodePointException("XMPP localparts must not contain '"
+ LOCALPART_FURTHER_EXCLUDED_CHARACTERS[excludedCharPos] + "'. Found in '" + input
+ "' at position " + excludedCharPos + ". See RFC 7622 § 3.3.1");
}
}
String res = super.enforce(input);
assertNotLongerThan1023BytesOrEmpty(res);
return res;
}
private static void assertNotLongerThan1023BytesOrEmpty(String string) {
byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
if (bytes.length > 1023) {
throw new IllegalArgumentException("Given string after enforcment and encoded in UTF-8 is longer then 1023 bytes");
} else if (bytes.length == 0) {
throw new IllegalArgumentException("Given string after enforcment and encoded in UTF-8 is empty");
}
}
}
precis-1.1.0/src/main/java/rocks/xmpp/precis/package-info.java 0000664 0000000 0000000 00000003755 14143322121 0024216 0 ustar 00root root 0000000 0000000 /*
* The MIT License (MIT)
*
* Copyright (c) 2015-2017 Christian Schudt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Provides classes for the PRECIS framework as well as related profiles.
*
*
*
* @see rocks.xmpp.precis.PrecisProfiles
*/
package rocks.xmpp.precis; precis-1.1.0/src/test/ 0000775 0000000 0000000 00000000000 14143322121 0014535 5 ustar 00root root 0000000 0000000 precis-1.1.0/src/test/java/ 0000775 0000000 0000000 00000000000 14143322121 0015456 5 ustar 00root root 0000000 0000000 precis-1.1.0/src/test/java/rocks/ 0000775 0000000 0000000 00000000000 14143322121 0016577 5 ustar 00root root 0000000 0000000 precis-1.1.0/src/test/java/rocks/xmpp/ 0000775 0000000 0000000 00000000000 14143322121 0017563 5 ustar 00root root 0000000 0000000 precis-1.1.0/src/test/java/rocks/xmpp/precis/ 0000775 0000000 0000000 00000000000 14143322121 0021050 5 ustar 00root root 0000000 0000000 precis-1.1.0/src/test/java/rocks/xmpp/precis/BidiRuleTest.java 0000664 0000000 0000000 00000005470 14143322121 0024260 0 ustar 00root root 0000000 0000000 /*
* The MIT License (MIT)
*
* Copyright (c) 2015-2017 Christian Schudt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package rocks.xmpp.precis;
import org.testng.annotations.Test;
/**
* @author Christian Schudt
*/
public class BidiRuleTest {
@Test(expectedExceptions = IllegalArgumentException.class)
public void testBidiRule1() {
// First character is not L, R or AL, but NSM:
PrecisProfile.checkBidiRule("\u07AA");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testBidiRule2() {
// RTL label should not contain L characters
PrecisProfile.checkBidiRule("\u0786test");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testBidiRule3() {
// RTL label should not end with L character
PrecisProfile.checkBidiRule("\u0786\u0793a\u07A6");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testBidiRule4() {
// RTL label should not contain both EN and AN characters.
PrecisProfile.checkBidiRule("\u0786123\u0660"); // 0660 = Arabic Zero
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testBidiRule5() {
PrecisProfile.checkBidiRule("abc\u0786a");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testBidiRule6() {
// LTR label should end with L or EN.
PrecisProfile.checkBidiRule("a\u0793");
}
@Test
public void testValidBidiString() {
PrecisProfile.checkBidiRule("\u0627\u0031\u0628");
// Failed with RFC 3454, but should work with RFC 5893:
PrecisProfile.checkBidiRule("\u0786\u07AE\u0782\u07B0\u0795\u07A9\u0793\u07A6\u0783\u07AA");
PrecisProfile.checkBidiRule("\u05D9\u05B4\u05D5\u05D0\u05B8");
}
}
precis-1.1.0/src/test/java/rocks/xmpp/precis/IDNProfileTest.java 0000664 0000000 0000000 00000002342 14143322121 0024507 0 ustar 00root root 0000000 0000000 package rocks.xmpp.precis;
import org.testng.Assert;
import org.testng.annotations.Test;
/**
* @author Christian Schudt
*/
public class IDNProfileTest {
@Test
public void testDots() {
String domainName = PrecisProfiles.IDN.enforce("a\u3002b\uFF0Ec\uFF61d");
Assert.assertEquals(domainName, "a.b.c.d");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNonLDHAsciiCharacters() {
PrecisProfiles.IDN.enforce("_test");
}
@Test
public void testUnicodeDomain() {
// Example from: https://tools.ietf.org/html/rfc5122#section-2.7.3
String domainName = PrecisProfiles.IDN.enforce("\u010Dechy.example");
Assert.assertEquals(domainName, "\u010Dechy.example");
}
@Test
public void testAsciiDomain() {
String domainName = PrecisProfiles.IDN.enforce("xn--dmin-moa0i");
Assert.assertEquals(domainName, "dömäin");
domainName = PrecisProfiles.IDN.enforce("xn--xample-2of.com");
Assert.assertEquals(domainName, "еxample.com");
}
@Test
public void testCaseMapping() {
String domainName = PrecisProfiles.IDN.enforce("DOMAIN");
Assert.assertEquals(domainName, "domain");
}
}
precis-1.1.0/src/test/java/rocks/xmpp/precis/IdentifierClassTest.java 0000664 0000000 0000000 00000006214 14143322121 0025626 0 ustar 00root root 0000000 0000000 /*
* The MIT License (MIT)
*
* Copyright (c) 2015-2017 Christian Schudt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package rocks.xmpp.precis;
import org.testng.Assert;
import org.testng.annotations.Test;
import static rocks.xmpp.precis.PrecisProfiles.USERNAME_CASE_MAPPED;
/**
* @author Christian Schudt
*/
public class IdentifierClassTest {
@Test(expectedExceptions = IllegalArgumentException.class)
public void shouldNotAllowNonCharacter() {
USERNAME_CASE_MAPPED.prepare("\uFDD0");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void shouldNotAllowOldHangulJamoCharacters() {
USERNAME_CASE_MAPPED.prepare("\uA960");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void shouldNotAllowIgnorableCharacters() {
USERNAME_CASE_MAPPED.prepare("\u034F");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void shouldNotAllowControlCharacters() {
USERNAME_CASE_MAPPED.prepare("\u061C");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void shouldNotAllowSymbols() {
USERNAME_CASE_MAPPED.prepare("\u265A"); // BLACK CHESS KING
}
@Test
public void testHasCompat() {
Assert.assertTrue(PrecisProfile.hasCompatibilityEquivalent(0x2163)); // ROMAN NUMERAL FOUR
}
@Test
public void shouldBeExceptionallyValid() {
USERNAME_CASE_MAPPED.prepare("\u03C2\u00DF");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void shouldBeExceptionallyDisallowed() {
USERNAME_CASE_MAPPED.prepare("\u3032");
}
/**
* Tests this rule for "Unassigned":
*
* General_Category(cp) is in {Cn} and
* Noncharacter_Code_Point(cp) = False
*/
@Test
public void testUnassigned() {
// Unassigned code points
Assert.assertTrue(PrecisProfile.isUnassigned(0x2065));
Assert.assertTrue(PrecisProfile.isUnassigned(0x05FF));
// Non-characters
Assert.assertFalse(PrecisProfile.isUnassigned(0xFFFF));
Assert.assertFalse(PrecisProfile.isUnassigned(0xFDD0));
}
}
precis-1.1.0/src/test/java/rocks/xmpp/precis/NicknameProfileTest.java 0000664 0000000 0000000 00000006615 14143322121 0025631 0 ustar 00root root 0000000 0000000 /*
* The MIT License (MIT)
*
* Copyright (c) 2015-2017 Christian Schudt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package rocks.xmpp.precis;
import org.testng.Assert;
import org.testng.annotations.Test;
import static rocks.xmpp.precis.PrecisProfiles.NICKNAME;
/**
* @author Christian Schudt
*/
public class NicknameProfileTest {
@Test
public void shouldReplaceNonAsciiSpaces() {
Assert.assertEquals(NICKNAME.enforce("a\u00A0a\u1680a\u2000a\u2001a\u2002a\u2003a\u2004a\u2005a\u2006a\u2007a\u2008a\u2009a\u200Aa\u202Fa\u205Fa\u3000a"), "a a a a a a a a a a a a a a a a a");
}
@Test
public void shouldTrim() {
Assert.assertEquals(NICKNAME.enforce("stpeter "), "stpeter");
}
@Test
public void shouldMapToSingleSpace() {
Assert.assertEquals(NICKNAME.enforce("st peter"), "st peter");
}
@Test
public void shouldNormalizeNFKC() {
Assert.assertEquals(NICKNAME.enforce("\u2163"), "IV");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void shouldNotBeEmpty() {
NICKNAME.enforce("");
}
@Test
public void testExamples() {
Assert.assertEquals(NICKNAME.compare("Foo", "foo"), 0);
Assert.assertEquals(NICKNAME.compare("foo", "foo"), 0);
Assert.assertEquals(NICKNAME.compare("Foo Bar", "foo bar"), 0);
Assert.assertEquals(NICKNAME.compare("foo bar", "foo bar"), 0);
Assert.assertEquals(NICKNAME.compare("\u03A3", "\u03C3"), 0);
Assert.assertEquals(NICKNAME.compare("\u03C3", "\u03C3"), 0);
Assert.assertEquals(NICKNAME.compare("\u03C2", "\u03C2"), 0);
Assert.assertEquals(NICKNAME.compare("\u265A", "\u265A"), 0);
Assert.assertEquals(NICKNAME.compare("\u03AB", "\u03CB"), 0); // GREEK SMALL LETTER UPSILON WITH DIALYTIKA
Assert.assertEquals(NICKNAME.compare("\u221E", "\u221E"), 0);
Assert.assertEquals(NICKNAME.compare("Richard \u2163", "richard iv"), 0);
}
@Test
public void testComparison() {
Assert.assertEquals(NICKNAME.toComparableString("Foo Bar "), NICKNAME.toComparableString("foo bar"));
}
@Test
public void testIdempotencyEnforcement() {
UsernameCaseMappedProfileTest.testIdempotency(NICKNAME::enforce);
}
@Test
public void testIdempotencyComparison() {
UsernameCaseMappedProfileTest.testIdempotency(NICKNAME::toComparableString);
}
}
precis-1.1.0/src/test/java/rocks/xmpp/precis/OpaqueStringProfileTest.java 0000664 0000000 0000000 00000004537 14143322121 0026526 0 ustar 00root root 0000000 0000000 /*
* The MIT License (MIT)
*
* Copyright (c) 2015-2017 Christian Schudt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package rocks.xmpp.precis;
import org.testng.Assert;
import org.testng.annotations.Test;
import static rocks.xmpp.precis.PrecisProfiles.OPAQUE_STRING;
/**
* @author Christian Schudt
*/
public class OpaqueStringProfileTest {
@Test
public void testAllowedStrings() {
// ASCII space is allowed
Assert.assertEquals(OPAQUE_STRING.enforce("correct horse battery staple"), "correct horse battery staple");
Assert.assertEquals(OPAQUE_STRING.enforce("Correct Horse Battery Staple"), "Correct Horse Battery Staple");
Assert.assertEquals(OPAQUE_STRING.enforce("πßå"), "πßå");
Assert.assertEquals(OPAQUE_STRING.enforce("Jack of \u2666s"), "Jack of \u2666s");
Assert.assertEquals(OPAQUE_STRING.enforce("foo\u1680bar"), "foo bar");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testZeroLength() {
OPAQUE_STRING.enforce("");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testControlCharacters() {
OPAQUE_STRING.enforce("my cat is a \u0009by");
}
@Test
public void testIdempotencyEnforcement() {
UsernameCaseMappedProfileTest.testIdempotency(OPAQUE_STRING::enforce);
}
}
precis-1.1.0/src/test/java/rocks/xmpp/precis/UsernameCaseMappedProfileTest.java 0000664 0000000 0000000 00000016523 14143322121 0027605 0 ustar 00root root 0000000 0000000 /*
* The MIT License (MIT)
*
* Copyright (c) 2015-2017 Christian Schudt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package rocks.xmpp.precis;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.util.function.Function;
import static rocks.xmpp.precis.PrecisProfiles.USERNAME_CASE_MAPPED;
/**
* @author Christian Schudt
*/
public class UsernameCaseMappedProfileTest {
@Test
public void testAllowedStrings() {
Assert.assertEquals(USERNAME_CASE_MAPPED.enforce("juliet@example.com"), "juliet@example.com");
Assert.assertEquals(USERNAME_CASE_MAPPED.enforce("fussball"), "fussball");
Assert.assertEquals(USERNAME_CASE_MAPPED.enforce("fu\u00DFball"), "fu\u00DFball");
Assert.assertEquals(USERNAME_CASE_MAPPED.enforce("\u03C0"), "\u03C0");
Assert.assertEquals(USERNAME_CASE_MAPPED.enforce("\u03A3"), "\u03C3");
Assert.assertEquals(USERNAME_CASE_MAPPED.enforce("\u03C3"), "\u03C3");
Assert.assertEquals(USERNAME_CASE_MAPPED.enforce("\u03C2"), "\u03C2");
Assert.assertEquals(USERNAME_CASE_MAPPED.enforce("\u0049"), "\u0069");
Assert.assertEquals(USERNAME_CASE_MAPPED.enforce("\u03B0"), "\u03B0");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testSpaceCharacters() {
USERNAME_CASE_MAPPED.enforce("foo bar");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testRomanFour() {
USERNAME_CASE_MAPPED.enforce("henry\u2163");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testInfinity() {
USERNAME_CASE_MAPPED.enforce("\u221E");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testBlackChessKing() {
USERNAME_CASE_MAPPED.enforce("\u265A");
}
@Test
public void testLetterDigits() {
USERNAME_CASE_MAPPED.enforce("\u007E");
USERNAME_CASE_MAPPED.enforce("a");
}
@Test
public void testPrintableCharacters() {
USERNAME_CASE_MAPPED.enforce("\u0021");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testSpaceCharacters1() {
USERNAME_CASE_MAPPED.prepare(" ");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testSpaceCharacters2() {
USERNAME_CASE_MAPPED.prepare("\t");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testSpaceCharacters3() {
USERNAME_CASE_MAPPED.prepare("\n");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testSymbolCharacters1() {
USERNAME_CASE_MAPPED.prepare("\u2600");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testSymbolCharacters2() {
USERNAME_CASE_MAPPED.prepare("\u26d6");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testSymbolCharacters3() {
USERNAME_CASE_MAPPED.prepare("\u26FF");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testCompatibilityEquivalent() {
USERNAME_CASE_MAPPED.prepare("\uFB00");
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testOtherLetterDigits1() {
USERNAME_CASE_MAPPED.prepare("\u01C5"); // Lt CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testOtherLetterDigits2() {
USERNAME_CASE_MAPPED.prepare("\u16EE"); // Nl RUNIC ARLAUG SYMBOL
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testOtherLetterDigits3() {
USERNAME_CASE_MAPPED.prepare("\u00B2"); // No SUPERSCRIPT TWO
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testOtherLetterDigits4() {
USERNAME_CASE_MAPPED.prepare("\u0488"); // Me COMBINING CYRILLIC HUNDRED THOUSANDS SIGN
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testEmptyUsername() {
USERNAME_CASE_MAPPED.enforce("");
}
@Test
public void testCompositeCharactersAndCombiningSequence() {
CharSequence ang = USERNAME_CASE_MAPPED.enforce("\u212B"); // angstrom sign
CharSequence a = USERNAME_CASE_MAPPED.enforce("\u0041\u030A"); // A + ring
CharSequence b = USERNAME_CASE_MAPPED.enforce("\u00C5"); // A with ring
Assert.assertEquals(a, b);
Assert.assertEquals(a, ang);
CharSequence c = USERNAME_CASE_MAPPED.enforce("\u0063\u0327"); // c + cedille
CharSequence d = USERNAME_CASE_MAPPED.enforce("\u00E7"); // c cedille
Assert.assertEquals(c, d);
CharSequence e = USERNAME_CASE_MAPPED.enforce("\u0052\u030C");
CharSequence f = USERNAME_CASE_MAPPED.enforce("\u0158");
Assert.assertEquals(e, f);
}
@Test
public void testConfusableCharacters() {
CharSequence a = USERNAME_CASE_MAPPED.enforce("\u0041"); // LATIN CAPITAL LETTER A
CharSequence b = USERNAME_CASE_MAPPED.enforce("\u0410"); // CYRILLIC CAPITAL LETTER A
Assert.assertNotEquals(a, b);
}
@Test
public void testWidthMapping() {
CharSequence a = USERNAME_CASE_MAPPED.enforce("\uFF21\uFF22");
CharSequence b = USERNAME_CASE_MAPPED.enforce("ab");
Assert.assertEquals(a, b);
}
@Test(enabled = false)
public void testPerformance() {
int n = 1000000;
long c = 0;
for (int i = 0; i < n; i++) {
long nano = System.nanoTime();
USERNAME_CASE_MAPPED.enforce("äääääääääääääääääääääääääääääääääääääääääääääää");
c += System.nanoTime() - nano;
}
System.out.println(c / n);
}
@Test
public void testIdempotencyEnforcement() {
testIdempotency(USERNAME_CASE_MAPPED::enforce);
}
static void testIdempotency(Function