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 *      https://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><em>State model</em>: The created instances of {@link DiskFileItem} are subject to a carefully designed state model,
041 * which is also controlled by the threshold. Therefore, it is strongly recommended to set the threshold explicitly, using
042 * {@link Builder#setThreshold(int)}. Details
043 * on the state model can be found {@link DiskFileItem here}.</p>
044 * <p>
045 * <strong>NOTE</strong>: Files are created in the system default temporary directory with predictable names. This means that a local attacker with write access
046 * to that 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
047 * the uploaded file is used, but could be significant. When using this implementation in an environment with local, untrusted users,
048 * {@link Builder#setPath(Path)} MUST be used to configure a repository location that is not publicly writable. In a Servlet container the location identified
049 * by the ServletContext attribute {@code javax.servlet.context.tempdir} may be used.
050 * </p>
051 * <p>
052 * 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
053 * set on the {@link DiskFileItemFactory}. However, if you do use such a tracker, then you must consider the following: Temporary files are automatically
054 * 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
055 * 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
056 * 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.
057 * </p>
058 *
059 * @see Builder
060 * @see Builder#get()
061 */
062public final class DiskFileItemFactory implements FileItemFactory<DiskFileItem> {
063
064    /**
065     * Builds a new {@link DiskFileItemFactory} instance.
066     * <p>
067     * For example:
068     * </p>
069     *
070     * <pre>{@code
071     * DiskFileItemFactory factory = DiskFileItemFactory.builder().setPath(path).setBufferSize(DEFAULT_THRESHOLD).get();
072     * }
073     * </pre>
074     */
075    public static class Builder extends AbstractStreamBuilder<DiskFileItemFactory, Builder> {
076
077        /**
078         * The instance of {@link FileCleaningTracker}, which is responsible for deleting temporary files.
079         * <p>
080         * May be null, if tracking files is not required.
081         * </p>
082         */
083        private FileCleaningTracker fileCleaningTracker;
084
085        /**
086         * The threshold. We do maintain this separate from the {@link #getBufferSize()},
087         * because the parent class might change the value in {@link #setBufferSize(int)}.
088         */
089        private int threshold;
090
091        /**
092         * Constructs a new instance.
093         */
094        public Builder() {
095            setBufferSize(DEFAULT_THRESHOLD);
096            setPath(PathUtils.getTempDirectory());
097            setCharset(DiskFileItem.DEFAULT_CHARSET);
098            setCharsetDefault(DiskFileItem.DEFAULT_CHARSET);
099        }
100
101        /**
102         * Constructs a new instance.
103         * <p>
104         * This builder use the aspects Path and buffer size.
105         * </p>
106         * <p>
107         * You must provide an origin that can be converted to a Reader by this builder, otherwise, this call will throw an
108         * {@link UnsupportedOperationException}.
109         * </p>
110         *
111         * @return a new instance.
112         * @throws UnsupportedOperationException if the origin cannot provide a Path.
113         * @see AbstractOrigin#getReader(Charset)
114         */
115        @Override
116        public DiskFileItemFactory get() {
117            return new DiskFileItemFactory(this);
118        }
119
120        /**
121         * Equivalent to {@link #getThreshold()}.
122         * @return The threshold, which is being used.
123         * @see #getThreshold()
124         * @deprecated Since 2.0.0, use {@link #getThreshold()} instead.
125         */
126        public int getBufferSize() {
127            return getThreshold();
128        }
129
130        /**
131         * Returns the threshold.
132         * @return The threshold.
133         */
134        public int getThreshold() {
135            return threshold;
136        }
137
138        /**
139         * Equivalent to {@link #setThreshold(int)}.
140         * @param bufferSize The threshold, which is being used.
141         * @see #setThreshold(int)
142         * @return This builder.
143         * @deprecated Since 2.0.0, use {@link #setThreshold(int)} instead.
144         */
145        @Override
146        public Builder setBufferSize(final int bufferSize) {
147            return setThreshold(bufferSize);
148        }
149
150        /**
151         * Sets the tracker, which is responsible for deleting temporary files.
152         *
153         * @param fileCleaningTracker Callback to track files created, or null (default) to disable tracking.
154         * @return {@code this} instance.
155         */
156        public Builder setFileCleaningTracker(final FileCleaningTracker fileCleaningTracker) {
157            this.fileCleaningTracker = fileCleaningTracker;
158            return this;
159        }
160
161        /**
162         * Sets the threshold. The uploaded data is typically kept in memory, until
163         * a certain number of bytes (the threshold) is reached. At this point, the
164         * incoming data is transferred to a temporary file, and the in-memory data
165         * is removed.
166         *
167         * The threshold will also control the <em>state model</em> of the created
168         * instances of {@link DiskFileItem}. Details on the state model can be
169         * found {@link DiskFileItem here}.
170         * @param threshold The threshold, which is being used.
171         * @return This builder.
172         */
173        public Builder setThreshold(final int threshold) {
174            this.threshold = threshold;
175            return this;
176        }
177    }
178
179    /**
180     * The default threshold in bytes above which uploads will be stored on disk.
181     */
182    public static final int DEFAULT_THRESHOLD = 10_240;
183
184    /**
185     * Constructs a new {@link Builder}.
186     *
187     * @return a new {@link Builder}.
188     */
189    public static Builder builder() {
190        return new Builder();
191    }
192
193    /**
194     * The directory in which uploaded files will be stored, if stored on disk.
195     */
196    private final Path repository;
197
198    /**
199     * The threshold above which uploads will be stored on disk.
200     */
201    private final int threshold;
202
203    /**
204     * The instance of {@link FileCleaningTracker}, which is responsible for deleting temporary files.
205     * <p>
206     * May be null, if tracking files is not required.
207     * </p>
208     */
209    private final FileCleaningTracker fileCleaningTracker;
210
211    /**
212     * Default content Charset to be used when no explicit Charset parameter is provided by the sender.
213     */
214    private final Charset charsetDefault;
215
216    /**
217     * Constructs a preconfigured instance of this class.
218     *
219     * @param repository          The data repository, which is the directory in which files will be created, should the item size exceed the threshold.
220     * @param threshold           The threshold, in bytes, below which items will be retained in memory and above which they will be stored as a file.
221     * @param charsetDefault      Sets the default charset for use when no explicit charset parameter is provided by the sender.
222     * @param fileCleaningTracker Callback to track files created, or null (default) to disable tracking.
223     */
224    private DiskFileItemFactory(final Builder builder) {
225        this.threshold = builder.threshold;
226        this.repository = builder.getPath();
227        this.charsetDefault = builder.getCharset();
228        this.fileCleaningTracker = builder.fileCleaningTracker;
229    }
230
231    @SuppressWarnings("unchecked")
232    @Override
233    public DiskFileItem.Builder fileItemBuilder() {
234        // @formatter:off
235        return DiskFileItem.builder()
236                .setThreshold(threshold)
237                .setCharset(charsetDefault)
238                .setFileCleaningTracker(fileCleaningTracker)
239                .setPath(repository);
240        // @formatter:on
241    }
242
243    /**
244     * Gets the default charset for use when no explicit charset parameter is provided by the sender.
245     *
246     * @return the default charset
247     */
248    public Charset getCharsetDefault() {
249        return charsetDefault;
250    }
251
252    /**
253     * Gets the tracker, which is responsible for deleting temporary files.
254     *
255     * @return An instance of {@link FileCleaningTracker}, or null (default), if temporary files aren't tracked.
256     */
257    public FileCleaningTracker getFileCleaningTracker() {
258        return fileCleaningTracker;
259    }
260
261    /**
262     * Gets the directory used to temporarily store files that are larger than the configured size threshold.
263     *
264     * @return The directory in which temporary files will be located.
265     */
266    public Path getRepository() {
267        return repository;
268    }
269
270    /**
271     * Gets the size threshold beyond which files are written directly to disk. The default value is {@value #DEFAULT_THRESHOLD} bytes.
272     *
273     * @return The size threshold in bytes.
274     */
275    public int getThreshold() {
276        return threshold;
277    }
278}