Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1112)

Side by Side Diff: net/url_request/url_request_inet_job.cc

Issue 523034: Remove WinInet FTP code. (Closed) Base URL: http://src.chromium.org/svn/trunk/src/
Patch Set: Created 10 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « net/url_request/url_request_inet_job.h ('k') | net/url_request/url_request_job_manager.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2006-2008 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 "net/url_request/url_request_inet_job.h"
6
7 #include <algorithm>
8
9 #include "base/message_loop.h"
10 #include "base/string_util.h"
11 #include "googleurl/src/gurl.h"
12 #include "net/base/auth.h"
13 #include "net/base/io_buffer.h"
14 #include "net/base/net_errors.h"
15 #include "net/base/net_util.h"
16 #include "net/base/wininet_util.h"
17 #include "net/url_request/url_request_context.h"
18 #include "net/url_request/url_request_error_job.h"
19 #include "net/url_request/url_request_ftp_job.h"
20 #include "net/url_request/url_request_job_metrics.h"
21 #include "net/url_request/url_request_job_tracker.h"
22
23 using net::WinInetUtil;
24
25 //
26 // HOW ASYNC IO WORKS
27 //
28 // The URLRequestInet* classes are now fully asynchronous. This means that
29 // all IO operations pass buffers into WinInet, and as WinInet completes those
30 // IO requests, it will fill the buffer, and then callback to the client.
31 // Asynchronous IO Operations include:
32 // HttpSendRequestEx
33 // InternetWriteFile
34 // HttpEndRequest
35 // InternetOpenUrl
36 // InternetReadFile (for FTP)
37 // InternetReadFileEx (for HTTP)
38 // InternetCloseHandle
39 //
40 // To understand how this works, you need to understand the basic class
41 // hierarchy for the URLRequestJob classes:
42 //
43 // URLRequestJob
44 // |
45 // +--------------+-------------------+
46 // | |
47 // (Other Job Types) URLRequestInetJob
48 // e.g. | |
49 // URLRequestFileJob URLRequestFtpJob URLRequestHttpJob
50 // |
51 // URLRequestHttpUploadJob
52 //
53 //
54 // To make this work, each URLRequestInetJob has a virtual method called
55 // OnIOComplete(). If a derived URLRequestInetJob class issues
56 // an asynchronous IO, it must override the OnIOComplete method
57 // to handle the IO completion. Once it has overridden this method,
58 // *all* asynchronous IO completions will come to this method, even
59 // those asynchronous IOs which may have been issued by a base class.
60 // For example, URLRequestInetJob has methods which Read from the
61 // connection asynchronously. Once URLRequestHttpJob overrides
62 // OnIOComplete (so that it can receive its own async IO callbacks)
63 // it will also receive the URLRequestInetJob async IO callbacks. To
64 // make this work, the derived class must track its own state, and call
65 // the base class' version of OnIOComplete if appropriate.
66 //
67
68 COMPILE_ASSERT(
69 sizeof(URLRequestInetJob::AsyncResult) == sizeof(INTERNET_ASYNC_RESULT),
70 async_result_inconsistent_size);
71
72 HINTERNET URLRequestInetJob::the_internet_ = NULL;
73 #ifndef NDEBUG
74 MessageLoop* URLRequestInetJob::my_message_loop_ = NULL;
75 #endif
76
77 URLRequestInetJob::URLRequestInetJob(URLRequest* request)
78 : URLRequestJob(request),
79 connection_handle_(NULL),
80 request_handle_(NULL),
81 last_error_(ERROR_SUCCESS),
82 is_waiting_(false),
83 read_in_progress_(false),
84 loop_(MessageLoop::current()) {
85 // TODO(darin): we should re-create the internet if the UA string changes,
86 // but we have to be careful about existing users of this internet.
87 if (!the_internet_) {
88 InitializeTheInternet(request->context() ?
89 request->context()->GetUserAgent(GURL()) : std::string());
90 }
91 #ifndef NDEBUG
92 DCHECK(MessageLoop::current() == my_message_loop_) <<
93 "All URLRequests should happen on the same thread";
94 #endif
95 }
96
97 URLRequestInetJob::~URLRequestInetJob() {
98 DCHECK(!request_) << "request should be detached at this point";
99
100 // The connections may have already been cleaned up. It is ok to call
101 // CleanupConnection again to make sure the resource is properly released.
102 // See bug 684997.
103 CleanupConnection();
104 }
105
106 void URLRequestInetJob::Kill() {
107 CleanupConnection();
108
109 {
110 AutoLock locked(loop_lock_);
111 loop_ = NULL;
112 }
113
114 // Dispatch the NotifyDone message to the URLRequest
115 URLRequestJob::Kill();
116 }
117
118 void URLRequestInetJob::SetAuth(const std::wstring& username,
119 const std::wstring& password) {
120 DCHECK((proxy_auth_ && proxy_auth_->state == net::AUTH_STATE_NEED_AUTH) ||
121 (server_auth_ && server_auth_->state == net::AUTH_STATE_NEED_AUTH));
122
123 // Proxy gets set first, then WWW.
124 net::AuthData* auth =
125 (proxy_auth_ && proxy_auth_->state == net::AUTH_STATE_NEED_AUTH ?
126 proxy_auth_.get() : server_auth_.get());
127
128 if (auth) {
129 auth->state = net::AUTH_STATE_HAVE_AUTH;
130 auth->username = username;
131 auth->password = password;
132 }
133
134 // Resend the request with the new username and password.
135 // Do this asynchronously in case we were called from within a
136 // NotifyDataAvailable callback.
137 // TODO(mpcomplete): hmm... is it possible 'this' gets deleted before the task
138 // is run?
139 OnSetAuth();
140 }
141
142 void URLRequestInetJob::CancelAuth() {
143 DCHECK((proxy_auth_ && proxy_auth_->state == net::AUTH_STATE_NEED_AUTH) ||
144 (server_auth_ && server_auth_->state == net::AUTH_STATE_NEED_AUTH));
145
146 // Proxy gets set first, then WWW.
147 net::AuthData* auth =
148 (proxy_auth_ && proxy_auth_->state == net::AUTH_STATE_NEED_AUTH ?
149 proxy_auth_.get() : server_auth_.get());
150
151 if (auth) {
152 auth->state = net::AUTH_STATE_CANCELED;
153 }
154
155 // Once the auth is cancelled, we proceed with the request as though
156 // there were no auth. So, send the OnResponseStarted. Schedule this
157 // for later so that we don't cause any recursing into the caller
158 // as a result of this call.
159 OnCancelAuth();
160 }
161
162 void URLRequestInetJob::OnIOComplete(const AsyncResult& result) {
163 URLRequestStatus status;
164
165 if (read_in_progress_) {
166 read_in_progress_ = false;
167 int bytes_read = 0;
168 if (GetReadBytes(result, &bytes_read)) {
169 SetStatus(status);
170 if (bytes_read == 0) {
171 NotifyDone(status);
172 CleanupConnection();
173 }
174 } else {
175 bytes_read = -1;
176 URLRequestStatus status;
177 status.set_status(URLRequestStatus::FAILED);
178 status.set_os_error(WinInetUtil::OSErrorToNetError(result.dwError));
179 NotifyDone(status);
180 CleanupConnection();
181 }
182 NotifyReadComplete(bytes_read);
183 } else {
184 // If we get here, an IO is completing which we didn't
185 // start or we lost track of our state.
186 NOTREACHED();
187 }
188 }
189
190 bool URLRequestInetJob::ReadRawData(net::IOBuffer* dest, int dest_size,
191 int *bytes_read) {
192 if (is_done())
193 return 0;
194
195 DCHECK_NE(dest_size, 0);
196 DCHECK_NE(bytes_read, (int*)NULL);
197 DCHECK(!read_in_progress_);
198
199 *bytes_read = 0;
200
201 int result = CallInternetRead(dest->data(), dest_size, bytes_read);
202 if (result == ERROR_SUCCESS) {
203 DLOG(INFO) << "read " << *bytes_read << " bytes";
204 if (*bytes_read == 0)
205 CleanupConnection(); // finished reading all the data
206 return true;
207 }
208
209 if (ProcessRequestError(result))
210 read_in_progress_ = true;
211
212 // Whether we had an error or the request is pending.
213 // Both of these cases return false.
214 return false;
215 }
216
217 void URLRequestInetJob::CallOnIOComplete(const AsyncResult& result) {
218 // It's important to clear this flag before calling OnIOComplete
219 is_waiting_ = false;
220
221 // the job could have completed with an error while the message was pending
222 if (!is_done()) {
223 // Verify that our status is currently set to IO_PENDING and
224 // reset it on success.
225 DCHECK(GetStatus().is_io_pending());
226 if (result.dwResult && result.dwError == 0)
227 SetStatus(URLRequestStatus());
228
229 OnIOComplete(result);
230 }
231
232 Release(); // may destroy self if last reference
233 }
234
235 bool URLRequestInetJob::ProcessRequestError(int error) {
236 if (error == ERROR_IO_PENDING) {
237 DLOG(INFO) << "waiting for WinInet call to complete";
238 AddRef(); // balanced in CallOnIOComplete
239 is_waiting_ = true;
240 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
241 return true;
242 }
243 DLOG(ERROR) << "WinInet call failed: " << error;
244 CleanupConnection();
245 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
246 WinInetUtil::OSErrorToNetError(error)));
247 return false;
248 }
249
250 void URLRequestInetJob::CleanupConnection() {
251 if (!request_handle_ && !connection_handle_)
252 return; // nothing to clean up
253
254 if (request_handle_) {
255 CleanupHandle(request_handle_);
256 request_handle_ = NULL;
257 }
258 if (connection_handle_) {
259 CleanupHandle(connection_handle_);
260 connection_handle_ = NULL;
261 }
262 }
263
264 void URLRequestInetJob::CleanupHandle(HINTERNET handle) {
265 // We no longer need notifications from this connection.
266 InternetSetStatusCallback(handle, NULL);
267
268 if (!InternetCloseHandle(handle)) {
269 // InternetCloseHandle is evil. The documentation specifies that it
270 // either succeeds immediately or returns ERROR_IO_PENDING if there is
271 // something outstanding, in which case the close will happen automagically
272 // later. In either of these cases, it will call us back with
273 // INTERNET_STATUS_HANDLE_CLOSING (because we set up the async callbacks)
274 // and we simply do nothing for the message.
275 //
276 // However, sometimes it also seems to fail with ERROR_INVALID_HANDLE.
277 // This seems to happen when we cancel before it has called us back with
278 // data. For example, if we cancel during DNS resolution or while waiting
279 // for a slow server.
280 //
281 // Our speculation is that in these cases WinInet creates a handle for
282 // us with an internal structure, but that the driver has not yet called
283 // it back with a "real" handle (the driver level is probably what
284 // generates IO_PENDING). The driver has not yet specified a handle, which
285 // causes WinInet to barf.
286 //
287 // However, in this case, the cancel seems to work. The TCP connection is
288 // closed and we still get a callback that the handle is being closed. Yay.
289 //
290 // We assert that the error is either of these two because we aren't sure
291 // if any other error values could also indicate this bogus condition, and
292 // we want to notice if we do something wrong that causes a real error.
293 DWORD last_error = GetLastError();
294 DCHECK(last_error == ERROR_INVALID_HANDLE) <<
295 "Unknown error when closing handle, possibly leaking job";
296 if (ERROR_IO_PENDING == last_error) {
297 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
298
299 AsyncResult result;
300 result.dwError = ERROR_INTERNET_CONNECTION_ABORTED;
301 result.dwResult = reinterpret_cast<DWORD_PTR>(handle);
302 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
303 this, &URLRequestInetJob::CallOnIOComplete, result));
304 }
305 }
306 }
307
308 // static
309 HINTERNET URLRequestInetJob::GetTheInternet() {
310 return the_internet_;
311 }
312
313 // static
314 void URLRequestInetJob::InitializeTheInternet(const std::string& user_agent) {
315 // Hack attack. We are hitting a deadlock in wininet deinitialization.
316 // What is happening is that when we deinitialize, FreeLibrary will be
317 // called on wininet. The loader lock is held, and wininet!DllMain is
318 // called. The problem is that wininet tries to do a bunch of cleanup
319 // in their DllMain, including calling ICAsyncThread::~ICASyncThread.
320 // This tries to shutdown the "select thread", and then does a
321 // WaitForSingleObject on the thread with a 5 sec timeout. However the
322 // thread they are waiting for cannot exit because the thread shutdown
323 // routine (LdrShutdownThread) is trying to acquire the loader lock.
324 // This causes chrome.exe to hang for 5 seconds on shutdown before the
325 // process will exit. Making sure we close our wininet handles did not help.
326 //
327 // Since DLLs are reference counted, we inflate the reference count on
328 // wininet so that it will never be deinitialized :)
329 LoadLibraryA("wininet");
330
331 the_internet_ = InternetOpenA(user_agent.c_str(),
332 INTERNET_OPEN_TYPE_PRECONFIG,
333 NULL, // no proxy override
334 NULL, // no proxy bypass list
335 INTERNET_FLAG_ASYNC);
336 InternetSetStatusCallback(the_internet_, URLRequestStatusCallback);
337
338 // Keep track of this message loop so we can catch callers who don't make
339 // requests on the same thread. Only do this in debug mode; in release mode
340 // my_message_loop_ doesn't exist.
341 #ifndef NDEBUG
342 DCHECK(!my_message_loop_) << "InitializeTheInternet() called twice";
343 DCHECK(my_message_loop_ = MessageLoop::current());
344 #endif
345 }
346
347 // static
348 void CALLBACK URLRequestInetJob::URLRequestStatusCallback(
349 HINTERNET handle, DWORD_PTR job_id, DWORD status, LPVOID status_info,
350 DWORD status_info_len) {
351 switch (status) {
352 case INTERNET_STATUS_REQUEST_COMPLETE: {
353 URLRequestInetJob* job = reinterpret_cast<URLRequestInetJob*>(job_id);
354
355 DCHECK(status_info_len == sizeof(AsyncResult));
356 AsyncResult* result = static_cast<AsyncResult*>(status_info);
357
358 AutoLock locked(job->loop_lock_);
359 if (job->loop_) {
360 job->loop_->PostTask(FROM_HERE, NewRunnableMethod(
361 job, &URLRequestInetJob::CallOnIOComplete, *result));
362 }
363 break;
364 }
365 case INTERNET_STATUS_USER_INPUT_REQUIRED:
366 case INTERNET_STATUS_STATE_CHANGE:
367 // TODO(darin): This is probably a security problem. Do something better.
368 ResumeSuspendedDownload(handle, 0);
369 break;
370 }
371 }
OLDNEW
« no previous file with comments | « net/url_request/url_request_inet_job.h ('k') | net/url_request/url_request_job_manager.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698