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.commons.fileupload2.core;
018
019import java.nio.charset.Charset;
020import java.nio.file.Path;
021
022import org.apache.commons.io.FileCleaningTracker;
023import org.apache.commons.io.build.AbstractOrigin;
024import org.apache.commons.io.build.AbstractStreamBuilder;
025import org.apache.commons.io.file.PathUtils;
026
027/**
028 * The default {@link FileItemFactory} implementation.
029 * <p>
030 * This implementation creates {@link FileItem} instances which keep their content either in memory, for smaller items, or in a temporary file on disk, for
031 * larger items. The size threshold, above which content will be stored on disk, is configurable, as is the directory in which temporary files will be created.
032 * </p>
033 * <p>
034 * If not otherwise configured, the default configuration values are as follows:
035 * </p>
036 * <ul>
037 * <li>Size threshold is 10 KB.</li>
038 * <li>Repository is the system default temporary directory, as returned by {@code System.getProperty("java.io.tmpdir")}.</li>
039 * </ul>
040 * <p>
041 * <b>NOTE</b>: Files are created in the system default temporary directory with predictable names. This means that a local attacker with write access to that
042 * directory can perform a TOUTOC attack to replace any uploaded file with a file of the attackers choice. The implications of this will depend on how the
043 * uploaded file is used but could be significant. When using this implementation in an environment with local, untrusted users, {@link Builder#setPath(Path)}
044 * MUST be used to configure a repository location that is not publicly writable. In a Servlet container the location identified by the ServletContext attribute
045 * {@code javax.servlet.context.tempdir} may be used.
046 * </p>
047 * <p>
048 * Temporary files, which are created for file items, should be deleted later on. The best way to do this is using a {@link FileCleaningTracker}, which you can
049 * set on the {@link DiskFileItemFactory}. However, if you do use such a tracker, then you must consider the following: Temporary files are automatically
050 * deleted as soon as they are no longer needed. (More precisely, when the corresponding instance of {@link java.io.File} is garbage collected.) This is done by
051 * the so-called reaper thread, which is started and stopped automatically by the {@link FileCleaningTracker} when there are files to be tracked. It might make
052 * sense to terminate that thread, for example, if your web application ends. See the section on "Resource cleanup" in the users guide of Commons FileUpload.
053 * </p>
054 *
055 * @see Builder
056 * @see Builder#get()
057 */
058public final class DiskFileItemFactory implements FileItemFactory<DiskFileItem> {
059
060    /**
061     * Builds a new {@link DiskFileItemFactory} instance.
062     * <p>
063     * For example:
064     * </p>
065     *
066     * <pre>{@code
067     * DiskFileItemFactory factory = DiskFileItemFactory.builder().setPath(path).setBufferSize(DEFAULT_THRESHOLD).get();
068     * }
069     * </pre>
070     */
071    public static class Builder extends AbstractStreamBuilder<DiskFileItemFactory, Builder> {
072
073        /**
074         * The instance of {@link FileCleaningTracker}, which is responsible for deleting temporary files.
075         * <p>
076         * May be null, if tracking files is not required.
077         * </p>
078         */
079        private FileCleaningTracker fileCleaningTracker;
080
081        public Builder() {
082            setBufferSize(DEFAULT_THRESHOLD);
083            setPath(PathUtils.getTempDirectory());
084            setCharset(DiskFileItem.DEFAULT_CHARSET);
085            setCharsetDefault(DiskFileItem.DEFAULT_CHARSET);
086        }
087
088        /**
089         * Constructs a new instance.
090         * <p>
091         * This builder use the aspects Path and buffer size.
092         * </p>
093         * <p>
094         * You must provide an origin that can be converted to a Reader by this builder, otherwise, this call will throw an
095         * {@link UnsupportedOperationException}.
096         * </p>
097         *
098         * @return a new instance.
099         * @throws UnsupportedOperationException if the origin cannot provide a Path.
100         * @see AbstractOrigin#getReader(Charset)
101         */
102        @Override
103        public DiskFileItemFactory get() {
104            return new DiskFileItemFactory(getPath(), getBufferSize(), getCharset(), fileCleaningTracker);
105        }
106
107        /**
108         * Sets the tracker, which is responsible for deleting temporary files.
109         *
110         * @param fileCleaningTracker Callback to track files created, or null (default) to disable tracking.
111         * @return this
112         */
113        public Builder setFileCleaningTracker(final FileCleaningTracker fileCleaningTracker) {
114            this.fileCleaningTracker = fileCleaningTracker;
115            return this;
116        }
117
118    }
119
120    /**
121     * The default threshold in bytes above which uploads will be stored on disk.
122     */
123    public static final int DEFAULT_THRESHOLD = 10_240;
124
125    /**
126     * Constructs a new {@link Builder}.
127     *
128     * @return a new {@link Builder}.
129     */
130    public static Builder builder() {
131        return new Builder();
132    }
133
134    /**
135     * The directory in which uploaded files will be stored, if stored on disk.
136     */
137    private final Path repository;
138
139    /**
140     * The threshold above which uploads will be stored on disk.
141     */
142    private final int threshold;
143
144    /**
145     * The instance of {@link FileCleaningTracker}, which is responsible for deleting temporary files.
146     * <p>
147     * May be null, if tracking files is not required.
148     * </p>
149     */
150    private final FileCleaningTracker fileCleaningTracker;
151
152    /**
153     * Default content Charset to be used when no explicit Charset parameter is provided by the sender.
154     */
155    private final Charset charsetDefault;
156
157    /**
158     * Constructs a preconfigured instance of this class.
159     *
160     * @param repository          The data repository, which is the directory in which files will be created, should the item size exceed the threshold.
161     * @param threshold           The threshold, in bytes, below which items will be retained in memory and above which they will be stored as a file.
162     * @param charsetDefault      Sets the default charset for use when no explicit charset parameter is provided by the sender.
163     * @param fileCleaningTracker Callback to track files created, or null (default) to disable tracking.
164     */
165    private DiskFileItemFactory(final Path repository, final int threshold, final Charset charsetDefault, final FileCleaningTracker fileCleaningTracker) {
166        this.threshold = threshold;
167        this.repository = repository;
168        this.charsetDefault = charsetDefault;
169        this.fileCleaningTracker = fileCleaningTracker;
170    }
171
172    @SuppressWarnings("unchecked")
173    @Override
174    public DiskFileItem.Builder fileItemBuilder() {
175        // @formatter:off
176        return DiskFileItem.builder()
177                .setBufferSize(threshold)
178                .setCharset(charsetDefault)
179                .setFileCleaningTracker(fileCleaningTracker)
180                .setPath(repository);
181        // @formatter:on
182    }
183
184    /**
185     * Gets the default charset for use when no explicit charset parameter is provided by the sender.
186     *
187     * @return the default charset
188     */
189    public Charset getCharsetDefault() {
190        return charsetDefault;
191    }
192
193    /**
194     * Gets the tracker, which is responsible for deleting temporary files.
195     *
196     * @return An instance of {@link FileCleaningTracker}, or null (default), if temporary files aren't tracked.
197     */
198    public FileCleaningTracker getFileCleaningTracker() {
199        return fileCleaningTracker;
200    }
201
202    /**
203     * Gets the directory used to temporarily store files that are larger than the configured size threshold.
204     *
205     * @return The directory in which temporary files will be located.
206     */
207    public Path getRepository() {
208        return repository;
209    }
210
211    /**
212     * Gets the size threshold beyond which files are written directly to disk. The default value is {@value #DEFAULT_THRESHOLD} bytes.
213     *
214     * @return The size threshold in bytes.
215     */
216    public int getThreshold() {
217        return threshold;
218    }
219}