    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.logging.log4j.LogManager;
    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.IOException;
    033import java.io.InputStream;
    034import java.nio.file.Files;
    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.attachment.provider";
    051
    052    /** The property name for defining the attachment provider class name. */
    053    @Deprecated String PROP_PROVIDER_DEPRECATED = "jspwiki.attachmentProvider";
    054
    055    /** The maximum size of attachments that can be uploaded. */
    056    String PROP_MAXSIZE  = "jspwiki.attachment.maxsize";
    057
    058    /** A space-separated list of attachment types which can be uploaded */
    059    String PROP_ALLOWEDEXTENSIONS = "jspwiki.attachment.allowed";
    060
    061    /** A space-separated list of attachment types which cannot be uploaded */
    062    String PROP_FORBIDDENEXTENSIONS = "jspwiki.attachment.forbidden";
    063
    064    /** A space-separated list of attachment types which will never open in the browser. */
    065    String PROP_FORCEDOWNLOAD = "jspwiki.attachment.forceDownload";
    066
    067    /**
    068     *  Returns true, if attachments are enabled and running.
    069     *
    070     *  @return A boolean value indicating whether attachment functionality is enabled.
    071     */
    072    boolean attachmentsEnabled();
    073
    074    /**
    075     *  Gets info on a particular attachment, latest version.
    076     *
    077     *  @param name A full attachment name.
    078     *  @return Attachment, or null, if no such attachment exists.
    079     *  @throws ProviderException If something goes wrong.
    080     */
    081    default Attachment getAttachmentInfo( final String name ) throws ProviderException {
    082        return getAttachmentInfo( name, WikiProvider.LATEST_VERSION );
    083    }
    084
    085    /**
    086     *  Gets info on a particular attachment with the given version.
    087     *
    088     *  @param name A full attachment name.
    089     *  @param version A version number.
    090     *  @return Attachment, or null, if no such attachment or version exists.
    091     *  @throws ProviderException If something goes wrong.
    092     */
    093
    094    default Attachment getAttachmentInfo( final String name, final int version ) throws ProviderException {
    095        if( name == null ) {
    096            return null;
    097        }
    098
    099        return getAttachmentInfo( null, name, version );
    100    }
    101
    102    /**
    103     *  Figures out the full attachment name from the context and attachment name.
    104     *
    105     *  @param context The current WikiContext
    106     *  @param attachmentname The file name of the attachment.
    107     *  @return Attachment, or null, if no such attachment exists.
    108     *  @throws ProviderException If something goes wrong.
    109     */
    110    default Attachment getAttachmentInfo( final Context context, final String attachmentname ) throws ProviderException {
    111        return getAttachmentInfo( context, attachmentname, WikiProvider.LATEST_VERSION );
    112    }
    113
    114    /**
    115     *  Figures out the full attachment name from the context and attachment name.
    116     *
    117     *  @param context The current WikiContext
    118     *  @param attachmentname The file name of the attachment.
    119     *  @param version A particular version.
    120     *  @return Attachment, or null, if no such attachment or version exists.
    121     *  @throws ProviderException If something goes wrong.
    122     */
    123    Attachment getAttachmentInfo( Context context, String attachmentname, int version ) throws ProviderException;
    124
    125    /**
    126     *  Figures out the full attachment name from the context and attachment name.
    127     *
    128     *  @param context The current WikiContext
    129     *  @param attachmentname The file name of the attachment.
    130     *  @return Attachment, or null, if no such attachment exists.
    131     */
    132    String getAttachmentInfoName( Context context, String attachmentname );
    133
    134    /**
    135     *  Returns the list of attachments associated with a given wiki page. If there are no attachments, returns an empty Collection.
    136     *
    137     *  @param wikipage The wiki page from which you are seeking attachments for.
    138     *  @return a valid collection of attachments.
    139     *  @throws ProviderException If there was something wrong in the backend.
    140     */
    141    List< Attachment > listAttachments( Page wikipage ) throws ProviderException;
    142
    143    /**
    144     *  Returns true, if the page has any attachments at all.  This is a convenience method.
    145     *
    146     *  @param wikipage The wiki page from which you are seeking attachments for.
    147     *  @return True, if the page has attachments, else false.
    148     */
    149    default boolean hasAttachments( final Page wikipage ) {
    150        try {
    151            return !listAttachments(wikipage).isEmpty();
    152        } catch( final Exception e ) {
    153            LogManager.getLogger( AttachmentManager.class ).info( e.getMessage(), e );
    154        }
    155
    156        return false;
    157    }
    158
    159    /**
    160     *  Check if attachement link should force a download iso opening the attachment in the browser.
    161     *
    162     *  @param name  Name of attachment to be checked
    163     *  @return true, if the attachment should be downloaded when clicking the link
    164     *  @since 2.11.0 M4
    165    */
    166    boolean forceDownload( String name );
    167
    168    /**
    169     *  Finds a (real) attachment from the repository as an {@link InputStream}.
    170     *
    171     *  @param att Attachment
    172     *  @return An InputStream to read from. May return null, if attachments are disabled.
    173     *  @throws IOException If the stream cannot be opened
    174     *  @throws ProviderException If the backend fails due to some other reason.
    175     */
    176    default InputStream getAttachmentStream( final Attachment att ) throws IOException, ProviderException {
    177        return getAttachmentStream( null, att );
    178    }
    179
    180    /**
    181     *  Returns an attachment stream using the particular WikiContext. This method should be used instead of
    182     *  {@link #getAttachmentStream(Attachment)}, since it also allows the DynamicAttachments to function.
    183     *
    184     *  @param ctx The Wiki Context
    185     *  @param att The Attachment to find
    186     *  @return An InputStream.  May return null, if attachments are disabled.  You must take care of closing it.
    187     *  @throws ProviderException If the backend fails due to some reason
    188     *  @throws IOException If the stream cannot be opened
    189     */
    190    InputStream getAttachmentStream( Context ctx, Attachment att ) throws ProviderException, IOException;
    191
    192    /**
    193     *  Stores a dynamic attachment.  Unlike storeAttachment(), this just stores the attachment in the memory.
    194     *
    195     *  @param ctx A WikiContext
    196     *  @param att An attachment to store
    197     */
    198    void storeDynamicAttachment( Context ctx, DynamicAttachment att );
    199
    200    /**
    201     *  Finds a DynamicAttachment.  Normally, you should just use {@link #getAttachmentInfo(String)} , since that will find also
    202     *  {@link DynamicAttachment}s.
    203     *
    204     *  @param name The name of the attachment to look for
    205     *  @return An Attachment, or null.
    206     *  @see #getAttachmentInfo(String)
    207     */
    208    DynamicAttachment getDynamicAttachment( String name );
    209
    210    /**
    211     *  Stores an attachment that lives in the given file. If the attachment did not exist previously, this method will create it.
    212     *  If it did exist, it stores a new version.
    213     *
    214     *  @param att Attachment to store this under.
    215     *  @param source A file to read from.
    216     *
    217     *  @throws IOException If writing the attachment failed.
    218     *  @throws ProviderException If something else went wrong.
    219     */
    220    default void storeAttachment( final Attachment att, final File source ) throws IOException, ProviderException {
    221        try( final InputStream in = Files.newInputStream( source.toPath() ) ) {
    222            storeAttachment( att, in );
    223        }
    224    }
    225
    226    /**
    227     *  Stores an attachment directly from a stream. If the attachment did not exist previously, this method will create it.
    228     *  If it did exist, it stores a new version.
    229     *
    230     *  @param att Attachment to store this under.
    231     *  @param in  InputStream from which the attachment contents will be read.
    232     *  @throws IOException If writing the attachment failed.
    233     *  @throws ProviderException If something else went wrong.
    234     */
    235    void storeAttachment( Attachment att, InputStream in ) throws IOException, ProviderException;
    236
    237    /**
    238     *  Returns a list of versions of the attachment.
    239     *
    240     *  @param attachmentName A fully qualified name of the attachment.
    241     *
    242     *  @return A list of Attachments.  May return null, if attachments are
    243     *          disabled.
    244     *  @throws ProviderException If the provider fails for some reason.
    245     */
    246    List< Attachment > getVersionHistory( String attachmentName ) throws ProviderException;
    247
    248    /**
    249     *  Returns a collection of Attachments, containing each and every attachment that is in this Wiki.
    250     *
    251     *  @return A collection of attachments.  If attachments are disabled, will return an empty collection.
    252     *  @throws ProviderException If something went wrong with the backend
    253     */
    254    Collection< Attachment > getAllAttachments() throws ProviderException;
    255
    256    /**
    257     *  Returns the current attachment provider.
    258     *
    259     *  @return The current provider.  May be null, if attachments are disabled.
    260     */
    261    AttachmentProvider getCurrentProvider();
    262
    263    /**
    264     *  Deletes the given attachment version.
    265     *
    266     *  @param att The attachment to delete
    267     *  @throws ProviderException If something goes wrong with the backend.
    268     */
    269    void deleteVersion( Attachment att ) throws ProviderException;
    270
    271    /**
    272     *  Deletes all versions of the given attachment.
    273     *
    274     *  @param att The Attachment to delete.
    275     *  @throws ProviderException if something goes wrong with the backend.
    276     */
    277    void deleteAttachment( Attachment att ) throws ProviderException;
    278
    279    /**
    280     *  Validates the filename and makes sure it is legal.  It trims and splits and replaces bad characters.
    281     *
    282     *  @param filename file name to validate.
    283     *  @return A validated name with annoying characters replaced.
    284     *  @throws WikiException If the filename is not legal (e.g. empty)
    285     */
    286    static String validateFileName( String filename ) throws WikiException {
    287        if( filename == null || filename.trim().isEmpty() ) {
    288            LogManager.getLogger( AttachmentManager.class ).error( "Empty file name given." );
    289
    290            // the caller should catch the exception and use the exception text as an i18n key
    291            throw new WikiException(  "attach.empty.file" );
    292        }
    293
    294        //  Some browsers send the full path info with the filename, so we need
    295        //  to remove it here by simply splitting along slashes and then taking the path.
    296        final String[] splitpath = filename.split( "[/\\\\]" );
    297        filename = splitpath[splitpath.length-1];
    298
    299        // Should help with IE 5.22 on OSX
    300        filename = filename.trim();
    301
    302        // If file name ends with .jsp or .jspf, the user is being naughty!
    303        if( filename.toLowerCase().endsWith( ".jsp" ) || filename.toLowerCase().endsWith( ".jspf" ) ) {
    304            LogManager.getLogger( AttachmentManager.class )
    305                      .info( "Attempt to upload a file with a .jsp/.jspf extension.  In certain cases this " +
    306                             "can trigger unwanted security side effects, so we're preventing it." );
    307
    308            // the caller should catch the exception and use the exception text as an i18n key
    309            throw new WikiException(  "attach.unwanted.file"  );
    310        }
    311
    312        //  Remove any characters that might be a problem. Most importantly - characters that might stop processing of the URL.
    313        return StringUtils.replaceChars( filename, "#?\"'", "____" );
    314    }
    315
    316}