001/*
002 * MIT License
003 * 
004 * Copyright (c) 2016 Michael Angstadt
005 * 
006 * Permission is hereby granted, free of charge, to any person obtaining a copy
007 * of this software and associated documentation files (the "Software"), to deal
008 * in the Software without restriction, including without limitation the rights
009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010 * copies of the Software, and to permit persons to whom the Software is
011 * furnished to do so, subject to the following conditions:
012 * 
013 * The above copyright notice and this permission notice shall be included in
014 * all copies or substantial portions of the Software.
015 * 
016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
022 * SOFTWARE.
023 */
024
025package com.github.mangstadt.vinnie.validate;
026
027import java.util.EnumMap;
028import java.util.HashMap;
029import java.util.Map;
030
031import com.github.mangstadt.vinnie.SyntaxStyle;
032
033/**
034 * <p>
035 * Checks properties for illegal characters.
036 * </p>
037 * <p>
038 * Two kinds of checking are supported: strict and non-strict. Strict ensures
039 * that the data adhere to the specifications. Non-strict allows all characters
040 * to be used, as long as they do not break the syntax.
041 * </p>
042 * <p>
043 * <b>Example:</b>
044 * </p>
045 * 
046 * <pre class="brush:java">
047 * SyntaxStyle style = SyntaxStyle.NEW;
048 * String name = "NOTE #2";
049 * 
050 * boolean strict = false;
051 * assertTrue(VObjectValidator.validatePropertyName(name, style, strict));
052 * 
053 * strict = true;
054 * assertFalse(VObjectValidator.validatePropertyName(name, style, strict));
055 * </pre>
056 * @author Michael Angstadt
057 */
058public class VObjectValidator {
059        private static final Map<SyntaxStyle, Map<Boolean, AllowedCharacters>> propertyName = new EnumMap<SyntaxStyle, Map<Boolean, AllowedCharacters>>(SyntaxStyle.class);
060        static {
061                boolean strict;
062                SyntaxStyle syntax;
063
064                syntax = SyntaxStyle.OLD;
065                {
066                        Map<Boolean, AllowedCharacters> map = new HashMap<Boolean, AllowedCharacters>();
067                        strict = false;
068                        {
069                                //@formatter:off
070                                map.put(strict, new AllowedCharacters.Builder()
071                                        .allowAll()
072                                        .except("\r\n:.;")
073                                .build());
074                                //@formatter:on
075                        }
076
077                        strict = true;
078                        {
079                                //@formatter:off
080                                map.put(strict, new AllowedCharacters.Builder()
081                                        .allowPrintable()
082                                        .except("[]=:.,")
083                                
084                                        /*
085                                         * Note: The specification's formal grammar allows semicolons to
086                                         * be present in property name. This may be a mistake because
087                                         * this would break the syntax. This validator will treat
088                                         * semicolons as invalid in this context.
089                                         * 
090                                         * The specifications state that semicolons can be included in
091                                         * parameter values by escaping them with a backslash--however,
092                                         * the specification is not clear as to whether this is also
093                                         * permitted in property names.
094                                         * 
095                                         * vCard 2.1: Section 2.1.2
096                                         * vCal 1.0: Section 2, "Property" sub-heading
097                                         */
098                                        .except(';')
099                                .build());
100                                //@formatter:on
101                        }
102
103                        propertyName.put(syntax, map);
104                }
105
106                syntax = SyntaxStyle.NEW;
107                {
108                        Map<Boolean, AllowedCharacters> map = new HashMap<Boolean, AllowedCharacters>();
109                        strict = false;
110                        {
111                                //same as old style syntax
112                                map.put(strict, propertyName.get(SyntaxStyle.OLD).get(strict));
113                        }
114
115                        strict = true;
116                        {
117                                //@formatter:off
118                                map.put(strict, new AllowedCharacters.Builder()
119                                        .allow('A', 'Z')
120                                        .allow('a', 'z')
121                                        .allow('0', '9')
122                                        .allow('-')
123                                .build());
124                                //@formatter:on
125                        }
126
127                        propertyName.put(syntax, map);
128                }
129        }
130
131        private static final Map<SyntaxStyle, Map<Boolean, AllowedCharacters>> group = propertyName;
132
133        private static final Map<SyntaxStyle, Map<Boolean, AllowedCharacters>> parameterName = new EnumMap<SyntaxStyle, Map<Boolean, AllowedCharacters>>(SyntaxStyle.class);
134        static {
135                boolean strict;
136                SyntaxStyle syntax;
137
138                syntax = SyntaxStyle.OLD;
139                {
140                        Map<Boolean, AllowedCharacters> map = new HashMap<Boolean, AllowedCharacters>();
141                        strict = false;
142                        {
143                                //@formatter:off
144                                map.put(strict, new AllowedCharacters.Builder()
145                                        .allowAll()
146                                        .except("\r\n:;=")
147                                .build());
148                                //@formatter:on
149                        }
150
151                        strict = true;
152                        {
153                                //same as property name
154                                map.put(strict, propertyName.get(syntax).get(strict));
155                        }
156
157                        parameterName.put(syntax, map);
158                }
159
160                syntax = SyntaxStyle.NEW;
161                {
162                        Map<Boolean, AllowedCharacters> map = new HashMap<Boolean, AllowedCharacters>();
163                        strict = false;
164                        {
165                                //same as old style syntax
166                                map.put(strict, parameterName.get(SyntaxStyle.OLD).get(strict));
167                        }
168
169                        strict = true;
170                        {
171                                //same as property name
172                                map.put(strict, propertyName.get(syntax).get(strict));
173                        }
174
175                        parameterName.put(syntax, map);
176                }
177        }
178
179        private static final Map<SyntaxStyle, Map<Boolean, Map<Boolean, AllowedCharacters>>> parameterValue = new EnumMap<SyntaxStyle, Map<Boolean, Map<Boolean, AllowedCharacters>>>(SyntaxStyle.class);
180        static {
181                boolean strict, caretEncoding;
182                SyntaxStyle syntax;
183
184                syntax = SyntaxStyle.OLD;
185                {
186                        Map<Boolean, Map<Boolean, AllowedCharacters>> map = new HashMap<Boolean, Map<Boolean, AllowedCharacters>>();
187                        caretEncoding = false;
188                        {
189                                Map<Boolean, AllowedCharacters> map2 = new HashMap<Boolean, AllowedCharacters>();
190                                strict = false;
191                                {
192                                        //@formatter:off
193                                        map2.put(strict, new AllowedCharacters.Builder()
194                                                .allowAll()
195                                                .except("\r\n:")
196                                        .build());
197                                        //@formatter:on
198                                }
199
200                                strict = true;
201                                {
202                                        //same as parameter name, except semicolons are allowed
203                                        //@formatter:off
204                                        AllowedCharacters paramName = parameterName.get(syntax).get(strict);
205                                        map2.put(strict, new AllowedCharacters.Builder(paramName)
206                                                .allow(';')
207                                        .build());
208                                        //@formatter::on
209                                }
210                                map.put(caretEncoding, map2);
211                        }
212
213                        caretEncoding = true;
214                        {
215                                /*
216                                 * Same as when caret encoding is disabled because
217                                 * old style syntax does not support caret encoding.
218                                 */
219                                map.put(caretEncoding, map.get(false));
220                        }
221
222                        parameterValue.put(syntax, map);
223                }
224
225                syntax = SyntaxStyle.NEW;
226                {
227                        Map<Boolean, Map<Boolean, AllowedCharacters>> map = new HashMap<Boolean, Map<Boolean, AllowedCharacters>>();
228                        caretEncoding = false;
229                        {
230                                Map<Boolean, AllowedCharacters> map2 = new HashMap<Boolean, AllowedCharacters>();
231                                strict = false;
232                                {
233                                        //@formatter:off
234                                        map2.put(strict, new AllowedCharacters.Builder()
235                                                .allowAll()
236                                                .except("\r\n\"")
237                                        .build());
238                                        //@formatter:on
239                                }
240
241                                strict = true;
242                                {
243                                        //@formatter:off
244                                        map2.put(strict, new AllowedCharacters.Builder()
245                                                .allowPrintable()
246                                                .allowNonAscii()
247                                                .allow('\t')
248                                                .except('"')
249                                        .build());
250                                        //@formatter:on
251                                }
252
253                                map.put(caretEncoding, map2);
254                        }
255
256                        caretEncoding = true;
257                        {
258                                Map<Boolean, AllowedCharacters> map2 = new HashMap<Boolean, AllowedCharacters>();
259                                strict = false;
260                                {
261                                        //@formatter:off
262                                        map2.put(strict, new AllowedCharacters.Builder()
263                                                .allowAll()
264                                        .build());
265                                        //@formatter:on
266                                }
267
268                                strict = true;
269                                {
270                                        //@formatter:off
271                                        map2.put(strict, new AllowedCharacters.Builder()
272                                                .allowPrintable()
273                                                .allowNonAscii()
274                                                .allow("\r\n\t")
275                                        .build());
276                                        //@formatter:on
277                                }
278
279                                map.put(caretEncoding, map2);
280                        }
281
282                        parameterValue.put(syntax, map);
283                }
284        }
285
286        /**
287         * Validates a property name.
288         * @param name the property name
289         * @param syntax the syntax style to validate against
290         * @param strict false to allow all characters as long as they don't break
291         * the syntax, true for spec-compliant validation
292         * @return true if the property name is valid, false if not
293         */
294        public static boolean validatePropertyName(String name, SyntaxStyle syntax, boolean strict) {
295                return allowedCharactersPropertyName(syntax, strict).check(name);
296        }
297
298        /**
299         * Gets the list of allowed characters for property names.
300         * @param syntax the syntax style
301         * @param strict false for the non-strict list, true for the spec-compliant
302         * list
303         * @return the character list
304         */
305        public static AllowedCharacters allowedCharactersPropertyName(SyntaxStyle syntax, boolean strict) {
306                return propertyName.get(syntax).get(strict);
307        }
308
309        /**
310         * Validates a group name.
311         * @param group the group name
312         * @param syntax the syntax style to validate against
313         * @param strict false to allow all characters as long as they don't break
314         * the syntax, true for spec-compliant validation
315         * @return true if the group name is valid, false if not
316         */
317        public static boolean validateGroupName(String group, SyntaxStyle syntax, boolean strict) {
318                return allowedCharactersGroup(syntax, strict).check(group);
319        }
320
321        /**
322         * Gets the list of allowed characters for group names.
323         * @param syntax the syntax style
324         * @param strict false for the non-strict list, true for the spec-compliant
325         * list
326         * @return the character list
327         */
328        public static AllowedCharacters allowedCharactersGroup(SyntaxStyle syntax, boolean strict) {
329                return group.get(syntax).get(strict);
330        }
331
332        /**
333         * Validates a parameter name.
334         * @param name the parameter name
335         * @param syntax the syntax style to validate against
336         * @param strict false to allow all characters as long as they don't break
337         * the syntax, true for spec-compliant validation
338         * @return true if the parameter name is valid, false if not
339         */
340        public static boolean validateParameterName(String name, SyntaxStyle syntax, boolean strict) {
341                return allowedCharactersParameterName(syntax, strict).check(name);
342        }
343
344        /**
345         * Gets the list of allowed characters for parameter names.
346         * @param syntax the syntax style
347         * @param strict false for the non-strict list, true for the spec-compliant
348         * list
349         * @return the character list
350         */
351        public static AllowedCharacters allowedCharactersParameterName(SyntaxStyle syntax, boolean strict) {
352                return parameterName.get(syntax).get(strict);
353        }
354
355        /**
356         * Validates a parameter value.
357         * @param value the parameter value
358         * @param syntax the syntax style to validate against
359         * @param caretEncoding true if caret encoding is enabled, false if not
360         * @param strict false to allow all characters as long as they don't break
361         * the syntax, true for spec-compliant validation
362         * @return true if the parameter value is valid, false if not
363         */
364        public static boolean validateParameterValue(String value, SyntaxStyle syntax, boolean caretEncoding, boolean strict) {
365                return allowedCharactersParameterValue(syntax, caretEncoding, strict).check(value);
366        }
367
368        /**
369         * Gets the list of allowed characters for parameter values.
370         * @param syntax the syntax style
371         * @param caretEncoding true if caret encoding is enabled, false if not
372         * @param strict false for the non-strict list, true for the spec-compliant
373         * list
374         * @return the character list
375         */
376        public static AllowedCharacters allowedCharactersParameterValue(SyntaxStyle syntax, boolean caretEncoding, boolean strict) {
377                return parameterValue.get(syntax).get(caretEncoding).get(strict);
378        }
379
380        private VObjectValidator() {
381                //hide
382        }
383}