| OLD | NEW |
| (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_ftp_job.h" | |
| 6 | |
| 7 #include <windows.h> | |
| 8 #include <wininet.h> | |
| 9 | |
| 10 #include "base/message_loop.h" | |
| 11 #include "base/string_util.h" | |
| 12 #include "base/sys_string_conversions.h" | |
| 13 #include "base/time.h" | |
| 14 #include "net/base/auth.h" | |
| 15 #include "net/base/escape.h" | |
| 16 #include "net/base/load_flags.h" | |
| 17 #include "net/base/net_errors.h" | |
| 18 #include "net/base/net_util.h" | |
| 19 #include "net/base/wininet_util.h" | |
| 20 #include "net/url_request/url_request.h" | |
| 21 #include "net/url_request/url_request_context.h" | |
| 22 #include "net/url_request/url_request_error_job.h" | |
| 23 #include "net/url_request/url_request_new_ftp_job.h" | |
| 24 | |
| 25 using std::string; | |
| 26 | |
| 27 using net::WinInetUtil; | |
| 28 | |
| 29 // When building the directory listing, the period to wait before notifying | |
| 30 // the parent class that we wrote the data. | |
| 31 #define kFtpBufferTimeMs 50 | |
| 32 | |
| 33 static bool UnescapeAndValidatePath(const URLRequest* request, | |
| 34 std::string* unescaped_path) { | |
| 35 // Path in GURL is %-encoded UTF-8. FTP servers do not | |
| 36 // understand %-escaped path so that we have to unescape leading to an | |
| 37 // unescaped UTF-8 path. Then, the presence of NULL, CR and LF is checked | |
| 38 // because they're not allowed in FTP. | |
| 39 // TODO(jungshik) Even though RFC 2640 specifies that UTF-8 be used. | |
| 40 // There are many FTP servers that use legacy encodings. For them, | |
| 41 // we need to identify the encoding and convert to that encoding. | |
| 42 static const std::string kInvalidChars("\x00\x0d\x0a", 3); | |
| 43 *unescaped_path = UnescapeURLComponent(request->url().path(), | |
| 44 UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS); | |
| 45 if (unescaped_path->find_first_of(kInvalidChars) != std::string::npos) { | |
| 46 SetLastError(ERROR_INTERNET_INVALID_URL); | |
| 47 // GURL path should not contain '%00' which is NULL(0x00) when unescaped. | |
| 48 // URLRequestFtpJob should not have been invoked for an invalid GURL. | |
| 49 DCHECK(unescaped_path->find(std::string("\x00", 1)) == std::string::npos) << | |
| 50 "Path should not contain %00."; | |
| 51 return false; | |
| 52 } | |
| 53 return true; | |
| 54 } | |
| 55 | |
| 56 // static | |
| 57 URLRequestJob* URLRequestFtpJob::Factory(URLRequest* request, | |
| 58 const std::string &scheme) { | |
| 59 // Checking whether we are using new or old FTP implementation. | |
| 60 if (request->context() && request->context()->ftp_transaction_factory()) | |
| 61 return URLRequestNewFtpJob::Factory(request, scheme); | |
| 62 | |
| 63 DCHECK(scheme == "ftp"); | |
| 64 | |
| 65 int port = request->url().IntPort(); | |
| 66 | |
| 67 if (request->url().has_port() && | |
| 68 !net::IsPortAllowedByFtp(port) && !net::IsPortAllowedByOverride(port)) | |
| 69 return new URLRequestErrorJob(request, net::ERR_UNSAFE_PORT); | |
| 70 | |
| 71 return new URLRequestFtpJob(request); | |
| 72 } | |
| 73 | |
| 74 URLRequestFtpJob::URLRequestFtpJob(URLRequest* request) | |
| 75 : URLRequestInetJob(request), state_(START), is_directory_(false), | |
| 76 dest_(NULL), dest_size_(0) { | |
| 77 } | |
| 78 | |
| 79 URLRequestFtpJob::~URLRequestFtpJob() { | |
| 80 } | |
| 81 | |
| 82 void URLRequestFtpJob::Start() { | |
| 83 GURL parts(request_->url()); | |
| 84 const std::string& scheme = parts.scheme(); | |
| 85 | |
| 86 // We should only be dealing with FTP at this point: | |
| 87 DCHECK(LowerCaseEqualsASCII(scheme, "ftp")); | |
| 88 | |
| 89 SendRequest(); | |
| 90 } | |
| 91 | |
| 92 bool URLRequestFtpJob::GetMimeType(std::string* mime_type) const { | |
| 93 if (!is_directory_) | |
| 94 return false; | |
| 95 | |
| 96 mime_type->assign("text/html"); | |
| 97 return true; | |
| 98 } | |
| 99 | |
| 100 void URLRequestFtpJob::OnCancelAuth() { | |
| 101 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( | |
| 102 this, &URLRequestFtpJob::ContinueNotifyHeadersComplete)); | |
| 103 } | |
| 104 | |
| 105 void URLRequestFtpJob::OnSetAuth() { | |
| 106 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( | |
| 107 this, &URLRequestFtpJob::SendRequest)); | |
| 108 } | |
| 109 | |
| 110 void URLRequestFtpJob::SendRequest() { | |
| 111 state_ = CONNECTING; | |
| 112 | |
| 113 DWORD flags = | |
| 114 INTERNET_FLAG_KEEP_CONNECTION | | |
| 115 INTERNET_FLAG_EXISTING_CONNECT | | |
| 116 INTERNET_FLAG_PASSIVE | | |
| 117 INTERNET_FLAG_RAW_DATA; | |
| 118 | |
| 119 // It doesn't make sense to ask for both a cache validation and a | |
| 120 // reload at the same time. | |
| 121 DCHECK(!((request_->load_flags() & net::LOAD_VALIDATE_CACHE) && | |
| 122 (request_->load_flags() & net::LOAD_BYPASS_CACHE))); | |
| 123 | |
| 124 if (request_->load_flags() & net::LOAD_BYPASS_CACHE) | |
| 125 flags |= INTERNET_FLAG_RELOAD; | |
| 126 | |
| 127 // Apply authentication if we have any, otherwise authenticate | |
| 128 // according to FTP defaults. (See InternetConnect documentation.) | |
| 129 // First, check if we have auth in cache, then check URL. | |
| 130 // That way a user can re-enter credentials, and we'll try with their | |
| 131 // latest input rather than always trying what they specified | |
| 132 // in the url (if anything). | |
| 133 string username, password; | |
| 134 bool have_auth = false; | |
| 135 if (server_auth_ && server_auth_->state == net::AUTH_STATE_HAVE_AUTH) { | |
| 136 // Add auth info to cache | |
| 137 have_auth = true; | |
| 138 username = WideToUTF8(server_auth_->username); | |
| 139 password = WideToUTF8(server_auth_->password); | |
| 140 request_->context()->ftp_auth_cache()->Add(request_->url().GetOrigin(), | |
| 141 server_auth_->username, | |
| 142 server_auth_->password); | |
| 143 } else { | |
| 144 if (request_->url().has_username()) { | |
| 145 username = request_->url().username(); | |
| 146 password = request_->url().has_password() ? request_->url().password() : | |
| 147 ""; | |
| 148 have_auth = true; | |
| 149 } | |
| 150 } | |
| 151 | |
| 152 int port = request_->url().has_port() ? | |
| 153 request_->url().IntPort() : INTERNET_DEFAULT_FTP_PORT; | |
| 154 | |
| 155 connection_handle_ = InternetConnectA( | |
| 156 GetTheInternet(), | |
| 157 request_->url().HostNoBrackets().c_str(), | |
| 158 port, | |
| 159 have_auth ? username.c_str() : NULL, | |
| 160 have_auth ? password.c_str() : NULL, | |
| 161 INTERNET_SERVICE_FTP, flags, | |
| 162 reinterpret_cast<DWORD_PTR>(this)); | |
| 163 | |
| 164 if (connection_handle_) { | |
| 165 OnConnect(); | |
| 166 } else { | |
| 167 ProcessRequestError(GetLastError()); | |
| 168 } | |
| 169 } | |
| 170 | |
| 171 void URLRequestFtpJob::OnIOComplete(const AsyncResult& result) { | |
| 172 if (state_ == CONNECTING) { | |
| 173 switch (result.dwError) { | |
| 174 case ERROR_NO_MORE_FILES: | |
| 175 // url is an empty directory | |
| 176 OnStartDirectoryTraversal(); | |
| 177 OnFinishDirectoryTraversal(); | |
| 178 return; | |
| 179 case ERROR_INTERNET_LOGIN_FAILURE: | |
| 180 // fall through | |
| 181 case ERROR_INTERNET_INCORRECT_USER_NAME: | |
| 182 // fall through | |
| 183 case ERROR_INTERNET_INCORRECT_PASSWORD: { | |
| 184 GURL origin = request_->url().GetOrigin(); | |
| 185 if (server_auth_ != NULL && | |
| 186 server_auth_->state == net::AUTH_STATE_HAVE_AUTH) { | |
| 187 request_->context()->ftp_auth_cache()->Remove(origin, | |
| 188 server_auth_->username, | |
| 189 server_auth_->password); | |
| 190 } else { | |
| 191 server_auth_ = new net::AuthData(); | |
| 192 } | |
| 193 server_auth_->state = net::AUTH_STATE_NEED_AUTH; | |
| 194 | |
| 195 net::FtpAuthCache::Entry* cached_auth = | |
| 196 request_->context()->ftp_auth_cache()->Lookup(origin); | |
| 197 | |
| 198 if (cached_auth) { | |
| 199 // Retry using cached auth data. | |
| 200 SetAuth(cached_auth->username, cached_auth->password); | |
| 201 } else { | |
| 202 // The io completed fine, the error was due to invalid auth. | |
| 203 SetStatus(URLRequestStatus()); | |
| 204 | |
| 205 // Prompt for a username/password. | |
| 206 NotifyHeadersComplete(); | |
| 207 } | |
| 208 return; | |
| 209 } | |
| 210 case ERROR_SUCCESS: | |
| 211 connection_handle_ = (HINTERNET)result.dwResult; | |
| 212 OnConnect(); | |
| 213 return; | |
| 214 case ERROR_INTERNET_EXTENDED_ERROR: { | |
| 215 DWORD extended_err(ERROR_SUCCESS); | |
| 216 DWORD size = 1; | |
| 217 char buffer[1]; | |
| 218 if (!InternetGetLastResponseInfoA(&extended_err, buffer, &size)) | |
| 219 // We don't care about the error text here, so the only acceptable | |
| 220 // error is one regarding insufficient buffer length. | |
| 221 DCHECK(GetLastError() == ERROR_INSUFFICIENT_BUFFER); | |
| 222 if (extended_err != ERROR_SUCCESS) { | |
| 223 CleanupConnection(); | |
| 224 NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, | |
| 225 WinInetUtil::OSErrorToNetError(extended_err))); | |
| 226 return; | |
| 227 } | |
| 228 // Fall through in the case we saw ERROR_INTERNET_EXTENDED_ERROR but | |
| 229 // InternetGetLastResponseInfo gave us no additional information. | |
| 230 } | |
| 231 default: | |
| 232 CleanupConnection(); | |
| 233 NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, | |
| 234 WinInetUtil::OSErrorToNetError(result.dwError))); | |
| 235 return; | |
| 236 } | |
| 237 } else if (state_ == SETTING_CUR_DIRECTORY) { | |
| 238 OnSetCurrentDirectory(result.dwError); | |
| 239 } else if (state_ == FINDING_FIRST_FILE) { | |
| 240 // We don't fail here if result.dwError != ERROR_SUCCESS because | |
| 241 // getting an error here doesn't always mean the file is not found. | |
| 242 // FindFirstFileA() issue a LIST command and may fail on some | |
| 243 // ftp server when the requested object is a file. So ERROR_NO_MORE_FILES | |
| 244 // from FindFirstFileA() is not a reliable criteria for valid path | |
| 245 // or not, we should proceed optimistically by getting the file handle. | |
| 246 if (result.dwError != ERROR_SUCCESS && | |
| 247 result.dwError != ERROR_NO_MORE_FILES) { | |
| 248 DWORD result_error = result.dwError; | |
| 249 CleanupConnection(); | |
| 250 NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, | |
| 251 WinInetUtil::OSErrorToNetError(result_error))); | |
| 252 return; | |
| 253 } | |
| 254 request_handle_ = (HINTERNET)result.dwResult; | |
| 255 OnFindFirstFile(result.dwError); | |
| 256 } else if (state_ == GETTING_DIRECTORY) { | |
| 257 OnFindFile(result.dwError); | |
| 258 } else if (state_ == GETTING_FILE_HANDLE) { | |
| 259 if (result.dwError != ERROR_SUCCESS) { | |
| 260 CleanupConnection(); | |
| 261 NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, | |
| 262 WinInetUtil::OSErrorToNetError(result.dwError))); | |
| 263 return; | |
| 264 } | |
| 265 // start reading file contents | |
| 266 state_ = GETTING_FILE; | |
| 267 request_handle_ = (HINTERNET)result.dwResult; | |
| 268 NotifyHeadersComplete(); | |
| 269 } else { | |
| 270 // we don't have IO outstanding. Pass to our base class. | |
| 271 URLRequestInetJob::OnIOComplete(result); | |
| 272 } | |
| 273 } | |
| 274 | |
| 275 bool URLRequestFtpJob::NeedsAuth() { | |
| 276 // Note that we only have to worry about cases where an actual FTP server | |
| 277 // requires auth (and not a proxy), because connecting to FTP via proxy | |
| 278 // effectively means the browser communicates via HTTP, and uses HTTP's | |
| 279 // Proxy-Authenticate protocol when proxy servers require auth. | |
| 280 return server_auth_ && server_auth_->state == net::AUTH_STATE_NEED_AUTH; | |
| 281 } | |
| 282 | |
| 283 void URLRequestFtpJob::GetAuthChallengeInfo( | |
| 284 scoped_refptr<net::AuthChallengeInfo>* result) { | |
| 285 DCHECK((server_auth_ != NULL) && | |
| 286 (server_auth_->state == net::AUTH_STATE_NEED_AUTH)); | |
| 287 scoped_refptr<net::AuthChallengeInfo> auth_info = new net::AuthChallengeInfo; | |
| 288 auth_info->is_proxy = false; | |
| 289 auth_info->host_and_port = ASCIIToWide( | |
| 290 net::GetHostAndPort(request_->url())); | |
| 291 auth_info->scheme = L""; | |
| 292 auth_info->realm = L""; | |
| 293 result->swap(auth_info); | |
| 294 } | |
| 295 | |
| 296 void URLRequestFtpJob::OnConnect() { | |
| 297 DCHECK_EQ(state_, CONNECTING); | |
| 298 | |
| 299 state_ = SETTING_CUR_DIRECTORY; | |
| 300 // Setting the directory lets us determine if the URL is a file, | |
| 301 // and also keeps the working directory for the FTP session in sync | |
| 302 // with what is being displayed in the browser. | |
| 303 if (request_->url().has_path()) { | |
| 304 std::string unescaped_path; | |
| 305 if (UnescapeAndValidatePath(request_, &unescaped_path) && | |
| 306 FtpSetCurrentDirectoryA(connection_handle_, | |
| 307 unescaped_path.c_str())) { | |
| 308 OnSetCurrentDirectory(ERROR_SUCCESS); | |
| 309 } else { | |
| 310 ProcessRequestError(GetLastError()); | |
| 311 } | |
| 312 } | |
| 313 } | |
| 314 | |
| 315 void URLRequestFtpJob::OnSetCurrentDirectory(DWORD last_error) { | |
| 316 DCHECK_EQ(state_, SETTING_CUR_DIRECTORY); | |
| 317 | |
| 318 is_directory_ = (last_error == ERROR_SUCCESS); | |
| 319 // if last_error is not ERROR_SUCCESS, the requested url is either | |
| 320 // a file or an invalid path. We optimistically try to read as a file, | |
| 321 // and if it fails, we fail. | |
| 322 state_ = FINDING_FIRST_FILE; | |
| 323 | |
| 324 std::string unescaped_path; | |
| 325 bool is_path_valid = true; | |
| 326 if (request_->url().has_path()) { | |
| 327 is_path_valid = UnescapeAndValidatePath(request_, &unescaped_path); | |
| 328 } | |
| 329 if (is_path_valid && | |
| 330 (request_handle_ = FtpFindFirstFileA(connection_handle_, | |
| 331 unescaped_path.c_str(), | |
| 332 &find_data_, 0, | |
| 333 reinterpret_cast<DWORD_PTR>(this)))) { | |
| 334 OnFindFirstFile(GetLastError()); | |
| 335 } else { | |
| 336 ProcessRequestError(GetLastError()); | |
| 337 } | |
| 338 } | |
| 339 | |
| 340 void URLRequestFtpJob::FindNextFile() { | |
| 341 DWORD last_error; | |
| 342 if (InternetFindNextFileA(request_handle_, &find_data_)) { | |
| 343 last_error = ERROR_SUCCESS; | |
| 344 } else { | |
| 345 last_error = GetLastError(); | |
| 346 // We'll get ERROR_NO_MORE_FILES if the directory is empty. | |
| 347 if (last_error != ERROR_NO_MORE_FILES) { | |
| 348 ProcessRequestError(last_error); | |
| 349 return; | |
| 350 } | |
| 351 } | |
| 352 // Use InvokeLater to call OnFindFile as it ends up calling us, so we don't | |
| 353 // to blow the stack. | |
| 354 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( | |
| 355 this, &URLRequestFtpJob::OnFindFile, last_error)); | |
| 356 } | |
| 357 | |
| 358 void URLRequestFtpJob::OnFindFirstFile(DWORD last_error) { | |
| 359 DCHECK_EQ(state_, FINDING_FIRST_FILE); | |
| 360 if (!is_directory_) { | |
| 361 // Note that it is not enough to just check !is_directory_ and assume | |
| 362 // the URL is a file, because is_directory_ is true iff we successfully | |
| 363 // set current directory to the URL path. Therefore, the URL could just | |
| 364 // be an invalid path. We proceed optimistically and fail in that case. | |
| 365 state_ = GETTING_FILE_HANDLE; | |
| 366 std::string unescaped_path; | |
| 367 if (UnescapeAndValidatePath(request_, &unescaped_path) && | |
| 368 (request_handle_ = FtpOpenFileA(connection_handle_, | |
| 369 unescaped_path.c_str(), | |
| 370 GENERIC_READ, | |
| 371 INTERNET_FLAG_TRANSFER_BINARY, | |
| 372 reinterpret_cast<DWORD_PTR>(this)))) { | |
| 373 // Start reading file contents | |
| 374 state_ = GETTING_FILE; | |
| 375 NotifyHeadersComplete(); | |
| 376 } else { | |
| 377 ProcessRequestError(GetLastError()); | |
| 378 } | |
| 379 } else { | |
| 380 OnStartDirectoryTraversal(); | |
| 381 // If we redirect in OnStartDirectoryTraversal() then this request job | |
| 382 // is cancelled. | |
| 383 if (request_handle_) | |
| 384 OnFindFile(last_error); | |
| 385 } | |
| 386 } | |
| 387 | |
| 388 void URLRequestFtpJob::OnFindFile(DWORD last_error) { | |
| 389 DCHECK_EQ(state_, GETTING_DIRECTORY); | |
| 390 | |
| 391 if (last_error == ERROR_SUCCESS) { | |
| 392 // TODO(jabdelmalek): need to add icons for files/folders. | |
| 393 int64 size = | |
| 394 (static_cast<unsigned __int64>(find_data_.nFileSizeHigh) << 32) | | |
| 395 find_data_.nFileSizeLow; | |
| 396 | |
| 397 // We don't know the encoding used on an FTP server, but we | |
| 398 // use FtpFindFirstFileA, which I guess does NOT preserve | |
| 399 // the raw byte sequence because it's implemented in terms | |
| 400 // of FtpFindFirstFileW. Without the raw byte sequence, we | |
| 401 // can't apply the encoding detection or other heuristics | |
| 402 // to determine/guess the encoding. Neither can we use UTF-8 | |
| 403 // used by a RFC-2640-compliant FTP server. In some cases (e.g. | |
| 404 // the default code page is an SBCS with almost all bytes assigned. | |
| 405 // In lucky cases, it's even possible with a DBCS), it's possible | |
| 406 // to recover the raw byte sequence in most cases. We can do | |
| 407 // some more here, but it's not worth the effort because we're | |
| 408 // going to replace this class with URLRequestNewFtpJob. | |
| 409 string file_entry = net::GetDirectoryListingEntry( | |
| 410 base::SysNativeMBToWide(find_data_.cFileName), std::string(), | |
| 411 false, size, base::Time::FromFileTime(find_data_.ftLastWriteTime)); | |
| 412 WriteData(&file_entry, true); | |
| 413 | |
| 414 FindNextFile(); | |
| 415 return; | |
| 416 } | |
| 417 | |
| 418 DCHECK(last_error == ERROR_NO_MORE_FILES); | |
| 419 OnFinishDirectoryTraversal(); | |
| 420 } | |
| 421 | |
| 422 void URLRequestFtpJob::OnStartDirectoryTraversal() { | |
| 423 state_ = GETTING_DIRECTORY; | |
| 424 | |
| 425 // Unescape the URL path and pass the raw 8bit directly to the browser. | |
| 426 // | |
| 427 // Here we can try to detect the encoding although it may not be very | |
| 428 // reliable because it's not likely to be long enough. Because this class | |
| 429 // will be replaced by URLRequestNewFtpJob and is used only on Windows, | |
| 430 // we use SysNativeMBToWide as a stopgap measure. | |
| 431 string html = net::GetDirectoryListingHeader( | |
| 432 base::SysNativeMBToWide(UnescapeURLComponent(request_->url().path(), | |
| 433 UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS))); | |
| 434 | |
| 435 // If this isn't top level directory (i.e. the path isn't "/",) add a link to | |
| 436 // the parent directory. | |
| 437 if (request_->url().path().length() > 1) | |
| 438 html.append(net::GetDirectoryListingEntry(L"..", std::string(), | |
| 439 false, 0, base::Time())); | |
| 440 | |
| 441 WriteData(&html, true); | |
| 442 | |
| 443 NotifyHeadersComplete(); | |
| 444 } | |
| 445 | |
| 446 void URLRequestFtpJob::OnFinishDirectoryTraversal() { | |
| 447 state_ = DONE; | |
| 448 } | |
| 449 | |
| 450 int URLRequestFtpJob::WriteData(const std::string* data, | |
| 451 bool call_io_complete) { | |
| 452 int written = 0; | |
| 453 | |
| 454 if (data && data->length()) | |
| 455 directory_html_.append(*data); | |
| 456 | |
| 457 if (dest_) { | |
| 458 size_t bytes_to_copy = std::min(static_cast<size_t>(dest_size_), | |
| 459 directory_html_.length()); | |
| 460 if (bytes_to_copy) { | |
| 461 memcpy(dest_, directory_html_.c_str(), bytes_to_copy); | |
| 462 directory_html_.erase(0, bytes_to_copy); | |
| 463 dest_ = NULL; | |
| 464 dest_size_ = NULL; | |
| 465 written = static_cast<int>(bytes_to_copy); | |
| 466 | |
| 467 if (call_io_complete) { | |
| 468 // Wait a little bit before telling the parent class that we wrote | |
| 469 // data. This avoids excessive cycles of us getting one file entry and | |
| 470 // telling the parent class to Read(). | |
| 471 MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableMethod( | |
| 472 this, &URLRequestFtpJob::ContinueIOComplete, written), | |
| 473 kFtpBufferTimeMs); | |
| 474 } | |
| 475 } | |
| 476 } | |
| 477 | |
| 478 return written; | |
| 479 } | |
| 480 | |
| 481 void URLRequestFtpJob::ContinueIOComplete(int bytes_written) { | |
| 482 AsyncResult result; | |
| 483 result.dwResult = bytes_written; | |
| 484 result.dwError = ERROR_SUCCESS; | |
| 485 URLRequestInetJob::OnIOComplete(result); | |
| 486 } | |
| 487 | |
| 488 void URLRequestFtpJob::ContinueNotifyHeadersComplete() { | |
| 489 NotifyHeadersComplete(); | |
| 490 } | |
| 491 | |
| 492 int URLRequestFtpJob::CallInternetRead(char* dest, int dest_size, | |
| 493 int *bytes_read) { | |
| 494 int result; | |
| 495 | |
| 496 if (is_directory_) { | |
| 497 // Copy the html that we created from the directory listing that we got | |
| 498 // from InternetFindNextFile. | |
| 499 DCHECK(dest_ == NULL); | |
| 500 dest_ = dest; | |
| 501 dest_size_ = dest_size; | |
| 502 | |
| 503 DCHECK(state_ == GETTING_DIRECTORY || state_ == DONE); | |
| 504 int written = WriteData(NULL, false); | |
| 505 if (written) { | |
| 506 *bytes_read = written; | |
| 507 result = ERROR_SUCCESS; | |
| 508 } else { | |
| 509 result = state_ == GETTING_DIRECTORY ? ERROR_IO_PENDING : ERROR_SUCCESS; | |
| 510 } | |
| 511 } else { | |
| 512 DWORD bytes_to_read = dest_size; | |
| 513 bytes_read_ = 0; | |
| 514 // InternetReadFileEx doesn't work for asynchronous FTP, InternetReadFile | |
| 515 // must be used instead. | |
| 516 if (!InternetReadFile(request_handle_, dest, bytes_to_read, &bytes_read_)) | |
| 517 return GetLastError(); | |
| 518 | |
| 519 *bytes_read = static_cast<int>(bytes_read_); | |
| 520 result = ERROR_SUCCESS; | |
| 521 } | |
| 522 | |
| 523 return result; | |
| 524 } | |
| 525 | |
| 526 bool URLRequestFtpJob::GetReadBytes(const AsyncResult& result, | |
| 527 int* bytes_read) { | |
| 528 if (is_directory_) { | |
| 529 *bytes_read = static_cast<int>(result.dwResult); | |
| 530 } else { | |
| 531 if (!result.dwResult) | |
| 532 return false; | |
| 533 | |
| 534 // IE5 and later return the number of read bytes in the | |
| 535 // INTERNET_ASYNC_RESULT structure. IE4 holds on to the pointer passed in | |
| 536 // to InternetReadFile and store it there. | |
| 537 *bytes_read = bytes_read_; | |
| 538 | |
| 539 if (!*bytes_read) | |
| 540 *bytes_read = result.dwError; | |
| 541 } | |
| 542 | |
| 543 return true; | |
| 544 } | |
| 545 | |
| 546 bool URLRequestFtpJob::IsRedirectResponse(GURL* location, | |
| 547 int* http_status_code) { | |
| 548 if (is_directory_) { | |
| 549 std::string ftp_path = request_->url().path(); | |
| 550 if (!ftp_path.empty() && ('/' != ftp_path[ftp_path.length() - 1])) { | |
| 551 ftp_path.push_back('/'); | |
| 552 GURL::Replacements replacements; | |
| 553 replacements.SetPathStr(ftp_path); | |
| 554 | |
| 555 *location = request_->url().ReplaceComponents(replacements); | |
| 556 *http_status_code = 301; // simulate a permanent redirect | |
| 557 return true; | |
| 558 } | |
| 559 } | |
| 560 | |
| 561 return false; | |
| 562 } | |
| OLD | NEW |