pax_global_header00006660000000000000000000000064141433221210014503gustar00rootroot0000000000000052 comment=3da66f451d63668f10ade6aa55e434578c15dbd3 precis-1.1.0/000077500000000000000000000000001414332212100127675ustar00rootroot00000000000000precis-1.1.0/LICENSE.txt000066400000000000000000000020771414332212100146200ustar00rootroot00000000000000The 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.precis-1.1.0/bitbucket-pipelines.yml000066400000000000000000000003041414332212100174510ustar00rootroot00000000000000pipelines: default: - step: image: maven:3-jdk-8 script: - mvn clean install - step: image: maven:3-jdk-11 script: - mvn clean installprecis-1.1.0/pom.xml000066400000000000000000000475561414332212100143250ustar00rootroot00000000000000 4.0.0 precis rocks.xmpp 1.1.0 PRECIS Framework Preparation, Enforcement, and Comparison of Internationalized Strings (RFC 8264, RFC 8265, RFC 8266) http://xmpp.rocks MIT License http://www.opensource.org/licenses/mit-license Christian Schudt developer +01:00 XMPP.rocks http://xmpp.rocks https://bitbucket.org/sco0ter/precis/src scm:git:git@bitbucket.org:sco0ter/precis.git scm:git:git@bitbucket.org:sco0ter/precis.git UTF-8 UTF-8 8 8 3.1.11 9+181-r4173-1 2015 ossrh https://oss.sonatype.org/content/repositories/snapshots ossrh https://oss.sonatype.org/service/local/staging/deploy/maven2/ maven-jar-plugin default-jar rocks.xmpp.precis org.apache.maven.plugins maven-source-plugin jar-no-fork org.sonatype.plugins nexus-staging-maven-plugin org.codehaus.mojo versions-maven-plugin 2.7 false 1.1.0 false display-plugin-updates display-plugin-updates org.apache.maven.plugins maven-deploy-plugin true org.apache.maven.plugins maven-compiler-plugin 8 8 true -XDcompilePolicy=simple -Xplugin:ErrorProne com.google.errorprone error_prone_core 2.3.3 com.github.spotbugs spotbugs-maven-plugin ${spotbugs.version} check com.github.spotbugs spotbugs ${spotbugs.version} org.codehaus.mojo animal-sniffer-maven-plugin 1.16 Check JDK compatibility verify check org.codehaus.mojo.signature java18 1.0 Check Android API level compatibility verify check net.sf.androidscents.signature android-api-level-24 7.0_r2 org.apache.maven.plugins maven-clean-plugin 3.1.0 org.apache.maven.plugins maven-install-plugin 2.5.2 org.apache.maven.plugins maven-resources-plugin 3.1.0 org.apache.maven.plugins maven-jar-plugin 3.1.2 org.apache.maven.plugins maven-compiler-plugin 3.8.1 org.apache.maven.plugins maven-project-info-reports-plugin 3.0.0 org.apache.maven.plugins maven-deploy-plugin 2.8.2 org.apache.maven.plugins maven-source-plugin 3.0.1 org.apache.maven.plugins maven-javadoc-plugin 3.0.1 org.apache.maven.plugins maven-surefire-plugin 2.22.1 org.sonatype.plugins nexus-staging-maven-plugin 1.6.8 true ossrh https://oss.sonatype.org/ false org.apache.maven.plugins maven-site-plugin 3.7 en org.apache.maven.doxia doxia-module-markdown 1.7 jdk8 1.8 org.apache.maven.plugins maven-compiler-plugin true -J-Xbootclasspath/p:${settings.localRepository}/com/google/errorprone/javac/${javac.version}/javac-${javac.version}.jar release org.apache.maven.plugins maven-source-plugin attach-sources jar-no-fork org.apache.maven.plugins maven-javadoc-plugin attach-javadocs jar true ${project.build.directory} false false false false false true PRECIS ${project.version} PRECIS ${project.version} true protected org.apache.maven.plugins maven-gpg-plugin 1.6 sign-artifacts verify sign com.github.spotbugs spotbugs-maven-plugin ${spotbugs.version} org.apache.maven.plugins maven-project-info-reports-plugin index org.apache.maven.plugins maven-javadoc-plugin javadoc true ${project.build.directory} false false false false false true PRECIS ${project.version} PRECIS ${project.version} true protected org.testng testng 6.9.4 test precis-1.1.0/readme.md000066400000000000000000000116111414332212100145460ustar00rootroot00000000000000A Java implementation of the **"PRECIS Framework: Preparation, Enforcement, and Comparison of Internationalized Strings in Application Protocols"** and profiles thereof. # About **PRECIS** validates and prepares Unicode strings in a way, so that they can safely be used in application protocols, e.g. when dealing with usernames and passwords. For example, if strings are used for authentication and authorization decisions, the security of an application could be compromised if an entity providing a given string is connected to the wrong account or online resource based on different interpretations of the string. **PRECIS** takes care of such issues. This library supports the following specifications: * [RFC 8264](https://tools.ietf.org/html/rfc8264): PRECIS Framework: Preparation, Enforcement, and Comparison of Internationalized Strings in Application Protocols * [RFC 8265](https://tools.ietf.org/html/rfc8265): Preparation, Enforcement, and Comparison of Internationalized Strings Representing Usernames and Passwords * [RFC 8266](https://tools.ietf.org/html/rfc8266): Preparation, Enforcement, and Comparison of Internationalized Strings Representing Nicknames * [RFC 5893](https://tools.ietf.org/html/rfc5893): Right-to-Left Scripts for Internationalized Domain Names for Applications (IDNA) **PRECIS** obsoletes Stringprep ([RFC 3454](https://tools.ietf.org/html/rfc3454)) and this library obsoletes software like [Libidn's Stringprep class](http://www.gnu.org/software/libidn/javadoc/gnu/inet/encoding/Stringprep.html). ## License This software is licensed under the [MIT license](https://opensource.org/licenses/MIT). ## Build This project is *Maven* based, you can simply build it with common *Maven* commands, e.g. > mvn clean install ## Maven Dependency [![Maven Central](http://img.shields.io/maven-central/v/rocks.xmpp/precis.svg?style=flat)](https://maven-badges.herokuapp.com/maven-central/rocks.xmpp/precis) ```xml rocks.xmpp precis 1.1.0 ``` # API & Samples For most cases, all you need to do is to choose an existing profile from the `PrecisProfiles` class and then prepare or enforce a string: ```java PrecisProfile profile1 = PrecisProfiles.USERNAME_CASE_MAPPED; PrecisProfile profile2 = PrecisProfiles.USERNAME_CASE_PRESERVED; PrecisProfile profile3 = PrecisProfiles.OPAQUE_STRING; PrecisProfile profile4 = PrecisProfiles.NICKNAME; ``` `PrecisProfile` is an abstract class, which you could derive from for defining your custom profile (which however is [discouraged](https://tools.ietf.org/html/rfc8264#section-5.1) by RFC 8264). **[JavaDoc can be found here.](http://sco0ter.bitbucket.io/precis/)** ## Preparation Preparation ensures, that characters are allowed, but (usually) does not apply any mapping rules. The following throws an exception because the string contains a character, which is in the Unicode category *Lt*, which is disallowed. ```java PrecisProfiles.USERNAME_CASE_MAPPED.prepare("\u01C5"); // CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON ``` ## Enforcement Enforcement applies a set of rules (e.g. Unicode normalization, width-mapping, case-folding, ...) to a string in order to transform it to a canonical form and to compare two strings, e.g. for the purpose of authentication. ```java String enforced = PrecisProfiles.USERNAME_CASE_MAPPED.enforce("UpperCaseUsername"); // => uppercaseusername ``` Here, only a simple mapping to lower case is applied. But enforcement does more: ```java String ang = PrecisProfiles.USERNAME_CASE_MAPPED.enforce("\u212B"); // ANGSTROM SIGN String a = PrecisProfiles.USERNAME_CASE_MAPPED.enforce("\u0041\u030A"); // LATIN CAPITAL LETTER A + COMBINING RING ABOVE String aRing = PrecisProfiles.USERNAME_CASE_MAPPED.enforce("\u00C5"); // LATIN CAPITAL LETTER A WITH RING ABOVE // ang.equals(a) == true // a.equals(aRing) == true ``` All three result in `LATIN SMALL LETTER A WITH RING ABOVE` (U+00E5) and are therefore equal after enforcement. The following throws an `InvalidDirectionalityException` because it violates the Bidi Rule (RFC 5893). ```java PrecisProfiles.USERNAME_CASE_MAPPED.enforce("\u0786test"); ``` If a string contains prohibited code points, e.g. symbols in usernames, a `InvalidCodePointException` is thrown, either during preparation or enforcement. ## Comparison You can use `PrecisProfile#toComparableString(CharSequence)` to check, if two strings compare to each other, e.g.: ```java PrecisProfile profile = PrecisProfiles.USERNAME_CASE_MAPPED; if (profile.toComparableString("foobar").equals(profile.toComparableString("FooBar"))) { // username already exists. } ``` Or you can use `PrecisProfile` as a `java.util.Comparator`: ```java 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). precis-1.1.0/src/000077500000000000000000000000001414332212100135565ustar00rootroot00000000000000precis-1.1.0/src/main/000077500000000000000000000000001414332212100145025ustar00rootroot00000000000000precis-1.1.0/src/main/java/000077500000000000000000000000001414332212100154235ustar00rootroot00000000000000precis-1.1.0/src/main/java/rocks/000077500000000000000000000000001414332212100165445ustar00rootroot00000000000000precis-1.1.0/src/main/java/rocks/xmpp/000077500000000000000000000000001414332212100175305ustar00rootroot00000000000000precis-1.1.0/src/main/java/rocks/xmpp/precis/000077500000000000000000000000001414332212100210155ustar00rootroot00000000000000precis-1.1.0/src/main/java/rocks/xmpp/precis/IDNProfile.java000066400000000000000000000071511414332212100236170ustar00rootroot00000000000000package rocks.xmpp.precis; import java.net.IDN; import java.text.Normalizer; import java.util.Locale; import java.util.regex.Pattern; /** * A profile for applying the rules for IDN as in RFC 5895. Although IDN doesn't use Precis, it's still very similar so that we can use the base class. * * @author Christian Schudt * @see RFC 5890 * @see RFC 5895 * @see 3.2. Domainpart */ final class IDNProfile extends PrecisProfile { /** * Whenever dots are used as label separators, the following characters MUST be recognized as dots: U+002E (full stop), U+3002 (ideographic full stop), U+FF0E (fullwidth full stop), U+FF61 (halfwidth ideographic full stop). */ private static final String DOTS = "[.\u3002\uFF0E\uFF61]"; /** * Label separators for domain labels, which should be mapped to "." (dot): IDEOGRAPHIC FULL STOP character (U+3002) */ private static final Pattern LABEL_SEPARATOR = Pattern.compile(DOTS); IDNProfile() { super(false); } /** * Prepares an input string, so that it does not contain invalid labels. * It uses the {@link IDN} class for this check. * * @param input The input string. * @return The unicode domain name. * @throws IllegalArgumentException If the input contains invalid labels. */ @Override public String prepare(CharSequence input) { // First ensure that the input contains valid LDH labels. Otherwise it will throw an IllegalArgumentException. // Then ensure that domain name is in Unicode, e.g. must not contain A-labels ("xn--") return IDN.toUnicode(IDN.toASCII(input.toString(), IDN.USE_STD3_ASCII_RULES), IDN.USE_STD3_ASCII_RULES); } /** * Enforces a domain name by applying the mapping rules of RFC 5895. * * @param input The input string. * @return The enforced string. * @see 2. The General Procedure */ @Override public String enforce(CharSequence input) { // 4. Map IDEOGRAPHIC FULL STOP character (U+3002) to dot. return applyAdditionalMappingRule( // 3. All characters are mapped using Unicode Normalization Form C (NFC). applyNormalizationRule( // 2. Fullwidth and halfwidth characters (those defined with // Decomposition Types and ) are mapped to their // decomposition mappings applyWidthMappingRule( // 1. Uppercase characters are mapped to their lowercase equivalents applyCaseMappingRule(prepare(input))))).toString(); } @Override protected CharSequence applyWidthMappingRule(CharSequence charSequence) { return widthMap(charSequence); } @Override protected CharSequence applyAdditionalMappingRule(CharSequence charSequence) { return LABEL_SEPARATOR.matcher(charSequence).replaceAll("."); } @Override protected CharSequence applyCaseMappingRule(CharSequence charSequence) { return charSequence.toString().toLowerCase(Locale.US); } @Override protected CharSequence applyNormalizationRule(CharSequence charSequence) { return Normalizer.normalize(charSequence, Normalizer.Form.NFC); } @Override protected CharSequence applyDirectionalityRule(CharSequence charSequence) { return charSequence; } }precis-1.1.0/src/main/java/rocks/xmpp/precis/InvalidCodePointException.java000066400000000000000000000030721414332212100267340ustar00rootroot00000000000000/* * 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; /** * Thrown to indicate that a string contains invalid code points after applying preparation or enforcement of the PRECIS framework. * * @author Christian Schudt */ public final class InvalidCodePointException extends IllegalArgumentException { private static final long serialVersionUID = 1313917796057813437L; public InvalidCodePointException(String message) { super(message); } } precis-1.1.0/src/main/java/rocks/xmpp/precis/InvalidDirectionalityException.java000066400000000000000000000030061414332212100300300ustar00rootroot00000000000000/* * 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; /** * Thrown to indicate that the directionality rule has been violated. * * @author Christian Schudt */ public final class InvalidDirectionalityException extends IllegalArgumentException { private static final long serialVersionUID = 4756689912849847361L; public InvalidDirectionalityException(String message) { super(message); } } precis-1.1.0/src/main/java/rocks/xmpp/precis/NicknameProfile.java000066400000000000000000000132631414332212100247330ustar00rootroot00000000000000/* * 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; import java.util.function.Function; /** * The implementation of the PRECIS: Nickname Profile, RFC 8266. * * @author Christian Schudt * @see Preparation, Enforcement, and Comparison of Internationalized Strings Representing Nicknames */ final class NicknameProfile extends PrecisProfile { private static final long serialVersionUID = 8561449693996385797L; NicknameProfile() { super(false); } @Override public final String enforce(final CharSequence input) { // An entity that performs enforcement according to this profile MUST // prepare an input string as described in Section 2.2 and MUST also // apply the following rules specified in Section 2.1 in the order // shown: // 1. Additional Mapping Rule // 2. Normalization Rule final Function rules = in -> prepare(applyNormalizationRule(applyAdditionalMappingRule(in))); final String enforced = stabilize(input, rules); // After all of the foregoing rules have been enforced, the entity MUST // ensure that the nickname is not zero bytes in length (this is done // after enforcing the rules to prevent applications from mistakenly // omitting a nickname entirely, because when internationalized // characters are accepted a non-empty sequence of characters can result // in a zero-length nickname after canonicalization). if (enforced.isEmpty()) { throw new IllegalArgumentException("Nickname must not be empty after applying the rules."); } return enforced; } /** * Comparison uses different rules than enforcement. *

