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}