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.fileupload.util;
018
019import java.io.ByteArrayOutputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.OutputStream;
023
024import org.apache.commons.fileupload.InvalidFileNameException;
025import org.apache.commons.io.IOUtils;
026
027/**
028 * Utility class for working with streams.
029 */
030public final class Streams {
031
032    /**
033     * Private constructor, to prevent instantiation.
034     * This class has only static methods.
035     */
036    private Streams() {
037        // Does nothing
038    }
039
040    /**
041     * Default buffer size for use in
042     * {@link #copy(InputStream, OutputStream, boolean)}.
043     */
044    public static final int DEFAULT_BUFFER_SIZE = 8192;
045
046    /**
047     * Copies the contents of the given {@link InputStream}
048     * to the given {@link OutputStream}. Shortcut for
049     * <pre>
050     *   copy(pInputStream, pOutputStream, new byte[8192]);
051     * </pre>
052     *
053     * @param inputStream The input stream, which is being read.
054     * It is guaranteed, that {@link InputStream#close()} is called
055     * on the stream.
056     * @param outputStream The output stream, to which data should
057     * be written. May be null, in which case the input streams
058     * contents are simply discarded.
059     * @param closeOutputStream True guarantees, that {@link OutputStream#close()}
060     * is called on the stream. False indicates, that only
061     * {@link OutputStream#flush()} should be called finally.
062     *
063     * @return Number of bytes, which have been copied.
064     * @throws IOException An I/O error occurred.
065     */
066    public static long copy(InputStream inputStream, OutputStream outputStream, boolean closeOutputStream)
067            throws IOException {
068        return copy(inputStream, outputStream, closeOutputStream, new byte[DEFAULT_BUFFER_SIZE]);
069    }
070
071    /**
072     * Copies the contents of the given {@link InputStream}
073     * to the given {@link OutputStream}.
074     *
075     * @param inputStream The input stream, which is being read.
076     *   It is guaranteed, that {@link InputStream#close()} is called
077     *   on the stream.
078     * @param outputStream The output stream, to which data should
079     *   be written. May be null, in which case the input streams
080     *   contents are simply discarded.
081     * @param closeOutputStream True guarantees, that {@link OutputStream#close()}
082     *   is called on the stream. False indicates, that only
083     *   {@link OutputStream#flush()} should be called finally.
084     * @param buffer Temporary buffer, which is to be used for
085     *   copying data.
086     * @return Number of bytes, which have been copied.
087     * @throws IOException An I/O error occurred.
088     */
089    public static long copy(InputStream inputStream,
090            OutputStream outputStream, boolean closeOutputStream,
091            byte[] buffer)
092    throws IOException {
093        OutputStream out = outputStream;
094        InputStream in = inputStream;
095        try {
096            long total = 0;
097            for (;;) {
098                int res = in.read(buffer);
099                if (res == -1) {
100                    break;
101                }
102                if (res > 0) {
103                    total += res;
104                    if (out != null) {
105                        out.write(buffer, 0, res);
106                    }
107                }
108            }
109            if (out != null) {
110                if (closeOutputStream) {
111                    out.close();
112                } else {
113                    out.flush();
114                }
115                out = null;
116            }
117            in.close();
118            in = null;
119            return total;
120        } finally {
121            IOUtils.closeQuietly(in);
122            if (closeOutputStream) {
123                IOUtils.closeQuietly(out);
124            }
125        }
126    }
127
128    /**
129     * This convenience method allows to read a
130     * {@link org.apache.commons.fileupload.FileItemStream}'s
131     * content into a string. The platform's default character encoding
132     * is used for converting bytes into characters.
133     *
134     * @param inputStream The input stream to read.
135     * @see #asString(InputStream, String)
136     * @return The streams contents, as a string.
137     * @throws IOException An I/O error occurred.
138     */
139    public static String asString(InputStream inputStream) throws IOException {
140        ByteArrayOutputStream baos = new ByteArrayOutputStream();
141        copy(inputStream, baos, true);
142        return baos.toString();
143    }
144
145    /**
146     * This convenience method allows to read a
147     * {@link org.apache.commons.fileupload.FileItemStream}'s
148     * content into a string, using the given character encoding.
149     *
150     * @param inputStream The input stream to read.
151     * @param encoding The character encoding, typically "UTF-8".
152     * @see #asString(InputStream)
153     * @return The streams contents, as a string.
154     * @throws IOException An I/O error occurred.
155     */
156    public static String asString(InputStream inputStream, String encoding) throws IOException {
157        ByteArrayOutputStream baos = new ByteArrayOutputStream();
158        copy(inputStream, baos, true);
159        return baos.toString(encoding);
160    }
161
162    /**
163     * Checks, whether the given file name is valid in the sense,
164     * that it doesn't contain any NUL characters. If the file name
165     * is valid, it will be returned without any modifications. Otherwise,
166     * an {@link InvalidFileNameException} is raised.
167     *
168     * @param fileName The file name to check
169     * @return Unmodified file name, if valid.
170     * @throws InvalidFileNameException The file name was found to be invalid.
171     */
172    public static String checkFileName(String fileName) {
173        if (fileName != null  &&  fileName.indexOf('\u0000') != -1) {
174            // pFileName.replace("\u0000", "\\0")
175            final StringBuilder sb = new StringBuilder();
176            for (int i = 0;  i < fileName.length();  i++) {
177                char c = fileName.charAt(i);
178                switch (c) {
179                    case 0:
180                        sb.append("\\0");
181                        break;
182                    default:
183                        sb.append(c);
184                        break;
185                }
186            }
187            throw new InvalidFileNameException(fileName,
188                    "Invalid file name: " + sb);
189        }
190        return fileName;
191    }
192
193}