* 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.java000066400000000000000000000067001414332212100256250ustar00rootroot00000000000000/* * 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.java000066400000000000000000001143601414332212100244330ustar00rootroot00000000000000/* * 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: *

    *
  • {@linkplain #prepare(CharSequence) Preparation}: entails only ensuring that the characters in an * individual string are allowed by the underlying PRECIS string class.
  • *
  • {@linkplain #enforce(CharSequence) Enforcement}: entails applying all of the rules specified for a * particular string class or profile thereof to an individual * string, for the purpose of determining if the string can be used * in a given protocol slot.
  • *
  • {@linkplain #compare(CharSequence, CharSequence) Comparison}: entails applying all of the rules specified for a * particular string class or profile thereof to two separate * strings, for the purpose of determining if the two strings are * equivalent.
  • *
*

* * @author Christian Schudt * @see 4. String Classes * @see 5. Profiles * @see PrecisProfiles */ public abstract class PrecisProfile implements Comparator, Serializable { static final Pattern WHITESPACE = Pattern.compile("\\p{Zs}"); /** * Maps full- and half-width characters to their decomposition form. */ private static final Map WIDTH_MAP = new HashMap<>(); /** * Used for the Bidi Rule. * EN, ES, CS, ET, ON, BN, or NSM. */ private static final int EN_ES_CS_ET_ON_BN_NSM = 1 << Character.DIRECTIONALITY_EUROPEAN_NUMBER | 1 << Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR | 1 << Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR | 1 << Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR | 1 << Character.DIRECTIONALITY_OTHER_NEUTRALS | 1 << Character.DIRECTIONALITY_BOUNDARY_NEUTRAL | 1 << Character.DIRECTIONALITY_NONSPACING_MARK; /** * Used for the Bidi Rule. * L, EN, ES, CS, ET, ON, BN, or NSM. */ private static final int L_EN_ES_CS_ET_ON_BN_NSM = 1 // 1 << Character.DIRECTIONALITY_LEFT_TO_RIGHT | EN_ES_CS_ET_ON_BN_NSM; /** * Used for the Bidi Rule. * R, AL, AN, EN, ES, CS, ET, ON, BN, or NSM. */ private static final int R_AL_AN_EN_ES_CS_ET_ON_BN_NSM = 1 << Character.DIRECTIONALITY_RIGHT_TO_LEFT | 1 << Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC | 1 << Character.DIRECTIONALITY_ARABIC_NUMBER | EN_ES_CS_ET_ON_BN_NSM; /** * Used for the Bidi Rule. * EN or AN. */ private static final int EN_AN = 1 << Character.DIRECTIONALITY_EUROPEAN_NUMBER | 1 << Character.DIRECTIONALITY_ARABIC_NUMBER; private static final long serialVersionUID = -8190355872090764571L; // Key — Original Character // Value — Replacement character static { // Fullwidth ASCII variants (Latin Symbols, Punctuation, Numbers, and Alphabet) for (char c = '\uFF01'; c <= '\uFF5E'; c++) { char mapping = (char) (c - '\uFEE0'); WIDTH_MAP.put(c, mapping); } // Fullwidth brackets WIDTH_MAP.put('\uFF5F', '\u2985'); // FULLWIDTH LEFT WHITE PARENTHESIS WIDTH_MAP.put('\uFF60', '\u2986'); // FULLWIDTH RIGHT WHITE PARENTHESIS // Halfwidth CJK punctuation WIDTH_MAP.put('\uFF61', '\u3002'); // HALFWIDTH IDEOGRAPHIC FULL STOP WIDTH_MAP.put('\uFF62', '\u300C'); // HALFWIDTH LEFT CORNER BRACKET WIDTH_MAP.put('\uFF63', '\u300D'); // HALFWIDTH RIGHT CORNER BRACKET WIDTH_MAP.put('\uFF64', '\u3001'); // HALFWIDTH IDEOGRAPHIC COMMA // Halfwidth Katakana variants WIDTH_MAP.put('\uFF65', '\u30FB'); // HALFWIDTH KATAKANA MIDDLE DOT WIDTH_MAP.put('\uFF66', '\u30F2'); // HALFWIDTH KATAKANA LETTER WO WIDTH_MAP.put('\uFF67', '\u30A1'); // HALFWIDTH KATAKANA LETTER SMALL A WIDTH_MAP.put('\uFF68', '\u30A3'); // HALFWIDTH KATAKANA LETTER SMALL I WIDTH_MAP.put('\uFF69', '\u30A5'); // HALFWIDTH KATAKANA LETTER SMALL U WIDTH_MAP.put('\uFF6A', '\u30A7'); // HALFWIDTH KATAKANA LETTER SMALL E WIDTH_MAP.put('\uFF6B', '\u30A9'); // HALFWIDTH KATAKANA LETTER SMALL O WIDTH_MAP.put('\uFF6C', '\u30E3'); // HALFWIDTH KATAKANA LETTER SMALL YA WIDTH_MAP.put('\uFF6D', '\u30E5'); // HALFWIDTH KATAKANA LETTER SMALL YU WIDTH_MAP.put('\uFF6E', '\u30E7'); // HALFWIDTH KATAKANA LETTER SMALL YO WIDTH_MAP.put('\uFF6F', '\u30C3'); // HALFWIDTH KATAKANA LETTER SMALL TU WIDTH_MAP.put('\uFF70', '\u30FC'); // HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK WIDTH_MAP.put('\uFF71', '\u30A2'); // HALFWIDTH KATAKANA LETTER A WIDTH_MAP.put('\uFF72', '\u30A4'); // HALFWIDTH KATAKANA LETTER I WIDTH_MAP.put('\uFF73', '\u30A6'); // HALFWIDTH KATAKANA LETTER U WIDTH_MAP.put('\uFF74', '\u30A8'); // HALFWIDTH KATAKANA LETTER E WIDTH_MAP.put('\uFF75', '\u30AA'); // HALFWIDTH KATAKANA LETTER O WIDTH_MAP.put('\uFF76', '\u30AB'); // HALFWIDTH KATAKANA LETTER KA WIDTH_MAP.put('\uFF77', '\u30AD'); // HALFWIDTH KATAKANA LETTER KI WIDTH_MAP.put('\uFF78', '\u30AF'); // HALFWIDTH KATAKANA LETTER KU WIDTH_MAP.put('\uFF79', '\u30B1'); // HALFWIDTH KATAKANA LETTER KE WIDTH_MAP.put('\uFF7A', '\u30B3'); // HALFWIDTH KATAKANA LETTER KO WIDTH_MAP.put('\uFF7B', '\u30B5'); // HALFWIDTH KATAKANA LETTER SA WIDTH_MAP.put('\uFF7C', '\u30B7'); // HALFWIDTH KATAKANA LETTER SI WIDTH_MAP.put('\uFF7D', '\u30B9'); // HALFWIDTH KATAKANA LETTER SU WIDTH_MAP.put('\uFF7E', '\u30BB'); // HALFWIDTH KATAKANA LETTER SE WIDTH_MAP.put('\uFF7F', '\u30BD'); // HALFWIDTH KATAKANA LETTER SO WIDTH_MAP.put('\uFF80', '\u30BF'); // HALFWIDTH KATAKANA LETTER TA WIDTH_MAP.put('\uFF81', '\u30C1'); // HALFWIDTH KATAKANA LETTER TI WIDTH_MAP.put('\uFF82', '\u30C4'); // HALFWIDTH KATAKANA LETTER TU WIDTH_MAP.put('\uFF83', '\u30C6'); // HALFWIDTH KATAKANA LETTER TE WIDTH_MAP.put('\uFF84', '\u30C8'); // HALFWIDTH KATAKANA LETTER TO WIDTH_MAP.put('\uFF85', '\u30CA'); // HALFWIDTH KATAKANA LETTER NA WIDTH_MAP.put('\uFF86', '\u30CB'); // HALFWIDTH KATAKANA LETTER NI WIDTH_MAP.put('\uFF87', '\u30CC'); // HALFWIDTH KATAKANA LETTER NU WIDTH_MAP.put('\uFF88', '\u30CD'); // HALFWIDTH KATAKANA LETTER NE WIDTH_MAP.put('\uFF89', '\u30CE'); // HALFWIDTH KATAKANA LETTER NO WIDTH_MAP.put('\uFF8A', '\u30CF'); // HALFWIDTH KATAKANA LETTER HA WIDTH_MAP.put('\uFF8B', '\u30D2'); // HALFWIDTH KATAKANA LETTER HI WIDTH_MAP.put('\uFF8C', '\u30D5'); // HALFWIDTH KATAKANA LETTER HU WIDTH_MAP.put('\uFF8D', '\u30D8'); // HALFWIDTH KATAKANA LETTER HE WIDTH_MAP.put('\uFF8E', '\u30DB'); // HALFWIDTH KATAKANA LETTER HO WIDTH_MAP.put('\uFF8F', '\u30DE'); // HALFWIDTH KATAKANA LETTER MA WIDTH_MAP.put('\uFF90', '\u30DF'); // HALFWIDTH KATAKANA LETTER MI WIDTH_MAP.put('\uFF91', '\u30E0'); // HALFWIDTH KATAKANA LETTER MU WIDTH_MAP.put('\uFF92', '\u30E1'); // HALFWIDTH KATAKANA LETTER ME WIDTH_MAP.put('\uFF93', '\u30E2'); // HALFWIDTH KATAKANA LETTER MO WIDTH_MAP.put('\uFF94', '\u30E4'); // HALFWIDTH KATAKANA LETTER YA WIDTH_MAP.put('\uFF95', '\u30E6'); // HALFWIDTH KATAKANA LETTER YU WIDTH_MAP.put('\uFF96', '\u30E8'); // HALFWIDTH KATAKANA LETTER YO WIDTH_MAP.put('\uFF97', '\u30E9'); // HALFWIDTH KATAKANA LETTER RA WIDTH_MAP.put('\uFF98', '\u30EA'); // HALFWIDTH KATAKANA LETTER RI WIDTH_MAP.put('\uFF99', '\u30EB'); // HALFWIDTH KATAKANA LETTER RU WIDTH_MAP.put('\uFF9A', '\u30EC'); // HALFWIDTH KATAKANA LETTER RE WIDTH_MAP.put('\uFF9B', '\u30ED'); // HALFWIDTH KATAKANA LETTER RO WIDTH_MAP.put('\uFF9C', '\u30EF'); // HALFWIDTH KATAKANA LETTER WA WIDTH_MAP.put('\uFF9D', '\u30F3'); // HALFWIDTH KATAKANA LETTER N WIDTH_MAP.put('\uFF9E', '\u3099'); // HALFWIDTH KATAKANA VOICED SOUND MARK WIDTH_MAP.put('\uFF9F', '\u309A'); // HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK // Halfwidth Hangul variants WIDTH_MAP.put('\uFFA0', '\u3164'); // HALFWIDTH HANGUL FILLER // KIYEOK - HIEUH for (char c = '\uFFA1'; c <= '\uFFBE'; c++) { char mapping = (char) (c - '\uCE70'); WIDTH_MAP.put(c, mapping); } // A - E for (char c = '\uFFC2'; c <= '\uFFC7'; c++) { char mapping = (char) (c - '\uCE73'); WIDTH_MAP.put(c, mapping); } // YEO - OE for (char c = '\uFFCA'; c <= '\uFFCF'; c++) { char mapping = (char) (c - '\uCE75'); WIDTH_MAP.put(c, mapping); } // YO - YU for (char c = '\uffd2'; c <= '\uFFD7'; c++) { char mapping = (char) (c - '\uCE77'); WIDTH_MAP.put(c, mapping); } WIDTH_MAP.put('\uFFDA', '\u3161'); // HALFWIDTH HANGUL LETTER EU WIDTH_MAP.put('\uFFDB', '\u3162'); // HALFWIDTH HANGUL LETTER YI WIDTH_MAP.put('\uFFDC', '\u3163'); // HALFWIDTH HANGUL LETTER I // Fullwidth symbol variants WIDTH_MAP.put('\uFFE0', '\u00A2'); // FULLWIDTH CENT SIGN WIDTH_MAP.put('\uFFE1', '\u00A3'); // FULLWIDTH POUND SIGN WIDTH_MAP.put('\uFFE2', '\u00AC'); // FULLWIDTH NOT SIGN WIDTH_MAP.put('\uFFE3', '\u00AF'); // FULLWIDTH MACRON WIDTH_MAP.put('\uFFE4', '\u00A6'); // FULLWIDTH BROKEN BAR WIDTH_MAP.put('\uFFE5', '\u00A5'); // FULLWIDTH YEN SIGN WIDTH_MAP.put('\uFFE6', '\u20A9'); // FULLWIDTH WON SIGN // Halfwidth symbol variants WIDTH_MAP.put('\uFFE8', '\u2502'); // HALFWIDTH FORMS LIGHT VERTICAL WIDTH_MAP.put('\uFFE9', '\u2190'); // HALFWIDTH LEFTWARDS ARROW WIDTH_MAP.put('\uFFEA', '\u2191'); // HALFWIDTH UPWARDS ARROW WIDTH_MAP.put('\uFFEB', '\u2192'); // HALFWIDTH RIGHTWARDS ARROW WIDTH_MAP.put('\uFFEC', '\u2193'); // HALFWIDTH DOWNWARDS ARROW WIDTH_MAP.put('\uFFED', '\u25A0'); // HALFWIDTH BLACK SQUARE WIDTH_MAP.put('\uFFEE', '\u25CB'); // HALFWIDTH WHITE CIRCLE } private final boolean identifierClass; /** * @param identifierClass True, if the base class for this profile is the "IdentifierClass"; false if it's the "FreeFormClass". */ protected PrecisProfile(boolean identifierClass) { this.identifierClass = identifierClass; } /** * Returns true if the code point is a letter or digit character (as per the PRECIS specification), i.e. in the general category "Ll", "Lu", "Lo", "Nd", "Lm", "Mn" or "Mc". * * @param cp The code point. * @return If the code point is a letter or digit character. * @see 9.1. LetterDigits (A) */ private static boolean isLetterDigit(final int cp) { // Ll, Lu, Lo, Nd, Lm, Mn, Mc return ((((1 << Character.LOWERCASE_LETTER) | (1 << Character.UPPERCASE_LETTER) | (1 << Character.OTHER_LETTER) | (1 << Character.DECIMAL_DIGIT_NUMBER) | (1 << Character.MODIFIER_LETTER) | (1 << Character.NON_SPACING_MARK) | (1 << Character.COMBINING_SPACING_MARK)) >> Character.getType(cp)) & 1) != 0; } /** * Returns true if the code point is in the exception category. * * @param cp The code point. * @return If the code point is backwards compatible. * @see 9.6. Exceptions (F) */ private static boolean isExceptionallyValid(final int cp) { // PVALID -- Would otherwise have been DISALLOWED // // 00DF; PVALID # LATIN SMALL LETTER SHARP S // 03C2; PVALID # GREEK SMALL LETTER FINAL SIGMA // 06FD; PVALID # ARABIC SIGN SINDHI AMPERSAND // 06FE; PVALID # ARABIC SIGN SINDHI POSTPOSITION MEN // 0F0B; PVALID # TIBETAN MARK INTERSYLLABIC TSHEG // 3007; PVALID # IDEOGRAPHIC NUMBER ZERO return cp == 0x00DF || cp == 0x03C2 || cp == 0x06FD || cp == 0x06FE || cp == 0x0F0B || cp == 0x3007; } /** * Returns true if the code point is in the exception category. * * @param cp The code point. * @return If the code point is backwards compatible. * @see 9.6. Exceptions (F) */ private static boolean isExceptionallyDisallowed(final int cp) { // 0640; DISALLOWED # ARABIC TATWEEL // 07FA; DISALLOWED # NKO LAJANYALAN // 302E; DISALLOWED # HANGUL SINGLE DOT TONE MARK // 302F; DISALLOWED # HANGUL DOUBLE DOT TONE MARK // 3031; DISALLOWED # VERTICAL KANA REPEAT MARK // 3032; DISALLOWED # VERTICAL KANA REPEAT WITH VOICED SOUND MARK // 3033; DISALLOWED # VERTICAL KANA REPEAT MARK UPPER HALF // 3034; DISALLOWED # VERTICAL KANA REPEAT WITH VOICED SOUND MARK UPPER HA // 3035; DISALLOWED # VERTICAL KANA REPEAT MARK LOWER HALF // 303B; DISALLOWED # VERTICAL IDEOGRAPHIC ITERATION MARK return cp == 0x0640 || cp == 0x07FA || cp == 0x302E || cp == 0x302F || (cp >= 0x3031 && cp <= 0x3035) || cp == 0x303B; } /** * Returns true if the code point is backwards compatible. * * @param cp The code point. * @return If the code point is backwards compatible. * @see 9.7. BackwardCompatible (G) */ @SuppressWarnings("unused") private static boolean isBackwardsCompatible(final int cp) { // Currently this category consists of the empty set, therefore return false. return false; } /** * Returns true if the code point is a join control character. * * @param cp The code point. * @return If the code point is a join control character. * @see 9.8. JoinControl (H) */ private static boolean isJoinControl(final int cp) { // U+200C ZERO WIDTH NON-JOINER // U+200D ZERO WIDTH JOINER return cp == 0x200C || cp == 0x200D; } /** * Returns true if the code point is an old hangul jamo character. * * @return If the code point is an old hangul jamo character. * @see 9.9. OldHangulJamo (I) * @see Unicode Standard, Chapter 18 */ private static boolean isOldHangulJamo(final int cp) { // Hangul Jamo: U+1100–U+11FF // Hangul Jamo Extended-A: U+A960–U+A97F // Hangul Jamo Extended-B: U+D7B0–U+D7FF return (cp >= 0x1100 && cp <= 0x11FF) || (cp >= 0xA960 && cp <= 0xA97F) || (cp >= 0xD7B0 && cp <= 0xD7FF); } /** * Returns true if the code point is unassigned. * * @return If the code point is unassigned. * @see 9.10. Unassigned (J) */ static boolean isUnassigned(final int cp) { // General_Category(cp) is in {Cn} and // Noncharacter_Code_Point(cp) = False return !Character.isDefined(cp) && !isNonCharacter(cp); } /** * Returns true if the code point is in the ASCII7 category. * * @return If the code point is in the ASCII7 category. * @see 9.11. ASCII7 (K) */ private static boolean isASCII7(final int cp) { // cp is in {0021..007E} return cp >= 0x0021 && cp <= 0x007E; } /** * Returns true if the code point is a control character. * * @return If the code point is a control character. * @see 9.12. Controls (L) */ private static boolean isControl(final int cp) { return Character.isISOControl(cp); } /** * http://unicode.org/Public/8.0.0/ucd/DerivedCoreProperties.txt */ private static boolean isDefaultIgnorable(final int cp) { return cp == 0x00AD || cp == 0x034F || cp == 0x061C || (cp >= 0x115F && cp <= 0x1160) || (cp >= 0x17B4 && cp <= 0x17B5) || (cp >= 0x180B && cp <= 0x180E) || (cp >= 0x200B && cp <= 0x200F) || (cp >= 0x202A && cp <= 0x202E) || (cp >= 0x2060 && cp <= 0x206F) || cp == 0x3164 || (cp >= 0xFE00 && cp <= 0xFE0F) || cp == 0xFEFF || cp == 0xFFA0 || (cp >= 0xFFF0 && cp <= 0xFFF8); } private static boolean isNonCharacter(final int cp) { return (cp >= 0xFDD0 && cp <= 0xFDEF) || (cp >= 0xFFFE && cp <= 0xFFFF); } /** * Returns true if the code point is ignorable * * @return If the code point is ignorable. * @see 9.13. PrecisIgnorableProperties (M) */ private static boolean isIgnorable(final int cp) { // Default_Ignorable_Code_Point(cp) = True or // Noncharacter_Code_Point(cp) = True return isDefaultIgnorable(cp) || isNonCharacter(cp); } /** * Returns true if the code point is a space character (as per the PRECIS specification), i.e. in the general category "Zs". * * @param cp The code point. * @return If the code point is a space character. * @see 9.14. Spaces (N) */ private static boolean isSpace(final int cp) { // Zs return (((1 << Character.SPACE_SEPARATOR) >> Character.getType(cp)) & 1) != 0; } /** * Returns true if the code point is a symbol character, i.e. in the general category "Sm", "Sc", "Sk" or "So". * * @param cp The code point. * @return If the code point is a symbol character. * @see 9.15. Symbols (O) */ private static boolean isSymbol(final int cp) { // Sm, Sc, Sk, So return ((((1 << Character.MATH_SYMBOL) | (1 << Character.CURRENCY_SYMBOL) | (1 << Character.MODIFIER_SYMBOL) | (1 << Character.OTHER_SYMBOL)) >> Character.getType(cp)) & 1) != 0; } /** * Returns true if the code point is a punctuation character, i.e. in the general category "Pc", "Pd", "Ps", "Pe", "Pi", "Pf" or "Po". * * @param cp The code point. * @return If the code point is a punctuation character. * @see 9.16. Punctuation (P) */ private static boolean isPunctuation(final int cp) { // Pc, Pd, Ps, Pe, Pi, Pf, Po return ((((1 << Character.CONNECTOR_PUNCTUATION) | (1 << Character.DASH_PUNCTUATION) | (1 << Character.START_PUNCTUATION) | (1 << Character.END_PUNCTUATION) | (1 << Character.INITIAL_QUOTE_PUNCTUATION) | (1 << Character.FINAL_QUOTE_PUNCTUATION) | (1 << Character.OTHER_PUNCTUATION)) >> Character.getType(cp)) & 1) != 0; } /** * Returns true, if the code point has compatibility equivalents as explained in the Unicode Standard. * * @param cp The code point. * @return If the code point is in in the category "HasCompat". * @see 9.17. HasCompat (Q) */ static boolean hasCompatibilityEquivalent(final int cp) { // toNFKC(cp) != cp CharSequence s = new String(new int[]{cp}, 0, 1); return !Normalizer.isNormalized(s, Normalizer.Form.NFKC); } /** * Returns true if the code point is in the category of letters and digits other than the "traditional" letters and digits, i.e. in the general category "Lt", "Nl", "No" or "Me". * * @param cp The code point. * @return If the code point is in the category of letters and digits other than the "traditional" letters and digits. * @see 9.18. OtherLetterDigits (R) */ private static boolean isOtherLetterDigit(final int cp) { // Lt, Nl, No, Me return ((((1 << Character.TITLECASE_LETTER) | (1 << Character.LETTER_NUMBER) | (1 << Character.OTHER_NUMBER) | (1 << Character.ENCLOSING_MARK)) >> Character.getType(cp)) & 1) != 0; } /** * Maps full-width and half-width characters to their decomposition mappings. * * @param input The input string. * @return The mapped string. * @see Halfwidth and Fullwidth Forms */ protected static CharSequence widthMap(CharSequence input) { StringBuilder sb = new StringBuilder(input); for (int i = 0; i < input.length(); i++) { Character c = WIDTH_MAP.get(input.charAt(i)); if (c != null) { sb.setCharAt(i, c); } } return sb; } /** * Applies the default case folding to a string. * * @param input The input string. * @return The case folded string. */ protected static CharSequence caseMap(final CharSequence input) { return input.toString().toLowerCase(Locale.US); } /** * Checks the Bidi Rule. * * @param label The label to check. * @throws InvalidDirectionalityException If the label violates the Bidi Rule. */ protected static void checkBidiRule(final CharSequence label) { if (label == null) { return; } if (label.length() == 0) { return; } // 1. The first character must be a character with Bidi property L, R, // or AL. If it has the R or AL property, it is an RTL label; if it // has the L property, it is an LTR label. int i = 0; int cp = Character.codePointAt(label, i); i += Character.charCount(cp); final byte dir1stChar = Character.getDirectionality(cp); final boolean isLTRLabel = dir1stChar == Character.DIRECTIONALITY_LEFT_TO_RIGHT; final boolean isRTLLabel = dir1stChar == Character.DIRECTIONALITY_RIGHT_TO_LEFT || dir1stChar == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC; if (!isLTRLabel && !isRTLLabel) { throw new InvalidDirectionalityException("Bidi Rule 1: The first character must be a character with Bidi property L, R or AL."); } // In order to check condition 3 and 6, get the Bidi property of the last character, which has not the property NSM. byte directionalityLastNonNSMCharacter; int length = label.length(); do { cp = Character.codePointBefore(label, length); length -= Character.charCount(cp); directionalityLastNonNSMCharacter = Character.getDirectionality(cp); if (directionalityLastNonNSMCharacter != Character.DIRECTIONALITY_NONSPACING_MARK) { break; } } while (length > 0); int directionalityMask = 0; while (i < length + 1) { cp = Character.codePointAt(label, i); i += Character.charCount(cp); directionalityMask |= 1 << Character.getDirectionality(cp); } if (isRTLLabel) { // 2. In an RTL label, only characters with the Bidi properties R, AL, // AN, EN, ES, CS, ET, ON, BN, or NSM are allowed. if ((directionalityMask & ~R_AL_AN_EN_ES_CS_ET_ON_BN_NSM) != 0) { throw new InvalidDirectionalityException("Bidi Rule 2: In an RTL label, only characters with the Bidi properties R, AL, AN, EN, ES, CS, ET, ON, BN, or NSM are allowed."); } // 3. In an RTL label, the end of the label must be a character with // Bidi property R, AL, EN, or AN, followed by zero or more // characters with Bidi property NSM. if (directionalityLastNonNSMCharacter != Character.DIRECTIONALITY_RIGHT_TO_LEFT && directionalityLastNonNSMCharacter != Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC && directionalityLastNonNSMCharacter != Character.DIRECTIONALITY_EUROPEAN_NUMBER && directionalityLastNonNSMCharacter != Character.DIRECTIONALITY_ARABIC_NUMBER) { throw new InvalidDirectionalityException("Bidi Rule 3: In an RTL label, the end of the label must be a character with Bidi property R, AL, EN, or AN."); } // 4. In an RTL label, if an EN is present, no AN may be present, and // vice versa. if ((directionalityMask & EN_AN) == EN_AN) { throw new InvalidDirectionalityException("Bidi Rule 4: In an RTL label, if an EN is present, no AN may be present, and vice versa."); } } else { // 5. In an LTR label, only characters with the Bidi properties L, EN, // ES, CS, ET, ON, BN, or NSM are allowed. if ((directionalityMask & ~L_EN_ES_CS_ET_ON_BN_NSM) != 0) { throw new InvalidDirectionalityException("Bidi Rule 5: In an LTR label, only characters with the Bidi properties L, EN, ES, CS, ET, ON, BN, or NSM are allowed."); } // 6. In an LTR label, the end of the label must be a character with // Bidi property L or EN, followed by zero or more characters with // Bidi property NSM. if (directionalityLastNonNSMCharacter != Character.DIRECTIONALITY_LEFT_TO_RIGHT && directionalityLastNonNSMCharacter != Character.DIRECTIONALITY_EUROPEAN_NUMBER) { throw new InvalidDirectionalityException("Bidi Rule 6: In an LTR label, the end of the label must be a character with Bidi property L or EN."); } } } /** * Preparation entails only ensuring that the characters in an * individual string are allowed by the underlying PRECIS string * class. * * @param input The input string. * @return For convenience, returns the same string as the input string. * @throws InvalidCodePointException If the input contains invalid code points (which are disallowed by the underlying Precis String class). */ public String prepare(final CharSequence input) { final int length = input.length(); int offset = 0; while (offset < length) { final int codePoint = Character.codePointAt(input, offset); boolean valid = false; // If .cp. .in. Exceptions Then Exceptions(cp); // Else If .cp. .in. BackwardCompatible Then BackwardCompatible(cp); // Else If .cp. .in. Unassigned Then UNASSIGNED; // Else If .cp. .in. ASCII7 Then PVALID; // Else If .cp. .in. JoinControl Then CONTEXTJ; // Else If .cp. .in. OldHangulJamo Then DISALLOWED; // Else If .cp. .in. PrecisIgnorableProperties Then DISALLOWED; // Else If .cp. .in. Controls Then DISALLOWED; // Else If .cp. .in. HasCompat Then ID_DIS or FREE_PVAL; // Else If .cp. .in. LetterDigits Then PVALID; // Else If .cp. .in. OtherLetterDigits Then ID_DIS or FREE_PVAL; // Else If .cp. .in. Spaces Then ID_DIS or FREE_PVAL; // Else If .cp. .in. Symbols Then ID_DIS or FREE_PVAL; // Else If .cp. .in. Punctuation Then ID_DIS or FREE_PVAL; // Else DISALLOWED; if (isExceptionallyValid(codePoint)) { valid = true; } else if (isExceptionallyDisallowed(codePoint)) { valid = false; } else if (isBackwardsCompatible(codePoint)) { valid = true; } else if (isUnassigned(codePoint)) { valid = false; } else if (isASCII7(codePoint)) { valid = true; } else if (isJoinControl(codePoint)) { valid = false; // TODO } else if (isOldHangulJamo(codePoint)) { valid = false; } else if (isIgnorable(codePoint)) { valid = false; } else if (isControl(codePoint)) { valid = false; } else if (hasCompatibilityEquivalent(codePoint)) { valid = !identifierClass; } else if (isLetterDigit(codePoint)) { valid = true; } else if (isOtherLetterDigit(codePoint)) { valid = !identifierClass; } else if (isSpace(codePoint)) { valid = !identifierClass; } else if (isSymbol(codePoint)) { valid = !identifierClass; } else if (isPunctuation(codePoint)) { valid = !identifierClass; } if (!valid) { throw new InvalidCodePointException("Invalid code point at position " + offset + ": 0x" + Integer.toHexString(codePoint)); } offset += Character.charCount(codePoint); } return input.toString(); } /** * Enforcement entails applying all of the rules specified for a * particular string class or profile thereof to an individual * string, for the purpose of determining if the string can be used * in a given protocol slot. *

* 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 Function rules) { String s1 = rules.apply(input); String s2; for (int i = 0; i < 3; i++) { s2 = rules.apply(s1); if (s1.equals(s2)) { // output string is stable. return s2; } s1 = s2; } // 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. throw new InvalidCodePointException("Input string did not stabilze after applying the rules three additional times."); } } precis-1.1.0/src/main/java/rocks/xmpp/precis/PrecisProfiles.java000066400000000000000000000133011414332212100246070ustar00rootroot00000000000000/* * 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; /** * This class provides common PRECIS profiles, mainly specified by Preparation, Enforcement, and Comparison of Internationalized Strings * Representing Usernames and Passwords (RFC 8265). Each profile offers methods for preparation, enforcement and comparison of Unicode strings. *

Preparation

* 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.java000066400000000000000000000110021414332212100247520ustar00rootroot00000000000000/* * 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.java000066400000000000000000000044271414332212100257760ustar00rootroot00000000000000package 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.java000066400000000000000000000037551414332212100242160ustar00rootroot00000000000000/* * 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/000077500000000000000000000000001414332212100145355ustar00rootroot00000000000000precis-1.1.0/src/test/java/000077500000000000000000000000001414332212100154565ustar00rootroot00000000000000precis-1.1.0/src/test/java/rocks/000077500000000000000000000000001414332212100165775ustar00rootroot00000000000000precis-1.1.0/src/test/java/rocks/xmpp/000077500000000000000000000000001414332212100175635ustar00rootroot00000000000000precis-1.1.0/src/test/java/rocks/xmpp/precis/000077500000000000000000000000001414332212100210505ustar00rootroot00000000000000precis-1.1.0/src/test/java/rocks/xmpp/precis/BidiRuleTest.java000066400000000000000000000054701414332212100242600ustar00rootroot00000000000000/* * 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.java000066400000000000000000000023421414332212100245070ustar00rootroot00000000000000package 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.java000066400000000000000000000062141414332212100256260ustar00rootroot00000000000000/* * 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.java000066400000000000000000000066151414332212100256310ustar00rootroot00000000000000/* * 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.java000066400000000000000000000045371414332212100265260ustar00rootroot00000000000000/* * 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.java000066400000000000000000000165231414332212100276050ustar00rootroot00000000000000/* * 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 rules) { for (int cp = Character.MIN_CODE_POINT; cp < Character.MAX_CODE_POINT; cp++) { String input = new String(Character.toChars(cp)); try { String applied = rules.apply(input); String applied2 = rules.apply(applied); Assert.assertEquals(applied, applied2); } catch (IllegalArgumentException ignore) { } } } } precis-1.1.0/src/test/java/rocks/xmpp/precis/UsernameCasePreservedProfileTest.java000066400000000000000000000033551414332212100303350ustar00rootroot00000000000000/* * 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_PRESERVED; /** * @author Christian Schudt */ public class UsernameCasePreservedProfileTest { @Test public void testConfusableCharacters() { // Everything else is tested with the UsernameCaseMappedProfileTest. Assert.assertEquals(USERNAME_CASE_PRESERVED.enforce("ABC"), "ABC"); } @Test public void testIdempotencyEnforcement() { UsernameCaseMappedProfileTest.testIdempotency(USERNAME_CASE_PRESERVED::enforce); } } precis-1.1.0/src/test/java/rocks/xmpp/precis/XmppLocalpartProfileTest.java000066400000000000000000000057711414332212100266740ustar00rootroot00000000000000/* * 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; import static rocks.xmpp.precis.PrecisProfiles.XMPP_LOCALPART; import java.util.Arrays; /** * @author Florian Schmaus */ public class XmppLocalpartProfileTest { @Test(expectedExceptions = IllegalArgumentException.class) public void testQuoteDisallowed() { XMPP_LOCALPART.enforce("\""); } @Test(expectedExceptions = IllegalArgumentException.class) public void testAmpersandDisallowed() { XMPP_LOCALPART.enforce("&"); } @Test(expectedExceptions = IllegalArgumentException.class) public void testApostropheDisallowed() { XMPP_LOCALPART.enforce("\'"); } @Test(expectedExceptions = IllegalArgumentException.class) public void testSolidusDisallowed() { XMPP_LOCALPART.enforce("/"); } @Test(expectedExceptions = IllegalArgumentException.class) public void testColonDisallowed() { XMPP_LOCALPART.enforce(":"); } @Test(expectedExceptions = IllegalArgumentException.class) public void testLessThanSignDisallowed() { XMPP_LOCALPART.enforce("<"); } @Test(expectedExceptions = IllegalArgumentException.class) public void testGreaterThanSignDisallowed() { XMPP_LOCALPART.enforce(">"); } @Test(expectedExceptions = IllegalArgumentException.class) public void testCommercialAtDisallowed() { XMPP_LOCALPART.enforce("@"); } @Test(expectedExceptions = IllegalArgumentException.class) public void testEmptyStringDisallowed() { XMPP_LOCALPART.enforce(""); } @Test(expectedExceptions = IllegalArgumentException.class) public void testLongerThan1023Disallowed() { char[] chars = new char[1024]; Arrays.fill(chars, 'a'); String stringExceedingLengthLimit = new String(chars); XMPP_LOCALPART.enforce(stringExceedingLengthLimit); } }