001/**
002 *
003 * Copyright 2019-2020 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jxmpp.strings.testframework;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.HashSet;
022import java.util.Iterator;
023import java.util.List;
024import java.util.Set;
025
026import org.jxmpp.stringprep.XmppStringprep;
027
028public class KnownFailures {
029
030        private static final Set<XmppStringprepStringCoupling> VALID_JIDS_WHITELIST = new HashSet<>();
031        private static final Set<XmppStringprepStringCoupling> INVALID_JIDS_WHITELIST = new HashSet<>();
032
033        static {
034                // ICU4J
035                whitelistValidJid(XmppStringPrepper.ICU4J, "fußball@example.com");
036
037                whitelistInvalidJid(XmppStringPrepper.ICU4J, "♚@example.com");
038                whitelistInvalidJid(XmppStringPrepper.ICU4J, "henry\u2163@example.com");
039                whitelistInvalidJid(XmppStringPrepper.ICU4J, "user@@host/resource");
040                whitelistInvalidJid(XmppStringPrepper.ICU4J, "user@@host");
041                whitelistInvalidJid(XmppStringPrepper.ICU4J, "user@@");
042                whitelistInvalidJid(XmppStringPrepper.ICU4J, "username@example.org@example.org");
043
044                // LIBIDN
045                whitelistValidJid(XmppStringPrepper.LIBIDN, "fußball@example.com");
046
047                whitelistInvalidJid(XmppStringPrepper.LIBIDN, "♚@example.com");
048                whitelistInvalidJid(XmppStringPrepper.LIBIDN, "henry\u2163@example.com");
049                whitelistInvalidJid(XmppStringPrepper.LIBIDN, "user@@host/resource");
050                whitelistInvalidJid(XmppStringPrepper.LIBIDN, "user@@host");
051                whitelistInvalidJid(XmppStringPrepper.LIBIDN, "user@@");
052                whitelistInvalidJid(XmppStringPrepper.LIBIDN, "xsf@muc.xmpp.org/؜x");
053                whitelistInvalidJid(XmppStringPrepper.LIBIDN, "username@example.org@example.org");
054
055
056                // ROCKS_XMPP_PRECIS
057                whitelistValidJid(XmppStringPrepper.ROCKS_XMPP_PRECIS, "ς@example.com");
058                whitelistValidJid(XmppStringPrepper.ROCKS_XMPP_PRECIS, "user@[2001:638:a000:4134::ffff:40]");
059                whitelistValidJid(XmppStringPrepper.ROCKS_XMPP_PRECIS, "user@[2001:638:a000:4134::ffff:40%eno1]");
060                whitelistValidJid(XmppStringPrepper.ROCKS_XMPP_PRECIS, "user@averylongdomainpartisstillvalideventhoughitexceedsthesixtyfourbytelimitofdnslabels");
061
062
063                // SIMPLE
064                whitelistValidJid(XmppStringPrepper.SIMPLE, "ς@example.com");
065
066                whitelistInvalidJid(XmppStringPrepper.SIMPLE, "♚@example.com");
067                whitelistInvalidJid(XmppStringPrepper.SIMPLE, "henry\u2163@example.com");
068                whitelistInvalidJid(XmppStringPrepper.SIMPLE, "user@@host/resource");
069                whitelistInvalidJid(XmppStringPrepper.SIMPLE, "user@@host");
070                whitelistInvalidJid(XmppStringPrepper.SIMPLE, "user@@");
071                whitelistInvalidJid(XmppStringPrepper.SIMPLE, "xsf@muc.xmpp.org/؜x");
072                whitelistInvalidJid(XmppStringPrepper.SIMPLE, "username@example.org@example.org");
073
074        }
075
076        /**
077         * Check a XMPP Strings Testframeworkresult against the list of known failures (whitelist).
078         *
079         * @param result the result of the XMPP Strings Testframework.
080         * @return the result of whitelist check.
081         */
082        public static Result checkAgainstWhitelist(StringsTestframework.Result result) {
083                List<ValidJidTestresult.Failed> validJidFailedTestresults = new ArrayList<>(result.validJidFailedTestresults);
084                List<InvalidJidTestresult.Failed> invalidJidFailedTestresults = new ArrayList<>(result.invalidJidFailedTestresults);
085
086                Set<XmppStringprepStringCoupling> validJidsWhitelist, invalidJidsWhitelist;
087                synchronized (VALID_JIDS_WHITELIST) {
088                        validJidsWhitelist = new HashSet<>(VALID_JIDS_WHITELIST);
089                }
090                synchronized (INVALID_JIDS_WHITELIST) {
091                        invalidJidsWhitelist = new HashSet<>(INVALID_JIDS_WHITELIST);
092                }
093
094                {
095                        Iterator<ValidJidTestresult.Failed> it = validJidFailedTestresults.iterator();
096                        while (it.hasNext()) {
097                                ValidJidTestresult.Failed failed = it.next();
098                                XmppStringprepStringCoupling coupling = new XmppStringprepStringCoupling(
099                                                failed.xmppStringPrepper.xmppStringprepClass, failed.validJid.unnormalizedJid);
100                                boolean wasWhitelisted = validJidsWhitelist.remove(coupling);
101                                if (wasWhitelisted) {
102                                        it.remove();
103                                }
104                        }
105                }
106
107                {
108                        Iterator<InvalidJidTestresult.Failed> it = invalidJidFailedTestresults.iterator();
109                        while (it.hasNext()) {
110                                InvalidJidTestresult.Failed failed = it.next();
111                                String invalidJid = failed.invalidJid.invalidJid;
112                                XmppStringprepStringCoupling coupling = new XmppStringprepStringCoupling(
113                                                failed.xmppStringPrepper.xmppStringprepClass, invalidJid);
114                                boolean wasWhitelisted = invalidJidsWhitelist.remove(coupling);
115                                if (wasWhitelisted) {
116                                        it.remove();
117                                }
118                        }
119                }
120
121                return new Result(validJidFailedTestresults, invalidJidFailedTestresults, validJidsWhitelist, invalidJidsWhitelist);
122        }
123
124        /**
125         * Whitelist the given valid JID and the used XMPP String prepper.
126         *
127         * @param xmppStringPrepper the used XMPP String prepper.
128         * @param validJid the valid JID.
129         */
130        public static void whitelistValidJid(XmppStringPrepper xmppStringPrepper, String validJid) {
131                whitelistValidJid(xmppStringPrepper.xmppStringprepClass, validJid);
132        }
133
134        /**
135         * Whitelist the given valid JID and the used XMPP String prepper.
136         *
137         * @param xmppStringprepClass the class of the used XMPP String prepper.
138         * @param validJid the valid JID.
139         */
140        public static void whitelistValidJid(Class<? extends XmppStringprep> xmppStringprepClass, String validJid) {
141                XmppStringprepStringCoupling coupling = new XmppStringprepStringCoupling(xmppStringprepClass, validJid);
142                final boolean newCoupling;
143                synchronized (VALID_JIDS_WHITELIST) {
144                        newCoupling = VALID_JIDS_WHITELIST.add(coupling);
145                }
146                if (!newCoupling) {
147                        throw new IllegalArgumentException(coupling + " is already whitelisted for valid JIDs");
148                }
149        }
150
151        /**
152         * Whitelist the given invalid JID and the used XMPP String prepper.
153         *
154         * @param xmppStringPrepper the used XMPP String prepper.
155         * @param invalidJid the invalid JID.
156         */
157        public static void whitelistInvalidJid(XmppStringPrepper xmppStringPrepper, String invalidJid) {
158                whitelistInvalidJid(xmppStringPrepper.xmppStringprepClass, invalidJid);
159        }
160
161        /**
162         * Whitelist the given invalid JID and the used XMPP String prepper.
163         *
164         * @param xmppStringprepClass the class of the used XMPP String prepper.
165         * @param invalidJid the invalid JID.
166         */
167        public static void whitelistInvalidJid(Class<? extends XmppStringprep> xmppStringprepClass, String invalidJid) {
168                XmppStringprepStringCoupling coupling = new XmppStringprepStringCoupling(xmppStringprepClass, invalidJid);
169                final boolean newCoupling;
170                synchronized (INVALID_JIDS_WHITELIST) {
171                        newCoupling = INVALID_JIDS_WHITELIST.add(coupling);
172                }
173                if (!newCoupling) {
174                        throw new IllegalArgumentException(coupling + " is already whitelisted for invalid JIDs");
175                }
176        }
177
178        public static class Result {
179                public final boolean noUnknownFailures;
180                public final List<ValidJidTestresult.Failed> remainingValidJidFailedTestresults;
181                public final List<InvalidJidTestresult.Failed> remainingInvalidJidFailedTestresults;
182
183                public final Set<XmppStringprepStringCoupling> remainingValidJidsWhitelist, remainingInvalidJidsWhitelist;
184
185                private Result(List<ValidJidTestresult.Failed> remainingValidJidFailedTestresults,
186                                List<InvalidJidTestresult.Failed> remainingInvalidJidFailedTestresults,
187                                Set<XmppStringprepStringCoupling> remainingValidJidsWhitelist,
188                                Set<XmppStringprepStringCoupling> remainingInvalidJidsWhitelist) {
189                        noUnknownFailures = remainingValidJidFailedTestresults.isEmpty()
190                                        && remainingInvalidJidFailedTestresults.isEmpty() && remainingValidJidsWhitelist.isEmpty()
191                                        && remainingInvalidJidsWhitelist.isEmpty();
192
193                        this.remainingValidJidFailedTestresults = Collections.unmodifiableList(remainingValidJidFailedTestresults);
194                        this.remainingInvalidJidFailedTestresults = Collections.unmodifiableList(remainingInvalidJidFailedTestresults);
195                        this.remainingValidJidsWhitelist = Collections.unmodifiableSet(remainingValidJidsWhitelist);
196                        this.remainingInvalidJidsWhitelist = Collections.unmodifiableSet(remainingInvalidJidsWhitelist);
197                }
198
199                @Override
200                public String toString() {
201                        StringBuilder sb = new StringBuilder();
202                        sb.append(
203                                        "XMPP Strings Testframework Known Failure Report\n" +
204                                        "===============================================\n");
205                        if (!remainingValidJidFailedTestresults.isEmpty()) {
206                                sb.append(
207                                                "Remaining Valid JID failed results:\n" +
208                                                "-----------------------------------\n"
209                                );
210                                for (ValidJidTestresult.Failed failed : remainingValidJidFailedTestresults) {
211                                        sb.append(failed).append('\n');
212                                }
213                        }
214                        if (!remainingValidJidsWhitelist.isEmpty()) {
215                                sb.append(
216                                                "Remaining Valid JID failed whitelist entries:\n" +
217                                                "---------------------------------------------\n"
218                                );
219                                for (XmppStringprepStringCoupling coupling : remainingValidJidsWhitelist) {
220                                        sb.append(coupling).append('\n');
221                                }
222                        }
223                        if (!remainingInvalidJidFailedTestresults.isEmpty()) {
224                                sb.append(
225                                                "Remaining Invalid JID failed results:\n" +
226                                                "-----------------------------------\n"
227                                );
228                                for (InvalidJidTestresult.Failed failed : remainingInvalidJidFailedTestresults) {
229                                        sb.append(failed).append('\n');
230                                }
231                        }
232                        if (!remainingInvalidJidsWhitelist.isEmpty()) {
233                                sb.append(
234                                                "Remaining Invalid JID failed whitelist entries:\n" +
235                                                "---------------------------------------------\n"
236                                );
237                                for (XmppStringprepStringCoupling coupling : remainingInvalidJidsWhitelist) {
238                                        sb.append(coupling).append('\n');
239                                }
240                        }
241                        return sb.toString();
242                }
243        }
244
245        private static class XmppStringprepStringCoupling {
246                public final Class<? extends XmppStringprep> xmppStringprepClass;
247                public final String string;
248
249                private XmppStringprepStringCoupling(Class<? extends XmppStringprep> xmppStringprepClass, String string) {
250                        this.xmppStringprepClass = xmppStringprepClass;
251                        this.string = string;
252                }
253
254                @Override
255                public int hashCode() {
256                        int result = 17;
257                        result = 31 * result + xmppStringprepClass.hashCode();
258                        result = 31 * result + string.hashCode();
259                        return result;
260                }
261
262                @Override
263                public boolean equals(Object other) {
264                        if (this == other) {
265                                return true;
266                        }
267
268                        if (!(other instanceof XmppStringprepStringCoupling)) {
269                                return false;
270                        }
271
272                        XmppStringprepStringCoupling lhs = (XmppStringprepStringCoupling) other;
273                        return xmppStringprepClass.equals(lhs.xmppStringprepClass) && string.equals(lhs.string);
274                }
275
276                @Override
277                public String toString() {
278                        return xmppStringprepClass.getName() + " '" + string + "\'";
279                }
280        }
281
282}