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.markup.html;
018
019import java.io.File;
020import java.util.HashSet;
021import java.util.Locale;
022import java.util.Set;
023
024import org.apache.wicket.Application;
025import org.apache.wicket.util.string.Strings;
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029
030/**
031 * Default implementation of {@link IPackageResourceGuard}. By default, the extensions 'properties',
032 * 'class' and 'java' are blocked and also files like 'log4j.xml' and 'applicationContext.xml'
033 * 
034 * A more secure implementation which by default denies access to any resource is
035 * {@link SecurePackageResourceGuard}
036 * 
037 * @author eelcohillenius
038 */
039public class PackageResourceGuard implements IPackageResourceGuard
040{
041        /** Log. */
042        private static final Logger log = LoggerFactory.getLogger(PackageResourceGuard.class);
043
044        /** Set of extensions that are denied access. */
045        private Set<String> blockedExtensions = new HashSet<>(4);
046
047        /** Set of filenames that are denied access. */
048        private Set<String> blockedFiles = new HashSet<>(4);
049
050        private boolean allowAccessToRootResources = false;
051
052        /**
053         * Construct.
054         */
055        public PackageResourceGuard()
056        {
057                blockedExtensions.add("properties");
058                blockedExtensions.add("class");
059                blockedExtensions.add("java");
060
061                blockedFiles.add("applicationContext.xml");
062                blockedFiles.add("log4j.xml");
063        }
064
065        /**
066         * @see org.apache.wicket.markup.html.IPackageResourceGuard#accept(java.lang.String)
067         */
068        @Override
069        public boolean accept(String path)
070        {
071                int ixExtension = path.lastIndexOf('.');
072                int len = path.length();
073                final String ext;
074                if (ixExtension <= 0 || ixExtension == len ||
075                        (path.lastIndexOf('/') + 1) == ixExtension ||
076                        (path.lastIndexOf('\\') + 1) == ixExtension)
077                {
078                        ext = null;
079                }
080                else
081                {
082                        ext = path.substring(ixExtension + 1).toLowerCase(Locale.ROOT).trim();
083                }
084
085                if ("html".equals(ext))
086                {
087                        String prefix = path.substring(0, ixExtension);
088
089                        ClassLoader classLoader = getClass().getClassLoader();
090                        while (true)
091                        {
092                                if (classLoader.getResource(prefix + ".class") != null)
093                                {
094                                        log.warn("Access denied to shared (static) resource because it is a Wicket markup file: " +
095                                                path);
096                                        return false;
097                                }
098
099                                int ixUnderscore = prefix.lastIndexOf('_');
100                                if (ixUnderscore == -1)
101                                {
102                                        break;
103                                }
104
105                                prefix = prefix.substring(0, ixUnderscore);
106                        }
107                }
108
109                if (acceptExtension(ext) == false)
110                {
111                        log.warn("Access denied to shared (static) resource because of the file extension: " +
112                                path);
113                        return false;
114                }
115
116                String filename = Strings.lastPathComponent(path, File.separatorChar);
117                if (acceptFile(filename) == false)
118                {
119                        log.warn("Access denied to shared (static) resource because of the file name: " + path);
120                        return false;
121                }
122
123                // Only if a placeholder, e.g. $up$ is defined, access to parent directories is allowed
124                if (Strings.isEmpty(Application.get().getResourceSettings().getParentFolderPlaceholder()))
125                {
126                        if (path.contains(".."))
127                        {
128                                log.warn("Access to parent directories via '..' is by default disabled for shared resources: " +
129                                        path);
130                                return false;
131                        }
132                }
133
134                //
135                // for windows we have to check both File.separator ('\') and the usual '/' since both can
136                // be used and are used interchangeably
137                //
138
139                if (!allowAccessToRootResources)
140                {
141                        String absolute = path;
142                        if ("\\".equals(File.separator))
143                        {
144                                // handle a windows path which may have a drive letter in it
145
146                                int drive = absolute.indexOf(":\\");
147                                if (drive < 0)
148                                {
149                                        drive = absolute.indexOf(":/");
150                                }
151                                if (drive > 0)
152                                {
153                                        // strip the drive letter off the path
154                                        absolute = absolute.substring(drive + 2);
155                                }
156                        }
157
158                        if (absolute.startsWith(File.separator) || absolute.startsWith("/"))
159                        {
160                                absolute = absolute.substring(1);
161                        }
162                        if (!absolute.contains(File.separator) && !absolute.contains("/"))
163                        {
164                                log.warn("Access to root directory is by default disabled for shared resources: " +
165                                        path);
166                                return false;
167                        }
168                }
169
170                return true;
171        }
172
173        /**
174         * Whether the provided extension is accepted.
175         * 
176         * @param extension
177         *            The extension, starting from the class root (packages are separated with forward
178         *            slashes instead of dots).
179         * @return True if accepted, false otherwise.
180         */
181        protected boolean acceptExtension(String extension)
182        {
183                return (!blockedExtensions.contains(extension));
184        }
185
186        /**
187         * Whether the provided filename is accepted.
188         * 
189         * @param file
190         *            filename
191         * @return True if accepted, false otherwise.
192         */
193        protected boolean acceptFile(String file)
194        {
195                if (file != null)
196                {
197                        file = file.trim();
198                }
199                return (!blockedFiles.contains(file));
200        }
201
202        /**
203         * Gets the set of extensions that are denied access.
204         * 
205         * @return The set of extensions that are denied access
206         */
207        protected final Set<String> getBlockedExtensions()
208        {
209                return blockedExtensions;
210        }
211
212        /**
213         * Gets the set of extensions that are denied access.
214         * 
215         * @return The set of extensions that are denied access
216         */
217        protected final Set<String> getBlockedFiles()
218        {
219                return blockedFiles;
220        }
221
222        /**
223         * Sets the set of extensions that are denied access.
224         * 
225         * @param blockedExtensions
226         *            Set of extensions that are denied access
227         */
228        protected final void setBlockedExtensions(Set<String> blockedExtensions)
229        {
230                this.blockedExtensions = blockedExtensions;
231        }
232
233        /**
234         * Sets the set of filenames that are denied access.
235         * 
236         * @param blockedFiles
237         *            Set of extensions that are denied access
238         */
239        protected final void setBlockedFiles(Set<String> blockedFiles)
240        {
241                this.blockedFiles = blockedFiles;
242        }
243
244        /**
245         * Checks whether or not resources in the web root folder can be access.
246         * 
247         * @return {@code true} iff root resources can be accessed
248         */
249        public final boolean isAllowAccessToRootResources()
250        {
251                return allowAccessToRootResources;
252        }
253
254        /**
255         * Sets whether or not resources in the web root folder can be accessed.
256         * 
257         * @param allowAccessToRootResources
258         */
259        public final void setAllowAccessToRootResources(boolean allowAccessToRootResources)
260        {
261                this.allowAccessToRootResources = allowAccessToRootResources;
262        }
263}