All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.catalina.fileupload.PartItem Maven / Gradle / Ivy

There is a newer version: 7.2024.1.Alpha1
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2016 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 *
 *
 * This file incorporates work covered by the following copyright and
 * permission notice:
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// Portions Copyright [2016-2021] [Payara Foundation and/or its affiliates]
package org.apache.catalina.fileupload;

import org.apache.catalina.Globals;
import org.apache.catalina.LogFacade;
import org.apache.catalina.util.RequestUtil;

import jakarta.servlet.http.Part;
import java.io.*;
import org.glassfish.hk2.utilities.CleanerFactory;
import java.nio.charset.Charset;
import java.sql.SQLException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 * This class implements java.servlet.http.Part.
 *
 * Original authors:
 * @author Rafal Krzewski
 * @author Sean Legassick
 * @author Jason van Zyl
 * @author John McNally
 * @author Martin Cooper
 * @author Sean C. Sullivan
 *
 * Adopted for Glassfish:
 * @author Kin-man Chung
 *
 * @version $Id: PartItem.java $
 */
class PartItem
    implements Serializable, Part {

    // ----------------------------------------------------- Manifest constants

    /**
     * The UID to use when serializing this instance.
     */
    private static final long serialVersionUID = 2237570099615271025L;

    private static final Logger log = LogFacade.getLogger();
    private final ResourceBundle rb = log.getResourceBundle();

    // ----------------------------------------------------------- Data members

    /**
     * UID used in unique file name generation.
     */
    private static final String UID =
            new java.rmi.server.UID().toString()
                .replace(':', '_').replace('-', '_');

    /**
     * Counter used in unique identifier generation.
     */
    private static int counter = 0;


    /**
     * The name of the form field as provided by the browser.
     */
    private String fieldName;


    /**
     * The content type passed by the browser, or null if
     * not defined.
     */
    private String contentType;


    /**
     * Whether or not this item is a simple form field.
     */
    private boolean isFormField;


    /**
     * The original filename in the user's filesystem.
     */
    private String fileName;


    /**
     * The size of the item, in bytes. This is used to cache the size when a
     * file item is moved from its original location.
     */
    private long size = -1;


    /**
     * The threshold above which uploads will be stored on disk.
     */
    private int sizeThreshold;


    /**
     * The directory in which uploaded files will be stored, if stored on disk.
     */
    private File repository;


    /**
     * Cached contents of the file.
     */
    private byte[] cachedContent;


    /**
     * Output stream for this item.
     */
    private transient DeferredFileOutputStream dfos;

    /**
     * The temporary file to use.
     */
    private transient File tempFile;

    /**
     * File to allow for serialization of the content of this item.
     */
    private File dfosFile;

    /**
     * The items headers.
     */
    private PartHeaders headers;

    /**
     * The Multipart base.
     */
    private Multipart multipart;

    /**
     * The request character encoding;
     */
    private String requestCharEncoding;

    // ----------------------------------------------------------- Constructors


    /**
     * Constructs a new DiskFileItem instance.
     *
     * @param multipart     The Multipart instance.
     * @param headers       The PartHeaders instance.
     * @param fieldName     The name of the form field.
     * @param contentType   The content type passed by the browser or
     *                      null if not specified.
     * @param isFormField   Whether or not this item is a plain form field, as
     *                      opposed to a file upload.
     * @param fileName      The original filename in the user's filesystem, or
     *                      null if not specified.
     * @param requestCharEncoding
     *                      The request character encoding.
     */
    public PartItem(Multipart multipart, PartHeaders headers,
                    String fieldName, String contentType,
                    boolean isFormField, String fileName,
                    String requestCharEncoding) {

        this.multipart = multipart;
        this.headers = headers;
        this.fieldName = fieldName;
        this.contentType = contentType;
        this.isFormField = isFormField;
        this.fileName = fileName;
        this.requestCharEncoding = requestCharEncoding;
        this.sizeThreshold = multipart.getFileSizeThreshold();
        this.repository = multipart.getRepository();
        registerCleanupEvent();
    }


    // ------------------------------- Methods from javax.activation.DataSource


    /**
     * Returns an {@link java.io.InputStream InputStream} that can be
     * used to retrieve the contents of the file.
     *
     * @return An {@link java.io.InputStream InputStream} that can be
     *         used to retrieve the contents of the file.
     *
     * @throws IOException if an error occurs.
     */
    public InputStream getInputStream()
        throws IOException {
        if (!isInMemory()) {
            return new FileInputStream(dfos.getFile());
        }

        if (cachedContent == null) {
            cachedContent = dfos.getData();
        }
        return new ByteArrayInputStream(cachedContent);
    }


    /**
     * Returns the content type passed by the agent or null if
     * not defined.
     *
     * @return The content type passed by the agent or null if
     *         not defined.
     */
    public String getContentType() {
        return contentType;
    }


    /**
     * Returns the content charset passed by the agent or null if
     * not defined.
     *
     * @return The content charset passed by the agent or null if
     *         not defined.
     */
    public String getCharSet() {
        ParameterParser parser = new ParameterParser();
        parser.setLowerCaseNames(true);
        // Parameter parser can handle null input
        Map params = parser.parse(getContentType(), ';');
        return params.get("charset");
    }


    /**
     * Returns the original filename in the client's filesystem.
     *
     * @return The original filename in the client's filesystem.
     */
    public String getSubmittedFileName() {
        return fileName;
    }


    // ------------------------------------------------------- FileItem methods


    /**
     * Provides a hint as to whether or not the file contents will be read
     * from memory.
     *
     * @return true if the file contents will be read
     *         from memory; false otherwise.
     */
    public boolean isInMemory() {
        if (cachedContent != null) {
            return true;
        }
        return dfos.isInMemory();
    }


    /**
     * Returns the size of the file.
     *
     * @return The size of the file, in bytes.
     */
    public long getSize() {
        if (size >= 0) {
            return size;
        } else if (cachedContent != null) {
            return cachedContent.length;
        } else if (dfos.isInMemory()) {
            return dfos.getData().length;
        } else {
            return dfos.getFile().length();
        }
    }


    /**
     * Returns the contents of the file as an array of bytes.  If the
     * contents of the file were not yet cached in memory, they will be
     * loaded from the disk storage and cached.
     *
     * @return The contents of the file as an array of bytes.
     */
    public byte[] get() {
        if (isInMemory()) {
            if (cachedContent == null) {
                cachedContent = dfos.getData();
            }
            return cachedContent;
        }

        byte[] fileData = new byte[(int) getSize()];
        FileInputStream fis = null;

        try {
            fis = new FileInputStream(dfos.getFile());
            if (fis.read(fileData) != (int)getSize())
                if (log.isLoggable(Level.INFO))
                    log.log(Level.INFO, LogFacade.FILE_DATA_IS_EMPTY_INFO);
        } catch (IOException e) {
            fileData = null;
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    // ignore
                }
            }
        }

        return fileData;
    }


    /**
     * Returns the contents of the file as a String, using the specified
     * encoding.  This method uses {@link #get()} to retrieve the
     * contents of the file.
     *
     * @param charset The charset to use.
     *
     * @return The contents of the file, as a string.
     *
     * @throws UnsupportedEncodingException if the requested character
     *                                      encoding is not available.
     */
    public String getString(final String charset)
        throws UnsupportedEncodingException {
        return new String(get(), RequestUtil.lookupCharset(charset));
    }


    /**
     * Returns the contents of the file as a String, using the default
     * character encoding.  This method uses {@link #get()} to retrieve the
     * contents of the file.
     *
     * @return The contents of the file, as a string.
     *
     * @todo Consider making this method throw UnsupportedEncodingException.
     */
    public String getString() {
        byte[] rawdata = get();
        String charset = getCharSet();
        if (charset == null) {
            // If content-type does not specify a charset, use the request
            // character encoding if it is non-null;
            if (requestCharEncoding != null) {
                charset = requestCharEncoding;
            } else {
                // Media subtypes of type "text" are defined to have a default
                // charset value of "ISO-8859-1" when received via HTTP
                charset = Globals.ISO_8859_1_ENCODING;
            }
        }
        try {
            return new String(rawdata, RequestUtil.lookupCharset(charset));
        } catch (UnsupportedEncodingException e) {
            return new String(rawdata, Charset.defaultCharset());
        }
    }


    /**
     * A convenience method to write an uploaded item to disk. The client code
     * is not concerned with whether or not the item is stored in memory, or on
     * disk in a temporary location. They just want to write the uploaded item
     * to a file.
     * 

* This implementation first attempts to rename the uploaded item to the * specified destination file, if the item was originally written to disk. * Otherwise, the data will be copied to the specified file. *

* This method is only guaranteed to work once, the first time it * is invoked for a particular item. This is because, in the event that the * method renames a temporary file, that file will no longer be available * to copy or rename again at a later time. * * @param file The file into which the uploaded item should be stored. * * @throws IOException if an error occurs. */ public void write(File file) throws IOException { if (isInMemory()) { FileOutputStream fout = null; try { fout = new FileOutputStream(file); fout.write(get()); } finally { if (fout != null) { fout.close(); } } } else { File outputFile = getStoreLocation(); if (outputFile != null) { // Save the length of the file size = outputFile.length(); /* * The uploaded file is being stored on disk * in a temporary location so move it to the * desired file. */ if (!outputFile.renameTo(file)) { BufferedInputStream in = null; BufferedOutputStream out = null; try { in = new BufferedInputStream( new FileInputStream(outputFile)); out = new BufferedOutputStream( new FileOutputStream(file)); Streams.copy(in, out, false); } finally { if (in != null) { try { in.close(); } catch (IOException e) { // ignore } } if (out != null) { try { out.close(); } catch (IOException e) { // ignore } } } } } else { /* * For whatever reason we cannot write the * file to disk. */ throw new IOException( "Cannot write uploaded file to disk!"); } } } public void write (String file) throws IOException { write(new File(repository, file)); } /** * Deletes the underlying storage for a file item, including deleting any * associated temporary disk file. Although this storage will be deleted * automatically when the FileItem instance is garbage * collected, this method can be used to ensure that this is done at an * earlier time, thus preserving system resources. */ public void delete() { cachedContent = null; File outputFile = getStoreLocation(); if (outputFile != null && outputFile.exists()) { deleteFile(outputFile); } } /** * Returns the name of the field in the multipart form corresponding to * this file item. * * @return The name of the form field. * * @see #setName(java.lang.String) * */ public String getName() { return fieldName; } /** * Sets the field name used to reference this file item. * * @param fieldName The name of the form field. * * @see #getName() * */ public void setName(String fieldName) { this.fieldName = fieldName; } /** * Determines whether or not a FileItem instance represents * a simple form field. * * @return true if the instance represents a simple form * field; false if it represents an uploaded file. * * @see #setFormField(boolean) * */ public boolean isFormField() { return isFormField; } /** * Specifies whether or not a FileItem instance represents * a simple form field. * * @param state true if the instance represents a simple form * field; false if it represents an uploaded file. * * @see #isFormField() * */ public void setFormField(boolean state) { isFormField = state; } /** * Returns an {@link java.io.OutputStream OutputStream} that can * be used for storing the contents of the file. * * @return An {@link java.io.OutputStream OutputStream} that can be used * for storing the contensts of the file. * * @throws IOException if an error occurs. */ public OutputStream getOutputStream() throws IOException { if (dfos == null) { File outputFile = getTempFile(); dfos = new DeferredFileOutputStream(sizeThreshold, outputFile); } return dfos; } // --------------------------------------------------------- Public methods /** * Returns the {@link java.io.File} object for the FileItem's * data's temporary location on the disk. Note that for * FileItems that have their data stored in memory, * this method will return null. When handling large * files, you can use {@link java.io.File#renameTo(java.io.File)} to * move the file to new location without copying the data, if the * source and destination locations reside within the same logical * volume. * * @return The data file, or null if the data is stored in * memory. */ public File getStoreLocation() { return dfos == null ? null : dfos.getFile(); } // ------------------------------------------------------ Protected methods /** * Removes the file contents from the temporary storage. */ public final void registerCleanupEvent() { CleanerFactory.create().register(this, () -> { File outputFile = dfos.getFile(); if (outputFile != null && outputFile.exists()) { deleteFile(outputFile); } }); } /** * Creates and returns a {@link java.io.File File} representing a uniquely * named temporary file in the configured repository path. The lifetime of * the file is tied to the lifetime of the FileItem instance; * the file will be deleted when the instance is garbage collected. * * @return The {@link java.io.File File} to be used for temporary storage. */ protected File getTempFile() { if (tempFile == null) { File tempDir = repository; if (tempDir == null) { tempDir = new File(System.getProperty("java.io.tmpdir")); } String tempFileName = "upload_" + UID + "_" + getUniqueId() + ".tmp"; tempFile = new File(tempDir, tempFileName); } return tempFile; } // -------------------------------------------------------- Private methods /** * Returns an identifier that is unique within the class loader used to * load this class, but does not have random-like apearance. * * @return A String with the non-random looking instance identifier. */ private static String getUniqueId() { final int limit = 100000000; int current; synchronized (PartItem.class) { current = counter++; } String id = Integer.toString(current); // If you manage to get more than 100 million of ids, you'll // start getting ids longer than 8 characters. if (current < limit) { id = ("00000000" + id).substring(id.length()); } return id; } /** * Returns a string representation of this object. * * @return a string representation of this object. */ public String toString() { return "File name=" + this.getSubmittedFileName() + ", StoreLocation=" + String.valueOf(this.getStoreLocation()) + ", size=" + this.getSize() + "bytes, " + "isFormField=" + isFormField() + ", FieldName=" + this.getName(); } // -------------------------------------------------- Serialization methods /** * Writes the state of this object during serialization. * * @param out The stream to which the state should be written. * * @throws IOException if an error occurs. */ private void writeObject(ObjectOutputStream out) throws IOException { // Read the data if (dfos.isInMemory()) { cachedContent = get(); } else { cachedContent = null; dfosFile = dfos.getFile(); } // write out values out.defaultWriteObject(); } /** * Reads the state of this object during deserialization. * * @param in The stream from which the state should be read. * * @throws IOException if an error occurs. * @throws ClassNotFoundException if class cannot be found. */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { // read values in.defaultReadObject(); // PAYARA-953 protect against null byte attacks if (repository != null) { if (repository.getPath().contains("\0")) { throw new IOException("Repository path cannot contain a null byte"); } if (!repository.isDirectory()) { throw new IOException("Repository path " + repository.getPath() + " is not a directory "); } } if (dfosFile != null) { if (dfosFile.getPath().contains("\0")) { throw new IOException("File path cannot contain a null byte"); } } // END PAYARA-953 try (OutputStream output = getOutputStream()) { if (cachedContent != null) { output.write(cachedContent); } else { FileInputStream input = new FileInputStream(dfosFile); Streams.copy(input, output, false); deleteFile(dfosFile); dfosFile = null; } } cachedContent = null; } /** * Returns the value of the specified mime header as a String. * @param name a String specifying the header name * @return a String containing the value of the requested header, * or null if the part does not have a header of that name */ public String getHeader(String name) { return headers.getHeader(name); } /** * Returns all the values of the specified Part header * @param name - a String specifying the header name * @return a Collection of the values of the requested header. * If the Part does not have any headers of that name return an * empty Collection. If the container does not allow access to header * information, return null */ public Collection getHeaders(String name) { List values = headers.getHeaders(name); if (values != Collections.EMPTY_LIST) { values = Collections.unmodifiableList(values); } return values; } /** * Returns a Collection of all the header names this part contains. * @return a Collection of all the header names sent with this part; * if the part has no headers, an empty Collection; * if the servlet container does not allow servlets to use this * method, null */ public Collection getHeaderNames() { return headers.getHeaderNames(); } private void deleteFile(File file) { if (!file.delete() && log.isLoggable(Level.FINE)) { log.log(Level.FINE, "Cannot delete file: " + file); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy