1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
|
// Copyright (c) 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/extensions/api/dial/device_description_fetcher.h"
#include "chrome/browser/extensions/api/dial/dial_device_data.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/load_flags.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "net/url_request/url_fetcher.h"
using content::BrowserThread;
constexpr char kApplicationUrlHeaderName[] = "Application-URL";
constexpr int kMaxRetries = 3;
// DIAL devices are unlikely to expose uPnP functions other than DIAL, so 256kb
// should be more than sufficient.
constexpr int kMaxDescriptionSizeBytes = 262144;
namespace extensions {
namespace api {
namespace dial {
DeviceDescriptionFetcher::DeviceDescriptionFetcher(
const GURL& device_description_url,
Profile* profile,
base::OnceCallback<void(const DialDeviceDescriptionData&)> success_cb,
base::OnceCallback<void(const std::string&)> error_cb)
: device_description_url_(device_description_url),
profile_(profile),
success_cb_(std::move(success_cb)),
error_cb_(std::move(error_cb)) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(profile_);
DCHECK(device_description_url_.is_valid());
}
DeviceDescriptionFetcher::~DeviceDescriptionFetcher() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
void DeviceDescriptionFetcher::Start() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!fetcher_);
// DIAL returns device descriptions via GET request.
fetcher_ =
net::URLFetcher::Create(kURLFetcherIDForTest, device_description_url_,
net::URLFetcher::GET, this);
// net::LOAD_BYPASS_PROXY: Proxies almost certainly hurt more cases than they
// help.
// net::LOAD_DISABLE_CACHE: The request should not touch the cache.
// net::LOAD_DO_NOT_{SAVE,SEND}_COOKIES: The request should not touch cookies.
// net::LOAD_DO_NOT_SEND_AUTH_DATA: The request should not send auth data.
fetcher_->SetLoadFlags(net::LOAD_BYPASS_PROXY | net::LOAD_DISABLE_CACHE |
net::LOAD_DO_NOT_SAVE_COOKIES |
net::LOAD_DO_NOT_SEND_COOKIES |
net::LOAD_DO_NOT_SEND_AUTH_DATA);
// Section 5.4 of the DIAL spec prohibits redirects.
fetcher_->SetStopOnRedirect(true);
// Allow the fetcher to retry on 5XX responses and ERR_NETWORK_CHANGED.
fetcher_->SetMaxRetriesOn5xx(kMaxRetries);
fetcher_->SetAutomaticallyRetryOnNetworkChanges(kMaxRetries);
fetcher_->SetRequestContext(profile_->GetRequestContext());
fetcher_->Start();
}
void DeviceDescriptionFetcher::OnURLFetchComplete(
const net::URLFetcher* source) {
DCHECK_EQ(fetcher_.get(), source);
if (source->GetResponseCode() != net::HTTP_OK) {
ReportError(
base::StringPrintf("HTTP %d: Unable to fetch device description",
source->GetResponseCode()));
return;
}
const net::HttpResponseHeaders* headers = source->GetResponseHeaders();
// NOTE: The uPnP spec requires devices to set a Content-Type: header of
// text/xml; charset="utf-8" (sec 2.11). However Chromecast (and possibly
// other devices) do not comply, so specifically not checking this header.
std::string app_url_header;
if (!headers->GetNormalizedHeader(kApplicationUrlHeaderName,
&app_url_header) ||
app_url_header.empty()) {
ReportError("Missing or empty Application-URL:");
return;
}
// Section 5.4 of the DIAL spec implies that the Application URL should not
// have path, query or fragment...unsure if that can be enforced.
GURL app_url(app_url_header);
if (!app_url.is_valid() || !app_url.SchemeIs("http") ||
!app_url.HostIsIPAddress() ||
app_url.host() != device_description_url_.host()) {
ReportError(base::StringPrintf("Invalid Application-URL: %s",
app_url_header.c_str()));
return;
}
if (source->GetReceivedResponseContentLength() > kMaxDescriptionSizeBytes) {
ReportError("Response too large");
return;
}
std::string device_description;
if (!source->GetResponseAsString(&device_description) ||
device_description.empty()) {
ReportError("Missing or empty response");
return;
}
if (!base::IsStringUTF8(device_description)) {
ReportError("Invalid response encoding");
return;
}
std::move(success_cb_)
.Run(DialDeviceDescriptionData(std::move(device_description), app_url));
}
void DeviceDescriptionFetcher::OnURLFetchDownloadProgress(
const net::URLFetcher* source,
int64_t current,
int64_t total,
int64_t current_network_bytes) {}
void DeviceDescriptionFetcher::OnURLFetchUploadProgress(
const net::URLFetcher* source,
int64_t current,
int64_t total) {}
void DeviceDescriptionFetcher::ReportError(const std::string& message) {
std::move(error_cb_).Run(message);
}
} // namespace dial
} // namespace api
} // namespace extensions
|