View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.configuration2.io;
19  
20  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
21  import static org.junit.jupiter.api.Assertions.assertThrows;
22  
23  import java.net.URL;
24  import java.util.Arrays;
25  import java.util.LinkedHashSet;
26  import java.util.Set;
27  import java.util.regex.Pattern;
28  import java.util.stream.Stream;
29  
30  import org.apache.commons.configuration2.ex.ConfigurationDeniedException;
31  import org.junit.jupiter.api.Test;
32  import org.junit.jupiter.params.ParameterizedTest;
33  import org.junit.jupiter.params.provider.Arguments;
34  import org.junit.jupiter.params.provider.MethodSource;
35  
36  /**
37   * Tests {@link AbstractFileLocationStrategy}.
38   */
39  public class TestAbstractFileLocationStrategy {
40  
41      private static Set<Pattern> hosts(final String... regexes) {
42          final LinkedHashSet<Pattern> set = new LinkedHashSet<>();
43          for (final String r : regexes) {
44              set.add(Pattern.compile(r, Pattern.CASE_INSENSITIVE));
45          }
46          return set;
47      }
48  
49      // Bypasses the validation of the single-arg constructor
50      private static URL jarUrl(final String spec) throws Exception {
51          return new URL("jar", null, spec);
52      }
53  
54      private static Set<String> schemes(final String... values) {
55          return new LinkedHashSet<>(Arrays.asList(values));
56      }
57  
58      static Stream<Arguments> testCheckUrlAccepts() throws Exception {
59          return Stream.of(
60                  // Empty scheme allows all.
61                  Arguments.of(url("file:/tmp/x.properties"), schemes(), hosts()),
62                  Arguments.of(url("https://example.com/x.properties"), schemes(), hosts()),
63                  // Bare schemes that match the allow-set.
64                  Arguments.of(url("file:/tmp/x.properties"), schemes("file"), hosts()),
65                  Arguments.of(url("https://example.com/x.properties"), schemes("https"), hosts()),
66                  // jar: unwraps to the inner scheme, which is in the allow-set.
67                  Arguments.of(url("jar:file:/tmp/x.jar!/y.properties"), schemes("file", "jar"), hosts()),
68                  Arguments.of(url("jar:https://example.com/x.jar!/y.properties"), schemes("https", "jar"), hosts()),
69                  // Empty host allow-set means "any host".
70                  Arguments.of(url("file:///tmp/x.properties"), schemes("file"), hosts()),
71                  Arguments.of(url("http://anything.example/x.properties"), schemes("http"), hosts()),
72                  Arguments.of(url("jar:https://anything.example/x.jar!/y.properties"), schemes("https", "jar"), hosts()),
73                  // Host satisfies allow-set
74                  Arguments.of(url("file:///tmp/x.properties"), schemes("file"), hosts("trusted\\.example")),
75                  Arguments.of(url("https://trusted.example/x.properties"), schemes("https", "jar"), hosts("trusted\\.example")),
76                  Arguments.of(url("jar:https://trusted.example/x.jar!/y.properties"), schemes("https", "jar"), hosts("trusted\\.example"))
77          );
78      }
79  
80      static Stream<Arguments> testCheckUrlRejects() throws Exception {
81          return Stream.of(
82                  // Plain scheme not in the allow-set.
83                  Arguments.of(url("http://example.com/x.properties"), schemes("file", "jar"), hosts()),
84                  // jar: is allowed but the inner scheme is not.
85                  Arguments.of(url("jar:file:/tmp/x.jar!/y.properties"), schemes("jar"), hosts()),
86                  Arguments.of(url("jar:https://example.com/x.jar!/y.properties"), schemes("jar"), hosts()),
87                  // Invalid jar URL
88                  Arguments.of(jarUrl("file:/tmp/x.properties"), schemes("file", "jar"), hosts()),
89                  Arguments.of(jarUrl("invalid url!/y.properties"), schemes("file", "jar"), hosts()),
90                  // Host is not allowed
91                  Arguments.of(url("https://evilhost/x.properties"), schemes(), hosts("trusted\\.example")),
92                  Arguments.of(url("jar:https://evilhost/x.jar!/y.properties"), schemes(), hosts("trusted\\.example"))
93          );
94      }
95  
96      private static URL url(final String spec) throws Exception {
97          return new URL(spec);
98      }
99  
100     @Test
101     void testBuilder() {
102         assertThrows(NullPointerException.class, () -> new AbstractFileLocationStrategy.StrategyBuilder<>(null));
103     }
104 
105     @ParameterizedTest
106     @MethodSource
107     void testCheckUrlAccepts(final URL url, final Set<String> validSchemes, final Set<Pattern> validHosts) {
108         assertDoesNotThrow(() -> AbstractFileLocationStrategy.checkUrl(url, validSchemes, validHosts));
109     }
110 
111     @ParameterizedTest
112     @MethodSource
113     void testCheckUrlRejects(final URL url, final Set<String> validSchemes, final Set<Pattern> validHosts) {
114         assertThrows(ConfigurationDeniedException.class, () -> AbstractFileLocationStrategy.checkUrl(url, validSchemes, validHosts));
115     }
116 
117 }