Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "google_apis/drive/drive_api_requests.h" | 5 #include "google_apis/drive/drive_api_requests.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "base/callback.h" | 8 #include "base/callback.h" |
| 9 #include "base/json/json_writer.h" | 9 #include "base/json/json_writer.h" |
| 10 #include "base/location.h" | 10 #include "base/location.h" |
| 11 #include "base/sequenced_task_runner.h" | 11 #include "base/sequenced_task_runner.h" |
| 12 #include "base/strings/stringprintf.h" | 12 #include "base/strings/stringprintf.h" |
| 13 #include "base/task_runner_util.h" | 13 #include "base/task_runner_util.h" |
| 14 #include "base/values.h" | 14 #include "base/values.h" |
| 15 #include "google_apis/drive/request_sender.h" | 15 #include "google_apis/drive/request_sender.h" |
| 16 #include "google_apis/drive/request_util.h" | 16 #include "google_apis/drive/request_util.h" |
| 17 #include "google_apis/drive/time_util.h" | 17 #include "google_apis/drive/time_util.h" |
| 18 #include "net/base/url_util.h" | 18 #include "net/base/url_util.h" |
| 19 #include "net/http/http_response_headers.h" | |
| 19 | 20 |
| 20 namespace google_apis { | 21 namespace google_apis { |
| 21 namespace drive { | 22 namespace drive { |
| 22 namespace { | 23 namespace { |
| 23 | 24 |
| 24 // Format of one request in batch uploading request. | 25 // Format of one request in batch uploading request. |
| 25 const char kBatchUploadRequestFormat[] = | 26 const char kBatchUploadRequestFormat[] = |
| 26 "%s %s HTTP/1.1\n" | 27 "%s %s HTTP/1.1\n" |
| 27 "Host: %s\n" | 28 "Host: %s\n" |
| 28 "X-Goog-Upload-Protocol: multipart\n" | 29 "X-Goog-Upload-Protocol: multipart\n" |
| 29 "Content-Type: %s\n" | 30 "Content-Type: %s\n" |
| 30 "\n" | 31 "\n" |
| 31 "%s"; | 32 "%s"; |
| 32 | 33 |
| 33 // Request header for specifying batch upload. | 34 // Request header for specifying batch upload. |
| 34 const char kBatchUploadHeader[] = "X-Goog-Upload-Protocol: batch"; | 35 const char kBatchUploadHeader[] = "X-Goog-Upload-Protocol: batch"; |
| 35 | 36 |
| 36 // Content type of HTTP request. | 37 // Content type of HTTP request. |
| 37 const char kHttpContentType[] = "application/http"; | 38 const char kHttpContentType[] = "application/http"; |
| 38 | 39 |
| 40 // Break line in HTTP message. | |
| 41 const char kHttpBr[] = "\r\n"; | |
| 42 | |
| 43 // Mime type of multipart mixed. | |
| 44 const char kMultipartMixedMimeTypePrefix[] = "multipart/mixed; boundary="; | |
| 45 | |
| 39 // Parses the JSON value to FileResource instance and runs |callback| on the | 46 // Parses the JSON value to FileResource instance and runs |callback| on the |
| 40 // UI thread once parsing is done. | 47 // UI thread once parsing is done. |
| 41 // This is customized version of ParseJsonAndRun defined above to adapt the | 48 // This is customized version of ParseJsonAndRun defined above to adapt the |
| 42 // remaining response type. | 49 // remaining response type. |
| 43 void ParseFileResourceWithUploadRangeAndRun(const UploadRangeCallback& callback, | 50 void ParseFileResourceWithUploadRangeAndRun(const UploadRangeCallback& callback, |
| 44 const UploadRangeResponse& response, | 51 const UploadRangeResponse& response, |
| 45 scoped_ptr<base::Value> value) { | 52 scoped_ptr<base::Value> value) { |
| 46 DCHECK(!callback.is_null()); | 53 DCHECK(!callback.is_null()); |
| 47 | 54 |
| 48 scoped_ptr<FileResource> file_resource; | 55 scoped_ptr<FileResource> file_resource; |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 119 root.SetString("lastViewedByMeDate", google_apis::util::FormatTimeAsString( | 126 root.SetString("lastViewedByMeDate", google_apis::util::FormatTimeAsString( |
| 120 last_viewed_by_me_date)); | 127 last_viewed_by_me_date)); |
| 121 } | 128 } |
| 122 | 129 |
| 123 AttachProperties(properties, &root); | 130 AttachProperties(properties, &root); |
| 124 std::string json_string; | 131 std::string json_string; |
| 125 base::JSONWriter::Write(&root, &json_string); | 132 base::JSONWriter::Write(&root, &json_string); |
| 126 return json_string; | 133 return json_string; |
| 127 } | 134 } |
| 128 | 135 |
| 136 // Checks if the string between |begin| and |end| equals to |expected|. The | |
| 137 // string between |begin| and |end| can include kHttpBr. |expected| must not | |
| 138 // include kHttpBr. | |
| 139 bool IsLineEqual(std::string::const_iterator begin, | |
| 140 std::string::const_iterator end, | |
| 141 const std::string& expected) { | |
| 142 size_t size = end - begin; | |
| 143 const std::string br(kHttpBr); | |
| 144 if (size >= br.size() && std::equal(br.begin(), br.end(), end - br.size())) { | |
| 145 size -= br.size(); | |
| 146 } | |
| 147 | |
| 148 if (size != expected.size()) | |
| 149 return false; | |
| 150 | |
| 151 return std::equal(expected.begin(), expected.end(), begin); | |
| 152 } | |
| 153 | |
| 154 // Obtains substring of string between |begin| and |end| that follows |prefix|. | |
| 155 // Returns false if |prefix| does not match. | |
| 156 bool GetAfterPrefix(std::string::const_iterator begin, | |
| 157 std::string::const_iterator end, | |
| 158 const std::string& prefix, | |
| 159 std::string* output) { | |
| 160 std::string::const_iterator output_begin = begin + prefix.size(); | |
|
kinaba
2015/04/27 08:09:37
strictly speaking this addition is not legal if ou
hirono
2015/04/27 12:32:45
I turned to use StringPiece and just removed this
| |
| 161 if (output_begin > end) | |
| 162 return false; | |
| 163 if (std::equal(prefix.begin(), prefix.end(), begin)) { | |
| 164 *output = std::string(output_begin, end); | |
| 165 return true; | |
| 166 } else { | |
| 167 return false; | |
| 168 } | |
| 169 } | |
| 170 | |
| 129 } // namespace | 171 } // namespace |
| 130 | 172 |
| 173 MultipartHttpResponse::MultipartHttpResponse() : code(HTTP_SUCCESS) { | |
| 174 } | |
| 175 | |
| 176 MultipartHttpResponse::~MultipartHttpResponse() { | |
| 177 } | |
| 178 | |
| 179 bool ParseMultipartResponse(const std::string& boundary, | |
| 180 const std::string& response, | |
| 181 std::vector<MultipartHttpResponse>* parts) { | |
| 182 if (boundary.empty() || response.empty()) | |
| 183 return false; | |
| 184 const size_t br_size = std::string(kHttpBr).size(); | |
| 185 std::vector<std::string::const_iterator> lines; | |
| 186 std::string::const_iterator it = response.begin(); | |
| 187 while (true) { | |
| 188 lines.push_back(it); | |
| 189 it = std::search(it, response.end(), kHttpBr, kHttpBr + br_size); | |
| 190 if (it == response.end()) { | |
| 191 lines.push_back(it); | |
| 192 break; | |
| 193 } | |
| 194 it += br_size; | |
| 195 } | |
|
kinaba
2015/04/27 08:09:37
nit: this loop can be factored out as a function,
hirono
2015/04/27 12:32:45
Done.
| |
| 196 | |
| 197 const std::string header = "--" + boundary; | |
| 198 const std::string terminator = "--" + boundary + "--"; | |
| 199 | |
| 200 enum { | |
| 201 STATE_START, | |
| 202 STATE_PART_HEADER, | |
| 203 STATE_PART_HTTP_STATUS_LINE, | |
| 204 STATE_PART_HTTP_HEADER, | |
| 205 STATE_PART_HTTP_BODY | |
| 206 } state = STATE_START; | |
| 207 | |
| 208 const std::string kHttpStatusPrefix = "HTTP/1.1 "; | |
| 209 std::vector<MultipartHttpResponse> responses; | |
| 210 DriveApiErrorCode code; | |
| 211 std::string::const_iterator body_begin; | |
| 212 for (size_t i = 0; i < lines.size() - 1; ++i) { | |
|
kinaba
2015/04/27 08:09:37
You can utilize base/strings/string_piece.h.
Let
hirono
2015/04/27 12:32:45
Done.
| |
| 213 if (state == STATE_PART_HEADER && IsLineEqual(lines[i], lines[i + 1], "")) { | |
| 214 state = STATE_PART_HTTP_STATUS_LINE; | |
| 215 continue; | |
| 216 } | |
| 217 | |
| 218 if (state == STATE_PART_HTTP_STATUS_LINE) { | |
| 219 std::string code_string; | |
| 220 if (GetAfterPrefix(lines[i], lines[i + 1], kHttpStatusPrefix, | |
| 221 &code_string)) { | |
| 222 code = static_cast<DriveApiErrorCode>(atoi(code_string.c_str())); | |
| 223 if (code <= 0) | |
| 224 code = DRIVE_PARSE_ERROR; | |
| 225 } else { | |
| 226 code = DRIVE_PARSE_ERROR; | |
| 227 } | |
| 228 state = STATE_PART_HTTP_HEADER; | |
| 229 continue; | |
| 230 } | |
| 231 | |
| 232 if (state == STATE_PART_HTTP_HEADER && | |
| 233 IsLineEqual(lines[i], lines[i + 1], "")) { | |
| 234 state = STATE_PART_HTTP_BODY; | |
| 235 body_begin = lines[i + 1]; | |
| 236 continue; | |
| 237 } | |
| 238 | |
| 239 const bool is_new_part = IsLineEqual(lines[i], lines[i + 1], header); | |
|
kinaba
2015/04/27 08:09:37
I'm not sure how much we need to be pedantic, but
hirono
2015/04/27 12:32:45
Let me handle this. Done
| |
| 240 const bool was_last_part = IsLineEqual(lines[i], lines[i + 1], terminator); | |
| 241 if (is_new_part || was_last_part) { | |
| 242 switch (state) { | |
| 243 case STATE_START: | |
| 244 break; | |
| 245 case STATE_PART_HEADER: | |
| 246 case STATE_PART_HTTP_STATUS_LINE: | |
| 247 responses.push_back(MultipartHttpResponse()); | |
| 248 responses.back().code = DRIVE_PARSE_ERROR; | |
| 249 break; | |
| 250 case STATE_PART_HTTP_HEADER: | |
| 251 responses.push_back(MultipartHttpResponse()); | |
| 252 responses.back().code = code; | |
| 253 break; | |
| 254 case STATE_PART_HTTP_BODY: | |
| 255 responses.push_back(MultipartHttpResponse()); | |
| 256 responses.back().code = code; | |
| 257 responses.back().body = std::string(body_begin, lines[i]); | |
|
kinaba
2015/04/27 08:09:37
then body will include the last CRLF (i.e., two ch
hirono
2015/04/27 12:32:45
Yes, it should not include CRLF. Thank you for cat
| |
| 258 break; | |
| 259 } | |
| 260 } | |
| 261 | |
| 262 if (is_new_part) | |
| 263 state = STATE_PART_HEADER; | |
| 264 if (was_last_part) | |
| 265 break; | |
|
kinaba
2015/04/27 08:09:37
nit: I slightly prefer these 4 lines to be put ins
hirono
2015/04/27 12:32:45
Done.
| |
| 266 } | |
| 267 | |
| 268 parts->swap(responses); | |
| 269 return true; | |
| 270 } | |
| 271 | |
| 131 Property::Property() : visibility_(VISIBILITY_PRIVATE) { | 272 Property::Property() : visibility_(VISIBILITY_PRIVATE) { |
| 132 } | 273 } |
| 133 | 274 |
| 134 Property::~Property() { | 275 Property::~Property() { |
| 135 } | 276 } |
| 136 | 277 |
| 137 //============================ DriveApiPartialFieldRequest ==================== | 278 //============================ DriveApiPartialFieldRequest ==================== |
| 138 | 279 |
| 139 DriveApiPartialFieldRequest::DriveApiPartialFieldRequest( | 280 DriveApiPartialFieldRequest::DriveApiPartialFieldRequest( |
| 140 RequestSender* sender) : UrlFetchRequestBase(sender) { | 281 RequestSender* sender) : UrlFetchRequestBase(sender) { |
| (...skipping 866 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1007 void BatchUploadRequest::SetBoundaryForTesting(const std::string& boundary) { | 1148 void BatchUploadRequest::SetBoundaryForTesting(const std::string& boundary) { |
| 1008 boundary_ = boundary; | 1149 boundary_ = boundary; |
| 1009 } | 1150 } |
| 1010 | 1151 |
| 1011 void BatchUploadRequest::AddRequest(BatchableRequestBase* request) { | 1152 void BatchUploadRequest::AddRequest(BatchableRequestBase* request) { |
| 1012 DCHECK(CalledOnValidThread()); | 1153 DCHECK(CalledOnValidThread()); |
| 1013 DCHECK(request); | 1154 DCHECK(request); |
| 1014 DCHECK(GetChildEntry(request) == child_requests_.end()); | 1155 DCHECK(GetChildEntry(request) == child_requests_.end()); |
| 1015 DCHECK(!committed_); | 1156 DCHECK(!committed_); |
| 1016 child_requests_.push_back(BatchUploadChildEntry(request)); | 1157 child_requests_.push_back(BatchUploadChildEntry(request)); |
| 1017 request->Prepare( | 1158 request->Prepare(base::Bind(&BatchUploadRequest::OnChildRequestPrepared, |
| 1018 base::Bind(&BatchUploadRequest::OnChildRequestPrepared, | 1159 weak_ptr_factory_.GetWeakPtr(), request)); |
| 1019 weak_ptr_factory_.GetWeakPtr(), | |
| 1020 request)); | |
| 1021 } | 1160 } |
| 1022 | 1161 |
| 1023 void BatchUploadRequest::OnChildRequestPrepared( | 1162 void BatchUploadRequest::OnChildRequestPrepared(RequestID request_id, |
| 1024 RequestID request_id, DriveApiErrorCode result) { | 1163 DriveApiErrorCode result) { |
| 1025 DCHECK(CalledOnValidThread()); | 1164 DCHECK(CalledOnValidThread()); |
| 1026 auto const child = GetChildEntry(request_id); | 1165 auto const child = GetChildEntry(request_id); |
| 1027 DCHECK(child != child_requests_.end()); | 1166 DCHECK(child != child_requests_.end()); |
| 1028 if (IsSuccessfulDriveApiErrorCode(result)) { | 1167 if (IsSuccessfulDriveApiErrorCode(result)) { |
| 1029 child->prepared = true; | 1168 child->prepared = true; |
| 1030 } else { | 1169 } else { |
| 1031 child->request->RunCallbackOnPrematureFailure(result); | 1170 child->request->RunCallbackOnPrematureFailure(result); |
| 1032 sender_->RequestFinished(child->request); | 1171 sender_->RequestFinished(child->request); |
| 1033 child_requests_.erase(child); | 1172 child_requests_.erase(child); |
| 1034 } | 1173 } |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 1054 for (auto& child : child_requests_) { | 1193 for (auto& child : child_requests_) { |
| 1055 // Request cancel should delete the request instance. | 1194 // Request cancel should delete the request instance. |
| 1056 child.request->Cancel(); | 1195 child.request->Cancel(); |
| 1057 } | 1196 } |
| 1058 child_requests_.clear(); | 1197 child_requests_.clear(); |
| 1059 UrlFetchRequestBase::Cancel(); | 1198 UrlFetchRequestBase::Cancel(); |
| 1060 } | 1199 } |
| 1061 | 1200 |
| 1062 // Obtains corresponding child entry of |request_id|. Returns NULL if the | 1201 // Obtains corresponding child entry of |request_id|. Returns NULL if the |
| 1063 // entry is not found. | 1202 // entry is not found. |
| 1064 std::vector<BatchUploadChildEntry>::iterator | 1203 std::vector<BatchUploadChildEntry>::iterator BatchUploadRequest::GetChildEntry( |
| 1065 BatchUploadRequest::GetChildEntry(RequestID request_id) { | 1204 RequestID request_id) { |
| 1066 for (auto it = child_requests_.begin(); it != child_requests_.end(); ++it) { | 1205 for (auto it = child_requests_.begin(); it != child_requests_.end(); ++it) { |
| 1067 if (it->request == request_id) | 1206 if (it->request == request_id) |
| 1068 return it; | 1207 return it; |
| 1069 } | 1208 } |
| 1070 return child_requests_.end(); | 1209 return child_requests_.end(); |
| 1071 } | 1210 } |
| 1072 | 1211 |
| 1073 void BatchUploadRequest::MayCompletePrepare() { | 1212 void BatchUploadRequest::MayCompletePrepare() { |
| 1074 if (!committed_ || prepare_callback_.is_null()) | 1213 if (!committed_ || prepare_callback_.is_null()) |
| 1075 return; | 1214 return; |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 1097 method = "PUT"; | 1236 method = "PUT"; |
| 1098 break; | 1237 break; |
| 1099 default: | 1238 default: |
| 1100 NOTREACHED(); | 1239 NOTREACHED(); |
| 1101 break; | 1240 break; |
| 1102 } | 1241 } |
| 1103 | 1242 |
| 1104 parts.push_back(ContentTypeAndData()); | 1243 parts.push_back(ContentTypeAndData()); |
| 1105 parts.back().type = kHttpContentType; | 1244 parts.back().type = kHttpContentType; |
| 1106 parts.back().data = base::StringPrintf( | 1245 parts.back().data = base::StringPrintf( |
| 1107 kBatchUploadRequestFormat, | 1246 kBatchUploadRequestFormat, method.c_str(), url.path().c_str(), |
| 1108 method.c_str(), | 1247 url_generator_.GetBatchUploadUrl().host().c_str(), type.c_str(), |
| 1109 url.path().c_str(), | |
| 1110 url_generator_.GetBatchUploadUrl().host().c_str(), | |
| 1111 type.c_str(), | |
| 1112 data.c_str()); | 1248 data.c_str()); |
| 1113 } | 1249 } |
| 1114 | 1250 |
| 1115 GenerateMultipartBody(MULTIPART_MIXED, boundary_, parts, &upload_content_); | 1251 GenerateMultipartBody(MULTIPART_MIXED, boundary_, parts, &upload_content_); |
| 1116 prepare_callback_.Run(HTTP_SUCCESS); | 1252 prepare_callback_.Run(HTTP_SUCCESS); |
| 1117 } | 1253 } |
| 1118 | 1254 |
| 1119 bool BatchUploadRequest::GetContentData(std::string* upload_content_type, | 1255 bool BatchUploadRequest::GetContentData(std::string* upload_content_type, |
| 1120 std::string* upload_content_data) { | 1256 std::string* upload_content_data) { |
| 1121 upload_content_type->assign(upload_content_.type); | 1257 upload_content_type->assign(upload_content_.type); |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 1142 return headers; | 1278 return headers; |
| 1143 } | 1279 } |
| 1144 | 1280 |
| 1145 void BatchUploadRequest::ProcessURLFetchResults(const net::URLFetcher* source) { | 1281 void BatchUploadRequest::ProcessURLFetchResults(const net::URLFetcher* source) { |
| 1146 if (!IsSuccessfulDriveApiErrorCode(GetErrorCode())) { | 1282 if (!IsSuccessfulDriveApiErrorCode(GetErrorCode())) { |
| 1147 RunCallbackOnPrematureFailure(GetErrorCode()); | 1283 RunCallbackOnPrematureFailure(GetErrorCode()); |
| 1148 sender_->RequestFinished(this); | 1284 sender_->RequestFinished(this); |
| 1149 return; | 1285 return; |
| 1150 } | 1286 } |
| 1151 | 1287 |
| 1152 for (auto& child : child_requests_) { | 1288 std::string content_type; |
| 1153 // TODO(hirono): Split the mutlipart result and return the correct code and | 1289 source->GetResponseHeaders()->EnumerateHeader( |
| 1154 // body. | 1290 /* need only first header */ NULL, "Content-Type", &content_type); |
| 1155 child.request->ProcessURLFetchResults(HTTP_SERVICE_UNAVAILABLE, ""); | 1291 std::string boundary; |
| 1156 // Request deletes itself after processing. | 1292 if (!GetAfterPrefix(content_type.begin(), content_type.end(), |
| 1293 kMultipartMixedMimeTypePrefix, &boundary)) { | |
|
kinaba
2015/04/27 08:09:37
According to the RFC boundary field can be quoted
hirono
2015/04/27 12:32:45
Done.
| |
| 1294 RunCallbackOnPrematureFailure(DRIVE_PARSE_ERROR); | |
| 1295 sender_->RequestFinished(this); | |
| 1296 return; | |
| 1297 } | |
| 1298 | |
| 1299 std::vector<MultipartHttpResponse> parts; | |
| 1300 if (!ParseMultipartResponse(boundary, response_writer()->data(), &parts) || | |
| 1301 child_requests_.size() != parts.size()) { | |
| 1302 RunCallbackOnPrematureFailure(DRIVE_PARSE_ERROR); | |
| 1303 sender_->RequestFinished(this); | |
| 1304 return; | |
| 1305 } | |
| 1306 | |
| 1307 for (size_t i = 0; i < parts.size(); ++i) { | |
| 1308 child_requests_[i].request->ProcessURLFetchResults(parts[i].code, | |
| 1309 parts[i].body); | |
| 1157 } | 1310 } |
| 1158 | 1311 |
| 1159 child_requests_.clear(); | 1312 child_requests_.clear(); |
| 1313 sender_->RequestFinished(this); | |
| 1160 } | 1314 } |
| 1161 | 1315 |
| 1162 void BatchUploadRequest::RunCallbackOnPrematureFailure(DriveApiErrorCode code) { | 1316 void BatchUploadRequest::RunCallbackOnPrematureFailure(DriveApiErrorCode code) { |
| 1163 for (auto child : child_requests_) { | 1317 for (auto child : child_requests_) { |
| 1164 child.request->RunCallbackOnPrematureFailure(code); | 1318 child.request->RunCallbackOnPrematureFailure(code); |
| 1165 sender_->RequestFinished(child.request); | 1319 sender_->RequestFinished(child.request); |
| 1166 } | 1320 } |
| 1167 child_requests_.clear(); | 1321 child_requests_.clear(); |
| 1168 } | 1322 } |
| 1169 | 1323 |
| 1170 } // namespace drive | 1324 } // namespace drive |
| 1171 } // namespace google_apis | 1325 } // namespace google_apis |
| OLD | NEW |