OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 The Native Client 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 #include "url_io/web_resource_loader.h" | |
5 | |
6 #include <algorithm> | |
7 #include <cassert> | |
8 #include <stdio.h> | |
9 #include "ppapi/c/pp_errors.h" | |
10 #include "ppapi/cpp/module.h" | |
11 #include "ppapi/cpp/url_loader.h" | |
12 #include "ppapi/cpp/url_request_info.h" | |
13 #include "ppapi/cpp/url_response_info.h" | |
14 | |
15 #include "threading/scoped_mutex_lock.h" | |
16 #include "url_io/url_request.h" | |
17 | |
18 namespace { | |
19 // Skip white space in string str, starting at index pos. Returns the index of | |
20 // the first non white-space character found, or str.length() if only | |
21 // whitespace characters are found. | |
22 size_t SkipWhiteSpace(const std::string& str, size_t pos) { | |
23 size_t str_length = str.length(); | |
24 while (pos < str_length && ::isspace(str[pos])) { | |
25 ++pos; | |
26 } | |
27 return pos; | |
28 } | |
29 } // anonymous namespace | |
30 | |
31 namespace url_io { | |
32 | |
33 WebResourceLoader::WebResourceLoader(pp::Instance* instance, | |
34 WebResourceLoader::Delegate* delegate) | |
35 : factory_(this), | |
36 instance_(instance), | |
37 url_loader_(*instance), | |
38 state_(kStateNotConnected), | |
39 buffer_(NULL), | |
40 buffer_size_(0), | |
41 data_size_(0), | |
42 delegate_(delegate) { | |
43 assert(delegate != NULL); | |
44 pthread_mutex_init(&state_mutex_, NULL); | |
45 } | |
46 | |
47 void WebResourceLoader::CloseAndDeleteSelf() { | |
48 // Can't close & delete this instance here. Instead, we schedule a main-thread | |
49 // callback to delete it later. | |
50 pp::CompletionCallback cc(DeleteOnMainThread, this); | |
51 pp::Module::Get()->core()->CallOnMainThread(0, cc, PP_OK); | |
52 } | |
53 | |
54 WebResourceLoader::~WebResourceLoader() { | |
55 Close(); | |
56 pthread_mutex_destroy(&state_mutex_); | |
57 } | |
58 | |
59 void WebResourceLoader::LoadURL(const URLRequest& url_request) { | |
60 // TODO(gwink): add support for stream-to-file. | |
61 assert(!url_request.stream_to_file() && "Stream-to-file not implemented."); | |
62 // Check that there is no pending request. | |
63 assert(state_ == kStateNotConnected); | |
64 | |
65 if (state_ == kStateNotConnected) { | |
66 state_ = kStateConnecting; | |
67 pp::CompletionCallback cc = factory_.NewCallback( | |
68 &WebResourceLoader::InternalLoadURL, url_request); | |
69 pp::Module::Get()->core()->CallOnMainThread(0, cc, PP_OK); | |
70 } | |
71 } | |
72 | |
73 bool WebResourceLoader::ReadMoreData() { | |
74 pthread_mutex_lock(&state_mutex_); | |
75 assert(state_ == kStateInvokedDelegate || state_ == kStateSuspended); | |
76 | |
77 if (state_ == kStateSuspended) { | |
78 // Call resume on the main thread. | |
79 state_ = kStateDownloading; | |
80 pthread_mutex_unlock(&state_mutex_); | |
81 pp::CompletionCallback cc = factory_.NewCallback( | |
82 &WebResourceLoader::InternalReadMoreData); | |
83 pp::Module::Get()->core()->CallOnMainThread(0, cc, PP_OK); | |
84 return true; | |
85 } else if (state_ == kStateInvokedDelegate) { | |
86 // We're currently within a delegate function. Advance the state to | |
87 // will-read-more-data; that insures we'll schedule the next read op when | |
88 // the delegate returns control to this instance. | |
89 state_ = kStateWillReadMoreData; | |
90 pthread_mutex_unlock(&state_mutex_); | |
91 return true; | |
92 } | |
93 state_ = kStateNotConnected; | |
94 pthread_mutex_unlock(&state_mutex_); | |
95 return false; | |
96 } | |
97 | |
98 void WebResourceLoader::set_content_buffer(uint8_t* buffer, int32_t size) { | |
99 buffer_ = buffer; | |
100 buffer_size_ = size; | |
101 } | |
102 | |
103 const std::string WebResourceLoader::url() const { | |
104 pp::URLResponseInfo response = url_loader_.GetResponseInfo(); | |
105 return response.GetURL().AsString(); | |
106 } | |
107 | |
108 int32_t WebResourceLoader::GetHttpStatus() const { | |
109 return GetResponseInfo().GetStatusCode(); | |
110 } | |
111 | |
112 pp::URLResponseInfo WebResourceLoader::GetResponseInfo() const { | |
113 assert(pp::Module::Get()->core()->IsMainThread()); | |
114 pp::URLResponseInfo response = url_loader_.GetResponseInfo(); | |
115 assert(!response.is_null()); | |
116 return response; | |
117 } | |
118 | |
119 int32_t WebResourceLoader::GetContentLength() const { | |
120 HeaderDictionary::const_iterator it = | |
121 response_headers_.find("CONTENT-LENGTH"); | |
122 if (it == response_headers_.end()) { | |
123 return 0; | |
124 } else { | |
125 // Return content-length value string, converted to int. | |
126 return static_cast<int32_t>(atol((*it).second.c_str())); | |
127 } | |
128 } | |
129 | |
130 void WebResourceLoader::Close() { | |
131 assert(pp::Module::Get()->core()->IsMainThread()); | |
132 url_loader_.Close(); | |
133 buffer_ = NULL; | |
134 buffer_size_ = 0; | |
135 data_size_ = 0; | |
136 state_ = kStateNotConnected; | |
137 response_headers_.clear(); | |
138 } | |
139 | |
140 void WebResourceLoader::DoResponseInfoReceived(int32_t result) { | |
141 pthread_mutex_lock(&state_mutex_); | |
142 pp::URLResponseInfo response = url_loader_.GetResponseInfo(); | |
143 ParseHeaders(response); | |
144 state_ = kStateInvokedDelegate; | |
145 pthread_mutex_unlock(&state_mutex_); | |
146 delegate_->OnLoaderReceivedResponseInfo(this); | |
147 | |
148 pthread_mutex_lock(&state_mutex_); | |
149 if (state_ == kStateWillReadMoreData) { | |
150 state_ = kStateDownloading; | |
151 pthread_mutex_unlock(&state_mutex_); | |
152 pp::CompletionCallback cc = factory_.NewCallback( | |
153 &WebResourceLoader::InternalReadMoreData); | |
154 pp::Module::Get()->core()->CallOnMainThread(0, cc, PP_OK); | |
155 } else { | |
156 state_ = kStateSuspended; | |
157 pthread_mutex_unlock(&state_mutex_); | |
158 } | |
159 } | |
160 | |
161 void WebResourceLoader::DoDataReceived(int32_t result) { | |
162 pthread_mutex_lock(&state_mutex_); | |
163 if (result == 0) { | |
164 // No more data; the download completed successfully. | |
165 state_ = kStateNotConnected; | |
166 data_size_ = 0; | |
167 pthread_mutex_unlock(&state_mutex_); | |
168 delegate_->OnLoaderCompletedDownload(this); | |
169 delegate_->OnLoaderDone(this); | |
170 } else { | |
171 assert(result > 0); | |
172 // We received a block of data of size |result|. | |
173 data_size_ = result; | |
174 state_ = kStateInvokedDelegate; | |
175 pthread_mutex_unlock(&state_mutex_); | |
176 delegate_->OnLoaderReceivedData(this); | |
177 | |
178 pthread_mutex_lock(&state_mutex_); | |
179 if (state_ == kStateWillReadMoreData) { | |
180 state_ = kStateDownloading; | |
181 pthread_mutex_unlock(&state_mutex_); | |
182 pp::CompletionCallback cc = factory_.NewCallback( | |
183 &WebResourceLoader::InternalReadMoreData); | |
184 pp::Module::Get()->core()->CallOnMainThread(0, cc, PP_OK); | |
185 } else { | |
186 state_ = kStateSuspended; | |
187 pthread_mutex_unlock(&state_mutex_); | |
188 } | |
189 } | |
190 } | |
191 | |
192 void WebResourceLoader::DoDownloadError(int32_t result) { | |
193 pthread_mutex_lock(&state_mutex_); | |
194 state_ = kStateNotConnected; | |
195 pthread_mutex_unlock(&state_mutex_); | |
196 delegate_->OnLoaderError(result, this); | |
197 delegate_->OnLoaderDone(this); | |
198 } | |
199 | |
200 void WebResourceLoader::CompletionCallbackFunc(int32_t result, | |
201 CallbackOpCode op_code) { | |
202 // TODO(gwink): add support for follow-redirect. | |
203 if (result < 0) { | |
204 // A negative value indicates an error. | |
205 DoDownloadError(result); | |
206 } else if (result == PP_OK && state_ == kStateConnecting) { | |
207 // If we're not yet connected and result == PP_OK, then we received | |
208 // the http headers. | |
209 assert(op_code == kResponseInfoCallback); | |
210 DoResponseInfoReceived(result); | |
211 } else { | |
212 assert(op_code == kDataReceivedCallback); | |
213 DoDataReceived(result); | |
214 } | |
215 } | |
216 | |
217 pp::CompletionCallback WebResourceLoader::MakeCallback(CallbackOpCode op_code) { | |
218 return factory_.NewCallback( | |
219 &WebResourceLoader::CompletionCallbackFunc, op_code); | |
220 } | |
221 | |
222 void WebResourceLoader::InternalLoadURL(int32_t result, | |
223 const URLRequest& url_request) { | |
224 if (result == PP_OK) { | |
225 // Only usable from main plugin thread. | |
226 assert(pp::Module::Get()->core()->IsMainThread()); | |
227 | |
228 pp::CompletionCallback cc = MakeCallback(kResponseInfoCallback); | |
229 int32_t rv = url_loader_.Open(url_request.GetRequestInfo(instance_), cc); | |
230 assert(rv == PP_OK_COMPLETIONPENDING); | |
231 } | |
232 } | |
233 | |
234 void WebResourceLoader::InternalReadMoreData(int32_t result) { | |
235 if (result == PP_OK) { | |
236 // If a custom buffer has not been set or has been set to NULL, use the | |
237 // internal buffer. | |
238 if (buffer_ == NULL) { | |
239 buffer_ = internal_buffer_; | |
240 buffer_size_ = kInternalBufferSize; | |
241 } | |
242 // Get the next block of data. | |
243 pp::CompletionCallback cc = MakeCallback(kDataReceivedCallback); | |
244 int32_t rv = url_loader_.ReadResponseBody(buffer_, buffer_size_, cc); | |
245 assert(rv == PP_OK_COMPLETIONPENDING); | |
246 } | |
247 } | |
248 | |
249 void WebResourceLoader::ParseHeaders(pp::URLResponseInfo response) { | |
250 // The headers dictionary should be empty. | |
251 assert(response_headers_.empty()); | |
252 response_headers_.clear(); | |
253 | |
254 // Get the headers as a string. | |
255 pp::Var headers_var = response.GetHeaders(); | |
256 assert(headers_var.is_string()); | |
257 if (!headers_var.is_string()) | |
258 return; | |
259 std::string headers = headers_var.AsString(); | |
260 | |
261 // Parse headers. | |
262 size_t i_line = 0; | |
263 size_t headers_length = headers.length(); | |
264 std::string key; | |
265 while (i_line != std::string::npos && i_line < headers_length) { | |
266 // A line that starts with space or tab continues the previous line. | |
267 size_t i_value = i_line; | |
268 if (!::isspace(headers[i_line])) { | |
269 // Extract the key. | |
270 size_t i_key = i_line; | |
271 size_t i_separator = headers.find(':', i_key); | |
272 if (i_separator == std::string::npos) | |
273 return; | |
274 key = headers.substr(i_key, i_separator - i_key); | |
275 i_value = i_separator + 1; | |
276 // Convert key to all upper case. | |
277 std::transform(key.begin(), key.end(), key.begin(), ::toupper); | |
278 } | |
279 | |
280 // Extract the value. | |
281 i_value = SkipWhiteSpace(headers, i_value); | |
282 if (i_value >= headers_length) | |
283 return; | |
284 size_t i_eol = headers.find_first_of("\r\n", i_value); | |
285 std::string value = headers.substr( | |
286 i_value, (i_eol == std::string::npos)? i_eol : i_eol - i_value); | |
287 | |
288 | |
289 if (!value.empty() && !key.empty()) { | |
290 // Append the value to what's already in the map; that takes care of | |
291 // values that are spread over multiple lines. | |
292 response_headers_[key].append(value); | |
293 } | |
294 | |
295 // Skip crlf at eol. | |
296 while (i_eol < headers_length && | |
297 (headers[i_eol] == '\r' || headers[i_eol] == '\n')) { | |
298 ++i_eol; | |
299 } | |
300 i_line = i_eol; | |
301 } | |
302 } | |
303 | |
304 void WebResourceLoader::DeleteOnMainThread(void* user_data, int32_t err) { | |
305 WebResourceLoader* loader = static_cast<WebResourceLoader*>(user_data); | |
306 if (loader != NULL) { | |
307 loader->Close(); | |
308 delete loader; | |
309 } | |
310 } | |
311 | |
312 } // namespace url_io | |
OLD | NEW |