1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/streams/stream_url_request_job.h"
6
7#include "base/strings/string_number_conversions.h"
8#include "content/browser/streams/stream.h"
9#include "net/base/io_buffer.h"
10#include "net/base/net_errors.h"
11#include "net/http/http_byte_range.h"
12#include "net/http/http_response_headers.h"
13#include "net/http/http_response_info.h"
14#include "net/http/http_util.h"
15#include "net/url_request/url_request.h"
16
17namespace content {
18
19StreamURLRequestJob::StreamURLRequestJob(
20    net::URLRequest* request,
21    net::NetworkDelegate* network_delegate,
22    scoped_refptr<Stream> stream)
23    : net::URLRequestJob(request, network_delegate),
24      weak_factory_(this),
25      stream_(stream),
26      headers_set_(false),
27      pending_buffer_size_(0),
28      total_bytes_read_(0),
29      max_range_(0),
30      request_failed_(false) {
31  DCHECK(stream_.get());
32  stream_->SetReadObserver(this);
33}
34
35StreamURLRequestJob::~StreamURLRequestJob() {
36  ClearStream();
37}
38
39void StreamURLRequestJob::OnDataAvailable(Stream* stream) {
40  // Clear the IO_PENDING status.
41  SetStatus(net::URLRequestStatus());
42  if (pending_buffer_.get()) {
43    int bytes_read;
44    stream_->ReadRawData(
45        pending_buffer_.get(), pending_buffer_size_, &bytes_read);
46
47    // Clear the buffers before notifying the read is complete, so that it is
48    // safe for the observer to read.
49    pending_buffer_ = NULL;
50    pending_buffer_size_ = 0;
51
52    total_bytes_read_ += bytes_read;
53    NotifyReadComplete(bytes_read);
54  }
55}
56
57// net::URLRequestJob methods.
58void StreamURLRequestJob::Start() {
59  // Continue asynchronously.
60  base::MessageLoop::current()->PostTask(
61      FROM_HERE,
62      base::Bind(&StreamURLRequestJob::DidStart, weak_factory_.GetWeakPtr()));
63}
64
65void StreamURLRequestJob::Kill() {
66  net::URLRequestJob::Kill();
67  weak_factory_.InvalidateWeakPtrs();
68  ClearStream();
69}
70
71bool StreamURLRequestJob::ReadRawData(net::IOBuffer* buf,
72                                      int buf_size,
73                                      int* bytes_read) {
74  if (request_failed_)
75    return true;
76
77  DCHECK(bytes_read);
78  int to_read = buf_size;
79  if (max_range_ && to_read) {
80    if (to_read + total_bytes_read_ > max_range_)
81      to_read = max_range_ - total_bytes_read_;
82
83    if (to_read <= 0) {
84      *bytes_read = 0;
85      return true;
86    }
87  }
88
89  switch (stream_->ReadRawData(buf, to_read, bytes_read)) {
90    case Stream::STREAM_HAS_DATA:
91    case Stream::STREAM_COMPLETE:
92      total_bytes_read_ += *bytes_read;
93      return true;
94    case Stream::STREAM_EMPTY:
95      pending_buffer_ = buf;
96      pending_buffer_size_ = to_read;
97      SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
98      return false;
99  }
100  NOTREACHED();
101  return false;
102}
103
104bool StreamURLRequestJob::GetMimeType(std::string* mime_type) const {
105  if (!response_info_)
106    return false;
107
108  // TODO(zork): Support registered MIME types if needed.
109  return response_info_->headers->GetMimeType(mime_type);
110}
111
112void StreamURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
113  if (response_info_)
114    *info = *response_info_;
115}
116
117int StreamURLRequestJob::GetResponseCode() const {
118  if (!response_info_)
119    return -1;
120
121  return response_info_->headers->response_code();
122}
123
124void StreamURLRequestJob::SetExtraRequestHeaders(
125    const net::HttpRequestHeaders& headers) {
126  std::string range_header;
127  if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) {
128    std::vector<net::HttpByteRange> ranges;
129    if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
130      if (ranges.size() == 1) {
131        // Streams don't support seeking, so a non-zero starting position
132        // doesn't make sense.
133        if (ranges[0].first_byte_position() == 0) {
134          max_range_ = ranges[0].last_byte_position() + 1;
135        } else {
136          NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED);
137          return;
138        }
139      } else {
140        NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED);
141        return;
142      }
143    }
144  }
145}
146
147void StreamURLRequestJob::DidStart() {
148  // We only support GET request.
149  if (request()->method() != "GET") {
150    NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED);
151    return;
152  }
153
154  HeadersCompleted(net::HTTP_OK);
155}
156
157void StreamURLRequestJob::NotifyFailure(int error_code) {
158  request_failed_ = true;
159
160  // If we already return the headers on success, we can't change the headers
161  // now. Instead, we just error out.
162  if (headers_set_) {
163    NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
164                                     error_code));
165    return;
166  }
167
168  // TODO(zork): Share these with BlobURLRequestJob.
169  net::HttpStatusCode status_code = net::HTTP_INTERNAL_SERVER_ERROR;
170  std::string status_txt;
171  switch (error_code) {
172    case net::ERR_ACCESS_DENIED:
173      status_code = net::HTTP_FORBIDDEN;
174      break;
175    case net::ERR_FILE_NOT_FOUND:
176      status_code = net::HTTP_NOT_FOUND;
177      break;
178    case net::ERR_METHOD_NOT_SUPPORTED:
179      status_code = net::HTTP_METHOD_NOT_ALLOWED;
180      break;
181    case net::ERR_FAILED:
182      break;
183    default:
184      DCHECK(false);
185      break;
186  }
187  HeadersCompleted(status_code);
188}
189
190void StreamURLRequestJob::HeadersCompleted(net::HttpStatusCode status_code) {
191  std::string status("HTTP/1.1 ");
192  status.append(base::IntToString(status_code));
193  status.append(" ");
194  status.append(net::GetHttpReasonPhrase(status_code));
195  status.append("\0\0", 2);
196  net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status);
197
198  if (status_code == net::HTTP_OK) {
199    std::string content_type_header(net::HttpRequestHeaders::kContentType);
200    content_type_header.append(": ");
201    content_type_header.append("plain/text");
202    headers->AddHeader(content_type_header);
203  }
204
205  response_info_.reset(new net::HttpResponseInfo());
206  response_info_->headers = headers;
207
208  headers_set_ = true;
209
210  NotifyHeadersComplete();
211}
212
213void StreamURLRequestJob::ClearStream() {
214  if (stream_.get()) {
215    stream_->RemoveReadObserver(this);
216    stream_ = NULL;
217  }
218}
219
220}  // namespace content
221