001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  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.apache.wicket.csp;
018
019import java.net.URI;
020import java.net.URISyntaxException;
021import java.util.List;
022
023import org.apache.wicket.util.string.Strings;
024
025/**
026 * An enum holding the possible CSP Directives. Via the
027 * {@link #checkValueForDirective(CSPRenderable, List)}-method, new values are verified before being
028 * added to the list of values for a directive.
029 *
030 * @see <a href="https://www.w3.org/TR/CSP2/">https://www.w3.org/TR/CSP2</a>
031 * @see <a href=
032 *      "https://developer.mozilla.org/en-US/docs/Web/Security/CSP">https://developer.mozilla.org/en-US/docs/Web/Security/CSP</a>
033 */
034public enum CSPDirective
035{
036        DEFAULT_SRC("default-src"),
037        SCRIPT_SRC("script-src"),
038        STYLE_SRC("style-src"),
039        IMG_SRC("img-src"),
040        CONNECT_SRC("connect-src"),
041        FONT_SRC("font-src"),
042        OBJECT_SRC("object-src"),
043        MANIFEST_SRC("manifest-src"),
044        MEDIA_SRC("media-src"),
045        CHILD_SRC("child-src"),
046        WORKER_SRC("worker-src"),
047        FRAME_ANCESTORS("frame-ancestors"),
048        BASE_URI("base-uri"),
049        /**
050         * This directive was deprecated in CSP 2, but no longer in 3. Wicket will automatically add a
051         * {@code frame-src} directive when {@code child-src} is added.
052         */
053        FRAME_SRC("frame-src"),
054        FORM_ACTION("form-action"),
055        SANDBOX("sandbox")
056        {
057                /**
058                 * Only allow {@link CSPDirectiveSandboxValue} for the {@code 'sandbox'} directive and block
059                 * conflicting options.
060                 */
061                @Override
062                public void checkValueForDirective(CSPRenderable value,
063                                List<CSPRenderable> existingDirectiveValues)
064                {
065                        if (!existingDirectiveValues.isEmpty())
066                        {
067                                if (CSPDirectiveSandboxValue.EMPTY.equals(value))
068                                {
069                                        throw new IllegalArgumentException(
070                                                "A sandbox directive can't contain an empty string if it already contains "
071                                                        + "other values: " + existingDirectiveValues);
072                                }
073                                if (existingDirectiveValues.contains(CSPDirectiveSandboxValue.EMPTY))
074                                {
075                                        throw new IllegalArgumentException(
076                                                "A sandbox directive can't contain other values if it already contains an "
077                                                        + "empty string, can't add " + value);
078                                }
079                        }
080
081                        if (!(value instanceof CSPDirectiveSandboxValue))
082                        {
083                                throw new IllegalArgumentException(
084                                        "A sandbox directive can only contain values from CSPDirectiveSandboxValue or "
085                                                + "be empty, can't add " + value);
086                        }
087                }
088        },
089        REPORT_URI("report-uri")
090        {
091                /**
092                 * Only allow URI, and only one.
093                 */
094                @Override
095                public void checkValueForDirective(CSPRenderable value,
096                                List<CSPRenderable> existingDirectiveValues)
097                {
098                        if (!existingDirectiveValues.isEmpty())
099                        {
100                                throw new IllegalArgumentException(
101                                        "A report-uri directive can only contain one URI, it already contains "
102                                                + existingDirectiveValues);
103                        }
104                        if (value instanceof RelativeURICSPValue)
105                        {
106                                return;
107                        }
108                        if (!(value instanceof FixedCSPValue))
109                        {
110                                throw new IllegalArgumentException(
111                                        "A report-uri directive can only contain an URI, not " + value);
112                        }
113                        try
114                        {
115                                new URI(value.toString());
116                        }
117                        catch (URISyntaxException urise)
118                        {
119                                throw new IllegalArgumentException("Illegal URI for report-uri directive", urise);
120                        }
121                }
122        };
123
124        private String value;
125
126        CSPDirective(String value)
127        {
128                this.value = value;
129        }
130
131        public String getValue()
132        {
133                return value;
134        }
135
136        /**
137         * Check if {@code value} can be added to the list of other values. By default, it checks for
138         * conflicts with wildcards and none and it checks if values are valid uris.
139         *
140         * @param value
141         *            The value to add.
142         * @param existingDirectiveValues
143         *            The other values.
144         * @throws IllegalArgumentException
145         *             if the given value is invalid.
146         */
147        public void checkValueForDirective(CSPRenderable value,
148                        List<CSPRenderable> existingDirectiveValues)
149        {
150                if (!existingDirectiveValues.isEmpty())
151                {
152                        if (CSPDirectiveSrcValue.WILDCARD.equals(value)
153                                || CSPDirectiveSrcValue.NONE.equals(value))
154                        {
155                                throw new IllegalArgumentException(
156                                        "A -src directive can't contain an * or a 'none' if it already contains other "
157                                                + "values: " + existingDirectiveValues);
158                        }
159                        if (existingDirectiveValues.contains(CSPDirectiveSrcValue.WILDCARD)
160                                || existingDirectiveValues.contains(CSPDirectiveSrcValue.NONE))
161                        {
162                                throw new IllegalArgumentException(
163                                        "A -src directive can't contain other values if it already contains an * or "
164                                                + "a 'none', can't add " + value);
165                        }
166                }
167
168                if (value instanceof CSPDirectiveSandboxValue)
169                {
170                        throw new IllegalArgumentException(
171                                "A -src directive can't contain any of the sandbox directive values, like "
172                                                + value);
173                }
174
175                value.checkValidityForSrc();
176        }
177
178        /**
179         * @return The CSPDirective constant whose value-parameter equals the input-parameter or
180         *         {@code null} if none can be found.
181         */
182        public static CSPDirective fromValue(String value)
183        {
184                if (Strings.isEmpty(value))
185                {
186                        return null;
187                }
188                for (int i = 0; i < values().length; i++)
189                {
190                        if (value.equals(values()[i].getValue()))
191                        {
192                                return values()[i];
193                        }
194                }
195                return null;
196        }
197}