001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019package org.apache.wiki.attachment; 020 021import org.apache.commons.lang3.StringUtils; 022import org.apache.log4j.Logger; 023import org.apache.wiki.api.core.Attachment; 024import org.apache.wiki.api.core.Context; 025import org.apache.wiki.api.core.Page; 026import org.apache.wiki.api.exceptions.ProviderException; 027import org.apache.wiki.api.exceptions.WikiException; 028import org.apache.wiki.api.providers.AttachmentProvider; 029import org.apache.wiki.api.providers.WikiProvider; 030 031import java.io.File; 032import java.io.FileInputStream; 033import java.io.IOException; 034import java.io.InputStream; 035import java.util.Collection; 036import java.util.List; 037 038 039/** 040 * Provides facilities for handling attachments. All attachment handling goes through this class. 041 * <p> 042 * The AttachmentManager provides a facade towards the current WikiAttachmentProvider that is in use. 043 * It is created by the Engine as a singleton object, and can be requested through the Engine. 044 * 045 * @since 1.9.28 046 */ 047public interface AttachmentManager { 048 049 /** The property name for defining the attachment provider class name. */ 050 String PROP_PROVIDER = "jspwiki.attachmentProvider"; 051 052 /** The maximum size of attachments that can be uploaded. */ 053 String PROP_MAXSIZE = "jspwiki.attachment.maxsize"; 054 055 /** A space-separated list of attachment types which can be uploaded */ 056 String PROP_ALLOWEDEXTENSIONS = "jspwiki.attachment.allowed"; 057 058 /** A space-separated list of attachment types which cannot be uploaded */ 059 String PROP_FORBIDDENEXTENSIONS = "jspwiki.attachment.forbidden"; 060 061 /** A space-separated list of attachment types which never will open in the browser. */ 062 String PROP_FORCEDOWNLOAD = "jspwiki.attachment.forceDownload"; 063 064 /** Name of the page cache. */ 065 String CACHE_NAME = "jspwiki.dynamicAttachmentCache"; 066 067 /** The capacity of the cache, if you want something else, tweak ehcache.xml. */ 068 int DEFAULT_CACHECAPACITY = 1_000; 069 070 /** 071 * Returns true, if attachments are enabled and running. 072 * 073 * @return A boolean value indicating whether attachment functionality is enabled. 074 */ 075 boolean attachmentsEnabled(); 076 077 /** 078 * Gets info on a particular attachment, latest version. 079 * 080 * @param name A full attachment name. 081 * @return Attachment, or null, if no such attachment exists. 082 * @throws ProviderException If something goes wrong. 083 */ 084 default Attachment getAttachmentInfo( final String name ) throws ProviderException { 085 return getAttachmentInfo( name, WikiProvider.LATEST_VERSION ); 086 } 087 088 /** 089 * Gets info on a particular attachment with the given version. 090 * 091 * @param name A full attachment name. 092 * @param version A version number. 093 * @return Attachment, or null, if no such attachment or version exists. 094 * @throws ProviderException If something goes wrong. 095 */ 096 097 default Attachment getAttachmentInfo( final String name, final int version ) throws ProviderException { 098 if( name == null ) { 099 return null; 100 } 101 102 return getAttachmentInfo( null, name, version ); 103 } 104 105 /** 106 * Figures out the full attachment name from the context and attachment name. 107 * 108 * @param context The current WikiContext 109 * @param attachmentname The file name of the attachment. 110 * @return Attachment, or null, if no such attachment exists. 111 * @throws ProviderException If something goes wrong. 112 */ 113 default Attachment getAttachmentInfo( final Context context, final String attachmentname ) throws ProviderException { 114 return getAttachmentInfo( context, attachmentname, WikiProvider.LATEST_VERSION ); 115 } 116 117 /** 118 * Figures out the full attachment name from the context and attachment name. 119 * 120 * @param context The current WikiContext 121 * @param attachmentname The file name of the attachment. 122 * @param version A particular version. 123 * @return Attachment, or null, if no such attachment or version exists. 124 * @throws ProviderException If something goes wrong. 125 */ 126 Attachment getAttachmentInfo( Context context, String attachmentname, int version ) throws ProviderException; 127 128 /** 129 * Figures out the full attachment name from the context and attachment name. 130 * 131 * @param context The current WikiContext 132 * @param attachmentname The file name of the attachment. 133 * @return Attachment, or null, if no such attachment exists. 134 */ 135 String getAttachmentInfoName( Context context, String attachmentname ); 136 137 /** 138 * Returns the list of attachments associated with a given wiki page. If there are no attachments, returns an empty Collection. 139 * 140 * @param wikipage The wiki page from which you are seeking attachments for. 141 * @return a valid collection of attachments. 142 * @throws ProviderException If there was something wrong in the backend. 143 */ 144 List< Attachment > listAttachments( Page wikipage ) throws ProviderException; 145 146 /** 147 * Returns true, if the page has any attachments at all. This is a convenience method. 148 * 149 * @param wikipage The wiki page from which you are seeking attachments for. 150 * @return True, if the page has attachments, else false. 151 */ 152 default boolean hasAttachments( final Page wikipage ) { 153 try { 154 return listAttachments( wikipage ).size() > 0; 155 } catch( final Exception e ) { 156 Logger.getLogger( AttachmentManager.class ).info( e.getMessage(), e ); 157 } 158 159 return false; 160 } 161 162 /** 163 * Check if attachement link should force a download iso opening the attachment in the browser. 164 * 165 * @param name Name of attachment to be checked 166 * @return true, if the attachment should be downloaded when clicking the link 167 * @since 2.11.0 M4 168 */ 169 boolean forceDownload( String name ); 170 171 /** 172 * Finds a (real) attachment from the repository as an {@link InputStream}. 173 * 174 * @param att Attachment 175 * @return An InputStream to read from. May return null, if attachments are disabled. 176 * @throws IOException If the stream cannot be opened 177 * @throws ProviderException If the backend fails due to some other reason. 178 */ 179 default InputStream getAttachmentStream( final Attachment att ) throws IOException, ProviderException { 180 return getAttachmentStream( null, att ); 181 } 182 183 /** 184 * Returns an attachment stream using the particular WikiContext. This method should be used instead of 185 * {@link #getAttachmentStream(Attachment)}, since it also allows the DynamicAttachments to function. 186 * 187 * @param ctx The Wiki Context 188 * @param att The Attachment to find 189 * @return An InputStream. May return null, if attachments are disabled. You must take care of closing it. 190 * @throws ProviderException If the backend fails due to some reason 191 * @throws IOException If the stream cannot be opened 192 */ 193 InputStream getAttachmentStream( Context ctx, Attachment att ) throws ProviderException, IOException; 194 195 /** 196 * Stores a dynamic attachment. Unlike storeAttachment(), this just stores the attachment in the memory. 197 * 198 * @param ctx A WikiContext 199 * @param att An attachment to store 200 */ 201 void storeDynamicAttachment( Context ctx, DynamicAttachment att ); 202 203 /** 204 * Finds a DynamicAttachment. Normally, you should just use {@link #getAttachmentInfo(String)} , since that will find also 205 * {@link DynamicAttachment}s. 206 * 207 * @param name The name of the attachment to look for 208 * @return An Attachment, or null. 209 * @see #getAttachmentInfo(String) 210 */ 211 DynamicAttachment getDynamicAttachment( String name ); 212 213 /** 214 * Stores an attachment that lives in the given file. If the attachment did not exist previously, this method will create it. 215 * If it did exist, it stores a new version. 216 * 217 * @param att Attachment to store this under. 218 * @param source A file to read from. 219 * 220 * @throws IOException If writing the attachment failed. 221 * @throws ProviderException If something else went wrong. 222 */ 223 default void storeAttachment( final Attachment att, final File source ) throws IOException, ProviderException { 224 try( final FileInputStream in = new FileInputStream( source ) ) { 225 storeAttachment( att, in ); 226 } 227 } 228 229 /** 230 * Stores an attachment directly from a stream. If the attachment did not exist previously, this method will create it. 231 * If it did exist, it stores a new version. 232 * 233 * @param att Attachment to store this under. 234 * @param in InputStream from which the attachment contents will be read. 235 * @throws IOException If writing the attachment failed. 236 * @throws ProviderException If something else went wrong. 237 */ 238 void storeAttachment( Attachment att, InputStream in ) throws IOException, ProviderException; 239 240 /** 241 * Returns a list of versions of the attachment. 242 * 243 * @param attachmentName A fully qualified name of the attachment. 244 * 245 * @return A list of Attachments. May return null, if attachments are 246 * disabled. 247 * @throws ProviderException If the provider fails for some reason. 248 */ 249 List< Attachment > getVersionHistory( String attachmentName ) throws ProviderException; 250 251 /** 252 * Returns a collection of Attachments, containing each and every attachment that is in this Wiki. 253 * 254 * @return A collection of attachments. If attachments are disabled, will return an empty collection. 255 * @throws ProviderException If something went wrong with the backend 256 */ 257 Collection< Attachment > getAllAttachments() throws ProviderException; 258 259 /** 260 * Returns the current attachment provider. 261 * 262 * @return The current provider. May be null, if attachments are disabled. 263 */ 264 AttachmentProvider getCurrentProvider(); 265 266 /** 267 * Deletes the given attachment version. 268 * 269 * @param att The attachment to delete 270 * @throws ProviderException If something goes wrong with the backend. 271 */ 272 void deleteVersion( Attachment att ) throws ProviderException; 273 274 /** 275 * Deletes all versions of the given attachment. 276 * 277 * @param att The Attachment to delete. 278 * @throws ProviderException if something goes wrong with the backend. 279 */ 280 void deleteAttachment( Attachment att ) throws ProviderException; 281 282 /** 283 * Validates the filename and makes sure it is legal. It trims and splits and replaces bad characters. 284 * 285 * @param filename file name to validate. 286 * @return A validated name with annoying characters replaced. 287 * @throws WikiException If the filename is not legal (e.g. empty) 288 */ 289 static String validateFileName( String filename ) throws WikiException { 290 if( filename == null || filename.trim().length() == 0 ) { 291 Logger.getLogger( AttachmentManager.class ).error( "Empty file name given." ); 292 293 // the caller should catch the exception and use the exception text as an i18n key 294 throw new WikiException( "attach.empty.file" ); 295 } 296 297 // Some browser send the full path info with the filename, so we need 298 // to remove it here by simply splitting along slashes and then taking the path. 299 final String[] splitpath = filename.split( "[/\\\\]" ); 300 filename = splitpath[splitpath.length-1]; 301 302 // Should help with IE 5.22 on OSX 303 filename = filename.trim(); 304 305 // If file name ends with .jsp or .jspf, the user is being naughty! 306 if( filename.toLowerCase().endsWith( ".jsp" ) || filename.toLowerCase().endsWith( ".jspf" ) ) { 307 Logger.getLogger( AttachmentManager.class ) 308 .info( "Attempt to upload a file with a .jsp/.jspf extension. In certain cases this " + 309 "can trigger unwanted security side effects, so we're preventing it." ); 310 311 // the caller should catch the exception and use the exception text as an i18n key 312 throw new WikiException( "attach.unwanted.file" ); 313 } 314 315 // Remove any characters that might be a problem. Most importantly - characters that might stop processing of the URL. 316 return StringUtils.replaceChars( filename, "#?\"'", "____" ); 317 } 318 319}