Document and validate web-api constructs
[aaa.git] / web / api / src / main / java / org / opendaylight / aaa / web / ServletSpec.java
1 /*
2  * Copyright (c) 2022 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.aaa.web;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11
12 import org.eclipse.jdt.annotation.NonNull;
13
14 /**
15  * Utility methods for dealing with aspects of Java Servlet Specification. We currently support
16  * <a href="https://github.com/javaee/servlet-spec/blob/gh-pages/downloads/servlet-3.1/Final/servlet-3_1-final.pdf">
17  * version 3.1</a>.
18  */
19 final class ServletSpec {
20     private ServletSpec() {
21         // utility class
22     }
23
24     /**
25      * Verify that the specified string is a valid Context Path as defined in Section 3.5.
26      *
27      * @param str String to check
28      * @return The string
29      * @throws IllegalArgumentException if {@code str} is not a valid context path
30      * @throws NullPointerException if {@code str} is {@code null}
31      */
32     static @NonNull String requireContextPath(final String str) {
33         // We do not allow this:
34         //   If this context is the “default” context rooted at the base of the
35         //   Web server’s URL name space, this path will be an empty string.
36         checkArgument(!str.isEmpty(), "Context path is empty");
37
38         // Otherwise, if the
39         // context is not rooted at the root of the server’s name space, the path starts with a
40         // character but does not end with a / character.
41         checkArgument(str.charAt(0) == '/', "Context path '%s' does not start with '/'", str);
42         checkArgument(str.charAt(str.length() - 1) != '/', "Context path '%s' ends with '/'", str);
43
44         // TODO: validate according to https://www.rfc-editor.org/rfc/rfc3986#section-3.3
45
46         return str;
47     }
48
49     /**
50      * Verify that the specified string is a valid Specification of Mapping as defined in Section 12.2.
51      *
52      * @param str String to check
53      * @return The string
54      * @throws IllegalArgumentException if {@code str} is not a valid mapping specification
55      * @throws NullPointerException if {@code str} is {@code null}
56      */
57     static @NonNull String requireMappingSpec(final String str) {
58         // Bullet 3:
59         //      The empty string ("") is a special URL pattern that exactly maps to the
60         //      application's context root, i.e., requests of the form http://host:port/<context-
61         //      root>/. In this case the path info is ’ / ’ and the servlet path and context path is
62         //      empty string (““).
63         if (str.isEmpty()) {
64             return "";
65         }
66
67         final char firstChar = str.charAt(0);
68         final int len = str.length();
69         if (firstChar == '/') {
70             // Bullet 4:
71             //      A string containing only the ’ / ’ character indicates the "default" servlet of the
72             //      application. In this case the servlet path is the request URI minus the context path
73             //      and the path info is null.
74             // otherwise ...
75             if (len != 1) {
76                 // ... more checks starting at the second character
77                 final int star = str.indexOf('*', 1);
78                 checkArgument(
79                     // Bullet 5:
80                     //      All other strings are used for exact matches only.
81                     star == -1
82                     // or Bullet 1:
83                     //      A string beginning with a ‘ / ’ character and ending with a ‘ /*’ suffix is used for
84                     //      path mapping.
85                     || star == len - 1 && str.charAt(star - 1) == '/',
86                     // ... otherwise it is a '*' in an exact path
87                     "Prefix-based spec '%s' with a '*' at offset %s", str, star);
88             }
89         } else {
90             // Bullet 2:
91             //      A string beginning with a ‘ *. ’ prefix is used as an extension mapping
92             checkArgument(firstChar == '.' && len > 1 && str.charAt(1) == '*',
93                 "Spec '%s' is neither prefix-based nor suffix-based", str);
94
95             final int slash = str.indexOf('/', 2);
96             checkArgument(slash == -1, "Suffix-based spec '%s' with a '/' at offset %s", str, slash);
97             final int star = str.indexOf('*', 2);
98             checkArgument(star == -1, "Suffix-based spec '%s' with a '*' at offset %s", str, star);
99         }
100
101         return str;
102     }
103 }