001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027
028package org.apache.http.util;
029
030import java.io.IOException;
031import java.io.InputStream;
032import java.io.InputStreamReader;
033import java.io.Reader;
034import java.io.UnsupportedEncodingException;
035import java.nio.charset.Charset;
036import java.nio.charset.UnsupportedCharsetException;
037
038import org.apache.http.HeaderElement;
039import org.apache.http.HttpEntity;
040import org.apache.http.HttpResponse;
041import org.apache.http.NameValuePair;
042import org.apache.http.ParseException;
043import org.apache.http.entity.ContentType;
044import org.apache.http.protocol.HTTP;
045
046/**
047 * Static helpers for dealing with {@link HttpEntity}s.
048 *
049 * @since 4.0
050 */
051public final class EntityUtils {
052
053    private static final int DEFAULT_BUFFER_SIZE = 4096;
054
055    private EntityUtils() {
056    }
057
058    /**
059     * Ensures that the entity content is fully consumed and the content stream, if exists,
060     * is closed. The process is done, <i>quietly</i> , without throwing any IOException.
061     *
062     * @param entity the entity to consume.
063     *
064     *
065     * @since 4.2
066     */
067    public static void consumeQuietly(final HttpEntity entity) {
068        try {
069          consume(entity);
070        } catch (final IOException ignore) {
071        }
072    }
073
074    /**
075     * Ensures that the entity content is fully consumed and the content stream, if exists,
076     * is closed.
077     *
078     * @param entity the entity to consume.
079     * @throws IOException if an error occurs reading the input stream
080     *
081     * @since 4.1
082     */
083    public static void consume(final HttpEntity entity) throws IOException {
084        if (entity == null) {
085            return;
086        }
087        if (entity.isStreaming()) {
088            final InputStream instream = entity.getContent();
089            if (instream != null) {
090                instream.close();
091            }
092        }
093    }
094
095    /**
096     * Updates an entity in a response by first consuming an existing entity, then setting the new one.
097     *
098     * @param response the response with an entity to update; must not be null.
099     * @param entity the entity to set in the response.
100     * @throws IOException if an error occurs while reading the input stream on the existing
101     * entity.
102     * @throws IllegalArgumentException if response is null.
103     *
104     * @since 4.3
105     */
106    public static void updateEntity(
107            final HttpResponse response, final HttpEntity entity) throws IOException {
108        Args.notNull(response, "Response");
109        consume(response.getEntity());
110        response.setEntity(entity);
111    }
112
113    /**
114     * Read the contents of an entity and return it as a byte array.
115     *
116     * @param entity the entity to read from=
117     * @return byte array containing the entity content. May be null if
118     *   {@link HttpEntity#getContent()} is null.
119     * @throws IOException if an error occurs reading the input stream
120     * @throws IllegalArgumentException if entity is null or if content length &gt; Integer.MAX_VALUE
121     */
122    public static byte[] toByteArray(final HttpEntity entity) throws IOException {
123        Args.notNull(entity, "Entity");
124        final InputStream instream = entity.getContent();
125        if (instream == null) {
126            return null;
127        }
128        try {
129            Args.check(entity.getContentLength() <= Integer.MAX_VALUE,
130                    "HTTP entity too large to be buffered in memory");
131            int capacity = (int)entity.getContentLength();
132            if (capacity < 0) {
133                capacity = DEFAULT_BUFFER_SIZE;
134            }
135            final ByteArrayBuffer buffer = new ByteArrayBuffer(capacity);
136            final byte[] tmp = new byte[DEFAULT_BUFFER_SIZE];
137            int l;
138            while((l = instream.read(tmp)) != -1) {
139                buffer.append(tmp, 0, l);
140            }
141            return buffer.toByteArray();
142        } finally {
143            instream.close();
144        }
145    }
146
147    /**
148     * Obtains character set of the entity, if known.
149     *
150     * @param entity must not be null
151     * @return the character set, or null if not found
152     * @throws ParseException if header elements cannot be parsed
153     * @throws IllegalArgumentException if entity is null
154     *
155     * @deprecated (4.1.3) use {@link ContentType#getOrDefault(HttpEntity)}
156     */
157    @Deprecated
158    public static String getContentCharSet(final HttpEntity entity) throws ParseException {
159        Args.notNull(entity, "Entity");
160        String charset = null;
161        if (entity.getContentType() != null) {
162            final HeaderElement values[] = entity.getContentType().getElements();
163            if (values.length > 0) {
164                final NameValuePair param = values[0].getParameterByName("charset");
165                if (param != null) {
166                    charset = param.getValue();
167                }
168            }
169        }
170        return charset;
171    }
172
173    /**
174     * Obtains MIME type of the entity, if known.
175     *
176     * @param entity must not be null
177     * @return the character set, or null if not found
178     * @throws ParseException if header elements cannot be parsed
179     * @throws IllegalArgumentException if entity is null
180     *
181     * @since 4.1
182     *
183     * @deprecated (4.1.3) use {@link ContentType#getOrDefault(HttpEntity)}
184     */
185    @Deprecated
186    public static String getContentMimeType(final HttpEntity entity) throws ParseException {
187        Args.notNull(entity, "Entity");
188        String mimeType = null;
189        if (entity.getContentType() != null) {
190            final HeaderElement values[] = entity.getContentType().getElements();
191            if (values.length > 0) {
192                mimeType = values[0].getName();
193            }
194        }
195        return mimeType;
196    }
197
198    private static String toString(
199            final HttpEntity entity,
200            final ContentType contentType) throws IOException {
201        final InputStream instream = entity.getContent();
202        if (instream == null) {
203            return null;
204        }
205        try {
206            Args.check(entity.getContentLength() <= Integer.MAX_VALUE,
207                    "HTTP entity too large to be buffered in memory");
208            int capacity = (int)entity.getContentLength();
209            if (capacity < 0) {
210                capacity = DEFAULT_BUFFER_SIZE;
211            }
212            Charset charset = null;
213            if (contentType != null) {
214                charset = contentType.getCharset();
215                if (charset == null) {
216                    final ContentType defaultContentType = ContentType.getByMimeType(contentType.getMimeType());
217                    charset = defaultContentType != null ? defaultContentType.getCharset() : null;
218                }
219            }
220            if (charset == null) {
221                charset = HTTP.DEF_CONTENT_CHARSET;
222            }
223            final Reader reader = new InputStreamReader(instream, charset);
224            final CharArrayBuffer buffer = new CharArrayBuffer(capacity);
225            final char[] tmp = new char[1024];
226            int l;
227            while((l = reader.read(tmp)) != -1) {
228                buffer.append(tmp, 0, l);
229            }
230            return buffer.toString();
231        } finally {
232            instream.close();
233        }
234    }
235
236    /**
237     * Get the entity content as a String, using the provided default character set
238     * if none is found in the entity.
239     * If defaultCharset is null, the default "ISO-8859-1" is used.
240     *
241     * @param entity must not be null
242     * @param defaultCharset character set to be applied if none found in the entity,
243     * or if the entity provided charset is invalid or not available.
244     * @return the entity content as a String. May be null if
245     *   {@link HttpEntity#getContent()} is null.
246     * @throws ParseException if header elements cannot be parsed
247     * @throws IllegalArgumentException if entity is null or if content length &gt; Integer.MAX_VALUE
248     * @throws IOException if an error occurs reading the input stream
249     * @throws java.nio.charset.UnsupportedCharsetException Thrown when the named entity's charset is not available in
250     * this instance of the Java virtual machine and no defaultCharset is provided.
251     */
252    public static String toString(
253            final HttpEntity entity, final Charset defaultCharset) throws IOException, ParseException {
254        Args.notNull(entity, "Entity");
255        ContentType contentType = null;
256        try {
257            contentType = ContentType.get(entity);
258        } catch (final UnsupportedCharsetException ex) {
259            if (defaultCharset == null) {
260                throw new UnsupportedEncodingException(ex.getMessage());
261            }
262        }
263        if (contentType != null) {
264            if (contentType.getCharset() == null) {
265                contentType = contentType.withCharset(defaultCharset);
266            }
267        } else {
268            contentType = ContentType.DEFAULT_TEXT.withCharset(defaultCharset);
269        }
270        return toString(entity, contentType);
271    }
272
273    /**
274     * Get the entity content as a String, using the provided default character set
275     * if none is found in the entity.
276     * If defaultCharset is null, the default "ISO-8859-1" is used.
277     *
278     * @param entity must not be null
279     * @param defaultCharset character set to be applied if none found in the entity
280     * @return the entity content as a String. May be null if
281     *   {@link HttpEntity#getContent()} is null.
282     * @throws ParseException if header elements cannot be parsed
283     * @throws IllegalArgumentException if entity is null or if content length &gt; Integer.MAX_VALUE
284     * @throws IOException if an error occurs reading the input stream
285     * @throws java.nio.charset.UnsupportedCharsetException Thrown when the named charset is not available in
286     * this instance of the Java virtual machine
287     */
288    public static String toString(
289            final HttpEntity entity, final String defaultCharset) throws IOException, ParseException {
290        return toString(entity, defaultCharset != null ? Charset.forName(defaultCharset) : null);
291    }
292
293    /**
294     * Read the contents of an entity and return it as a String.
295     * The content is converted using the character set from the entity (if any),
296     * failing that, "ISO-8859-1" is used.
297     *
298     * @param entity the entity to convert to a string; must not be null
299     * @return String containing the content.
300     * @throws ParseException if header elements cannot be parsed
301     * @throws IllegalArgumentException if entity is null or if content length &gt; Integer.MAX_VALUE
302     * @throws IOException if an error occurs reading the input stream
303     * @throws java.nio.charset.UnsupportedCharsetException Thrown when the named charset is not available in
304     * this instance of the Java virtual machine
305     */
306    public static String toString(final HttpEntity entity) throws IOException, ParseException {
307        Args.notNull(entity, "Entity");
308        return toString(entity, ContentType.get(entity));
309    }
310
311}