1/*
2 * Copyright (C) 2014 Square, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.squareup.okhttp;
17
18import com.squareup.okhttp.internal.Util;
19import java.io.Closeable;
20import java.io.IOException;
21import java.io.InputStream;
22import java.io.InputStreamReader;
23import java.io.Reader;
24import java.nio.charset.Charset;
25import okio.Buffer;
26import okio.BufferedSource;
27
28import static com.squareup.okhttp.internal.Util.UTF_8;
29
30public abstract class ResponseBody implements Closeable {
31  /** Multiple calls to {@link #charStream()} must return the same instance. */
32  private Reader reader;
33
34  public abstract MediaType contentType();
35
36  /**
37   * Returns the number of bytes in that will returned by {@link #bytes}, or
38   * {@link #byteStream}, or -1 if unknown.
39   */
40  public abstract long contentLength() throws IOException;
41
42  public final InputStream byteStream() throws IOException {
43    return source().inputStream();
44  }
45
46  public abstract BufferedSource source() throws IOException;
47
48  public final byte[] bytes() throws IOException {
49    long contentLength = contentLength();
50    if (contentLength > Integer.MAX_VALUE) {
51      throw new IOException("Cannot buffer entire body for content length: " + contentLength);
52    }
53
54    BufferedSource source = source();
55    byte[] bytes;
56    try {
57      bytes = source.readByteArray();
58    } finally {
59      Util.closeQuietly(source);
60    }
61    if (contentLength != -1 && contentLength != bytes.length) {
62      throw new IOException("Content-Length and stream length disagree");
63    }
64    return bytes;
65  }
66
67  /**
68   * Returns the response as a character stream decoded with the charset
69   * of the Content-Type header. If that header is either absent or lacks a
70   * charset, this will attempt to decode the response body as UTF-8.
71   */
72  public final Reader charStream() throws IOException {
73    Reader r = reader;
74    return r != null ? r : (reader = new InputStreamReader(byteStream(), charset()));
75  }
76
77  /**
78   * Returns the response as a string decoded with the charset of the
79   * Content-Type header. If that header is either absent or lacks a charset,
80   * this will attempt to decode the response body as UTF-8.
81   */
82  public final String string() throws IOException {
83    return new String(bytes(), charset().name());
84  }
85
86  private Charset charset() {
87    MediaType contentType = contentType();
88    return contentType != null ? contentType.charset(UTF_8) : UTF_8;
89  }
90
91  @Override public void close() throws IOException {
92    source().close();
93  }
94
95  /**
96   * Returns a new response body that transmits {@code content}. If {@code
97   * contentType} is non-null and lacks a charset, this will use UTF-8.
98   */
99  public static ResponseBody create(MediaType contentType, String content) {
100    Charset charset = Util.UTF_8;
101    if (contentType != null) {
102      charset = contentType.charset();
103      if (charset == null) {
104        charset = Util.UTF_8;
105        contentType = MediaType.parse(contentType + "; charset=utf-8");
106      }
107    }
108    Buffer buffer = new Buffer().writeString(content, charset);
109    return create(contentType, buffer.size(), buffer);
110  }
111
112  /** Returns a new response body that transmits {@code content}. */
113  public static ResponseBody create(final MediaType contentType, byte[] content) {
114    Buffer buffer = new Buffer().write(content);
115    return create(contentType, content.length, buffer);
116  }
117
118  /** Returns a new response body that transmits {@code content}. */
119  public static ResponseBody create(
120      final MediaType contentType, final long contentLength, final BufferedSource content) {
121    if (content == null) throw new NullPointerException("source == null");
122    return new ResponseBody() {
123      @Override public MediaType contentType() {
124        return contentType;
125      }
126
127      @Override public long contentLength() {
128        return contentLength;
129      }
130
131      @Override public BufferedSource source() {
132        return content;
133      }
134    };
135  }
136}
137