Fix the duplicatedly commited composition text when switching IME.
[chromium-blink-merge.git] / chrome / browser / chromeos / policy / upload_job_impl.cc
blob22cbf4d2cf75b3b8b66dbd549c9aef8e5f03dba7
1 // Copyright (c) 2015 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.
5 #include "chrome/browser/chromeos/policy/upload_job_impl.h"
7 #include <set>
9 #include "base/logging.h"
10 #include "base/rand_util.h"
11 #include "base/strings/stringprintf.h"
12 #include "google_apis/gaia/gaia_constants.h"
13 #include "google_apis/gaia/google_service_auth_error.h"
14 #include "net/http/http_status_code.h"
15 #include "net/url_request/url_request_status.h"
17 namespace policy {
19 namespace {
21 // Defines the characters that might appear in strings generated by
22 // GenerateRandomString().
23 const char kAlphaNum[] =
24 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
26 // Format for bearer tokens in HTTP requests to access OAuth 2.0 protected
27 // resources.
28 const char kAuthorizationHeaderFormat[] = "Authorization: Bearer %s";
30 // Prefix added to a randomly generated string when choosing the MIME boundary.
31 const char kMultipartBoundaryPrefix[] = "----**--";
33 // Postfix added to a randomly generated string when choosing the MIME boundary.
34 const char kMultipartBoundaryPostfix[] = "--**----";
36 // Value the "Content-Type" field will be set to in the POST request.
37 const char kUploadContentType[] = "multipart/form-data";
39 // Number of retries when randomly generating a MIME boundary.
40 const int kMimeBoundaryRetries = 3;
42 // Length of the random string for the MIME boundary.
43 const int kMimeBoundarySize = 32;
45 // Number of upload retries.
46 const int kMaxRetries = 1;
48 // Generates a random alphanumeric string of length |length|.
49 std::string GenerateRandomString(size_t length) {
50 std::string random;
51 random.reserve(length);
52 for (size_t i = 0; i < length; i++)
53 random.push_back(kAlphaNum[base::RandGenerator(sizeof(kAlphaNum) - 1)]);
54 return random;
57 } // namespace
59 UploadJobImpl::Delegate::~Delegate() {
62 UploadJobImpl::MimeBoundaryGenerator::~MimeBoundaryGenerator() {
65 UploadJobImpl::RandomMimeBoundaryGenerator::~RandomMimeBoundaryGenerator() {
68 // multipart/form-data POST request to upload the data. A DataSegment
69 // corresponds to one "Content-Disposition" in the "multipart" request.
70 class DataSegment {
71 public:
72 DataSegment(const std::string& name,
73 const std::string& filename,
74 scoped_ptr<std::string> data,
75 const std::map<std::string, std::string>& header_entries);
77 // Returns the header entries for this DataSegment.
78 const std::map<std::string, std::string>& GetHeaderEntries() const;
80 // Returns the string that will be assigned to the |name| field in the header.
81 // |name| must be unique throughout the multipart message. This is enforced in
82 // SetUpMultipart().
83 const std::string& GetName() const;
85 // Returns the string that will be assigned to the |filename| field in the
86 // header. If the |filename| is the empty string, the header field will be
87 // omitted.
88 const std::string& GetFilename() const;
90 // Returns the data contained in this DataSegment. Ownership is passed.
91 scoped_ptr<std::string> GetData();
93 // Returns the size in bytes of the blob in |data_|.
94 size_t GetDataSize() const;
96 // Helper method that performs a substring match of |chunk| in |data_|.
97 // Returns |true| if |chunk| matches a substring, |false| otherwise.
98 bool CheckIfDataContains(const std::string& chunk);
100 private:
101 const std::string name_;
102 const std::string filename_;
103 scoped_ptr<std::string> data_;
104 std::map<std::string, std::string> header_entries_;
106 DISALLOW_COPY_AND_ASSIGN(DataSegment);
109 DataSegment::DataSegment(
110 const std::string& name,
111 const std::string& filename,
112 scoped_ptr<std::string> data,
113 const std::map<std::string, std::string>& header_entries)
114 : name_(name),
115 filename_(filename),
116 data_(data.Pass()),
117 header_entries_(header_entries) {
118 DCHECK(data_);
121 const std::map<std::string, std::string>& DataSegment::GetHeaderEntries()
122 const {
123 return header_entries_;
126 const std::string& DataSegment::GetName() const {
127 return name_;
130 const std::string& DataSegment::GetFilename() const {
131 return filename_;
134 scoped_ptr<std::string> DataSegment::GetData() {
135 return data_.Pass();
138 bool DataSegment::CheckIfDataContains(const std::string& chunk) {
139 DCHECK(data_);
140 return data_->find(chunk) != std::string::npos;
143 size_t DataSegment::GetDataSize() const {
144 DCHECK(data_);
145 return data_->size();
148 std::string UploadJobImpl::RandomMimeBoundaryGenerator::GenerateBoundary(
149 size_t length) const {
150 std::string boundary;
151 boundary.reserve(length);
152 DCHECK_GT(length, sizeof(kMultipartBoundaryPrefix) +
153 sizeof(kMultipartBoundaryPostfix));
154 const size_t random_part_length = length - sizeof(kMultipartBoundaryPrefix) -
155 sizeof(kMultipartBoundaryPostfix);
156 boundary.append(kMultipartBoundaryPrefix);
157 boundary.append(GenerateRandomString(random_part_length));
158 boundary.append(kMultipartBoundaryPostfix);
159 return boundary;
162 UploadJobImpl::UploadJobImpl(
163 const GURL& upload_url,
164 const std::string& account_id,
165 OAuth2TokenService* token_service,
166 scoped_refptr<net::URLRequestContextGetter> url_context_getter,
167 Delegate* delegate,
168 scoped_ptr<MimeBoundaryGenerator> boundary_generator)
169 : OAuth2TokenService::Consumer("cros_upload_job"),
170 upload_url_(upload_url),
171 account_id_(account_id),
172 token_service_(token_service),
173 url_context_getter_(url_context_getter),
174 delegate_(delegate),
175 boundary_generator_(boundary_generator.Pass()),
176 state_(IDLE),
177 retry_(0) {
178 DCHECK(token_service_);
179 DCHECK(url_context_getter_);
180 DCHECK(delegate_);
181 if (!upload_url_.is_valid()) {
182 state_ = ERROR;
183 NOTREACHED() << upload_url_ << " is not a valid URL.";
187 UploadJobImpl::~UploadJobImpl() {
190 void UploadJobImpl::AddDataSegment(
191 const std::string& name,
192 const std::string& filename,
193 const std::map<std::string, std::string>& header_entries,
194 scoped_ptr<std::string> data) {
195 // Cannot add data to busy or failed instance.
196 DCHECK_EQ(IDLE, state_);
197 if (state_ != IDLE)
198 return;
200 scoped_ptr<DataSegment> data_segment(
201 new DataSegment(name, filename, data.Pass(), header_entries));
202 data_segments_.push_back(data_segment.Pass());
205 void UploadJobImpl::Start() {
206 // Cannot start an upload on a busy or failed instance.
207 DCHECK_EQ(IDLE, state_);
208 if (state_ != IDLE)
209 return;
210 RequestAccessToken();
213 void UploadJobImpl::RequestAccessToken() {
214 state_ = ACQUIRING_TOKEN;
216 OAuth2TokenService::ScopeSet scope_set;
217 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth);
218 access_token_request_ =
219 token_service_->StartRequest(account_id_, scope_set, this);
222 bool UploadJobImpl::SetUpMultipart() {
223 DCHECK_EQ(ACQUIRING_TOKEN, state_);
224 state_ = PREPARING_CONTENT;
226 if (mime_boundary_ && post_data_)
227 return true;
229 std::set<std::string> used_names;
231 // Check uniqueness of header field names.
232 for (const auto& data_segment : data_segments_) {
233 if (!used_names.insert(data_segment->GetName()).second)
234 return false;
237 // Generates random MIME boundaries and tests if they appear in any of the
238 // data segments. Tries up to |kMimeBoundaryRetries| times to find a MIME
239 // boundary that does not appear within any data segment.
240 bool found = false;
241 int retry = 0;
242 do {
243 found = true;
244 mime_boundary_.reset(new std::string(
245 boundary_generator_->GenerateBoundary(kMimeBoundarySize)));
246 for (const auto& data_segment : data_segments_) {
247 if (data_segment->CheckIfDataContains(*mime_boundary_)) {
248 found = false;
249 break;
252 ++retry;
253 } while (!found && retry <= kMimeBoundaryRetries);
255 // Notify the delegate that content encoding failed.
256 if (!found) {
257 delegate_->OnFailure(CONTENT_ENCODING_ERROR);
258 mime_boundary_.reset();
259 return false;
262 // Estimate an upper bound for the total message size to make memory
263 // allocation more efficient. It is not an error if this turns out to be too
264 // small as std::string will take care of the realloc.
265 size_t size = 0;
266 for (const auto& data_segment : data_segments_) {
267 for (const auto& entry : data_segment->GetHeaderEntries())
268 size += entry.first.size() + entry.second.size();
269 size += kMimeBoundarySize + data_segment->GetName().size() +
270 data_segment->GetFilename().size() + data_segment->GetDataSize();
271 // Add some extra space for all the constants and control characters.
272 size += 128;
275 // Allocate memory of the expected size.
276 post_data_.reset(new std::string);
277 post_data_->reserve(size);
279 for (const auto& data_segment : data_segments_) {
280 post_data_->append("--" + *mime_boundary_.get() + "\r\n");
281 post_data_->append("Content-Disposition: form-data; name=\"" +
282 data_segment->GetName() + "\"");
283 if (!data_segment->GetFilename().empty()) {
284 post_data_->append("; filename=\"" + data_segment->GetFilename() + "\"");
286 post_data_->append("\r\n");
288 // Add custom header fields.
289 for (const auto& entry : data_segment->GetHeaderEntries()) {
290 post_data_->append(entry.first + ": " + entry.second + "\r\n");
292 scoped_ptr<std::string> data = data_segment->GetData();
293 post_data_->append("\r\n" + *data + "\r\n");
295 post_data_->append("--" + *mime_boundary_.get() + "--\r\n");
297 // Issues a warning if our buffer size estimate was too small.
298 if (post_data_->size() > size) {
299 LOG(WARNING)
300 << "Reallocation needed in POST data buffer. Expected maximum size "
301 << size << " bytes, actual size " << post_data_->size() << " bytes.";
304 // Discard the data segments as they are not needed anymore from here on.
305 data_segments_.clear();
307 return true;
310 void UploadJobImpl::CreateAndStartURLFetcher(const std::string& access_token) {
311 // Ensure that the content has been prepared and the upload url is valid.
312 DCHECK_EQ(PREPARING_CONTENT, state_);
314 std::string content_type = kUploadContentType;
315 content_type.append("; boundary=");
316 content_type.append(*mime_boundary_.get());
318 upload_fetcher_ =
319 net::URLFetcher::Create(upload_url_, net::URLFetcher::POST, this);
320 upload_fetcher_->SetRequestContext(url_context_getter_.get());
321 upload_fetcher_->SetUploadData(content_type, *post_data_);
322 upload_fetcher_->AddExtraRequestHeader(
323 base::StringPrintf(kAuthorizationHeaderFormat, access_token.c_str()));
324 upload_fetcher_->Start();
327 void UploadJobImpl::StartUpload(const std::string& access_token) {
328 if (!SetUpMultipart()) {
329 LOG(ERROR) << "Multipart message assembly failed.";
330 state_ = ERROR;
331 return;
333 CreateAndStartURLFetcher(access_token);
334 state_ = UPLOADING;
337 void UploadJobImpl::OnGetTokenSuccess(
338 const OAuth2TokenService::Request* request,
339 const std::string& access_token,
340 const base::Time& expiration_time) {
341 DCHECK_EQ(ACQUIRING_TOKEN, state_);
342 DCHECK_EQ(access_token_request_.get(), request);
343 access_token_request_.reset();
345 // Also cache the token locally, so that we can revoke it later if necessary.
346 access_token_ = access_token;
347 StartUpload(access_token);
350 void UploadJobImpl::OnGetTokenFailure(
351 const OAuth2TokenService::Request* request,
352 const GoogleServiceAuthError& error) {
353 DCHECK_EQ(ACQUIRING_TOKEN, state_);
354 DCHECK_EQ(access_token_request_.get(), request);
355 access_token_request_.reset();
356 LOG(ERROR) << "Token request failed: " << error.ToString();
357 state_ = ERROR;
358 delegate_->OnFailure(AUTHENTICATION_ERROR);
361 void UploadJobImpl::OnURLFetchComplete(const net::URLFetcher* source) {
362 DCHECK_EQ(upload_fetcher_.get(), source);
363 const net::URLRequestStatus& status = source->GetStatus();
364 if (!status.is_success()) {
365 LOG(ERROR) << "URLRequestStatus error " << status.error();
366 upload_fetcher_.reset();
367 state_ = ERROR;
368 post_data_.reset();
369 delegate_->OnFailure(NETWORK_ERROR);
370 return;
373 const int response_code = source->GetResponseCode();
374 const bool success = response_code == net::HTTP_OK;
375 if (!success)
376 LOG(ERROR) << "POST request failed with HTTP status code " << response_code;
378 if (response_code == net::HTTP_UNAUTHORIZED) {
379 if (retry_ >= kMaxRetries) {
380 upload_fetcher_.reset();
381 LOG(ERROR) << "Unauthorized request.";
382 state_ = ERROR;
383 post_data_.reset();
384 delegate_->OnFailure(AUTHENTICATION_ERROR);
385 return;
387 retry_++;
388 upload_fetcher_.reset();
389 OAuth2TokenService::ScopeSet scope_set;
390 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth);
391 token_service_->InvalidateAccessToken(account_id_, scope_set,
392 access_token_);
393 access_token_.clear();
394 RequestAccessToken();
395 return;
398 upload_fetcher_.reset();
399 access_token_.clear();
400 upload_fetcher_.reset();
401 post_data_.reset();
402 if (success) {
403 state_ = SUCCESS;
404 delegate_->OnSuccess();
405 } else {
406 state_ = ERROR;
407 delegate_->OnFailure(SERVER_ERROR);
411 } // namespace policy