1 // Copyright (c) 2013 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.
13 #include "json/reader.h"
14 #include "json/writer.h"
15 #include "ppapi/c/pp_errors.h"
16 #include "ppapi/cpp/completion_callback.h"
17 #include "ppapi/cpp/instance.h"
18 #include "ppapi/cpp/module.h"
19 #include "ppapi/cpp/url_loader.h"
20 #include "ppapi/cpp/url_request_info.h"
21 #include "ppapi/cpp/url_response_info.h"
22 #include "ppapi/cpp/var.h"
23 #include "ppapi/utility/completion_callback_factory.h"
24 #include "ppapi/utility/threading/simple_thread.h"
28 // When we upload files, we also upload the metadata at the same time. To do so,
29 // we use the mimetype multipart/related. This mimetype requires specifying a
30 // boundary between the JSON metadata and the file content.
31 const char kBoundary
[] = "NACL_BOUNDARY_600673";
33 // This is a simple implementation of JavaScript's encodeUriComponent. We
34 // assume the data is already UTF-8. See
35 // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent.
36 std::string
EncodeUriComponent(const std::string
& s
) {
37 char hex
[] = "0123456789ABCDEF";
39 for (size_t i
= 0; i
< s
.length(); ++i
) {
41 if (isalpha(c
) || isdigit(c
) || strchr("-_.!~*'()", c
)) {
45 result
+= hex
[(c
>> 4) & 0xf];
46 result
+= hex
[c
& 0xf];
52 std::string
IntToString(int x
) {
54 snprintf(&buffer
[0], 32, "%d", x
);
58 void AddQueryParameter(std::string
* s
,
59 const std::string
& key
,
60 const std::string
& value
,
62 *s
+= first
? '?' : '&';
63 *s
+= EncodeUriComponent(key
);
65 *s
+= EncodeUriComponent(value
);
68 void AddQueryParameter(std::string
* s
,
69 const std::string
& key
,
72 AddQueryParameter(s
, key
, IntToString(value
), first
);
75 void AddAuthTokenHeader(std::string
* s
, const std::string
& auth_token
) {
76 *s
+= "Authorization: Bearer ";
81 void AddHeader(std::string
* s
, const char* key
, const std::string
& value
) {
93 struct ReadUrlParams
{
96 std::string request_headers
;
97 std::string request_body
;
100 // This function blocks so it needs to be called off the main thread.
101 int32_t ReadUrl(pp::Instance
* instance
,
102 const ReadUrlParams
& params
,
103 std::string
* output
) {
104 pp::URLRequestInfo
url_request(instance
);
105 pp::URLLoader
url_loader(instance
);
107 url_request
.SetURL(params
.url
);
108 url_request
.SetMethod(params
.method
);
109 url_request
.SetHeaders(params
.request_headers
);
110 url_request
.SetRecordDownloadProgress(true);
111 if (params
.request_body
.size()) {
112 url_request
.AppendDataToBody(params
.request_body
.data(),
113 params
.request_body
.size());
116 int32_t result
= url_loader
.Open(url_request
, pp::BlockUntilComplete());
117 if (result
!= PP_OK
) {
121 pp::URLResponseInfo url_response
= url_loader
.GetResponseInfo();
122 if (url_response
.GetStatusCode() != 200)
123 return PP_ERROR_FAILED
;
127 int64_t bytes_received
= 0;
128 int64_t total_bytes_to_be_received
= 0;
129 if (url_loader
.GetDownloadProgress(&bytes_received
,
130 &total_bytes_to_be_received
)) {
131 if (total_bytes_to_be_received
> 0) {
132 output
->reserve(total_bytes_to_be_received
);
136 url_request
.SetRecordDownloadProgress(false);
138 const int32_t kReadBufferSize
= 16 * 1024;
139 uint8_t* buffer_
= new uint8_t[kReadBufferSize
];
142 result
= url_loader
.ReadResponseBody(
143 buffer_
, kReadBufferSize
, pp::BlockUntilComplete());
145 assert(result
<= kReadBufferSize
);
146 size_t num_bytes
= result
;
147 output
->insert(output
->end(), buffer_
, buffer_
+ num_bytes
);
149 } while (result
> 0);
159 // This is a simplistic implementation of the files.list method defined here:
160 // https://developers.google.com/drive/v2/reference/files/list
162 struct ListFilesParams
{
164 std::string page_token
;
168 int32_t ListFiles(pp::Instance
* instance
,
169 const std::string
& auth_token
,
170 const ListFilesParams
& params
,
172 static const char base_url
[] = "https://www.googleapis.com/drive/v2/files";
177 AddQueryParameter(&p
.url
, "maxResults", params
.max_results
, true);
178 if (params
.page_token
.length())
179 AddQueryParameter(&p
.url
, "pageToken", params
.page_token
, false);
180 AddQueryParameter(&p
.url
, "q", params
.query
, false);
181 // Request a "partial response". See
182 // https://developers.google.com/drive/performance#partial for more
184 AddQueryParameter(&p
.url
, "fields", "items(id,downloadUrl)", false);
185 AddAuthTokenHeader(&p
.request_headers
, auth_token
);
188 int32_t result
= ReadUrl(instance
, p
, &output
);
189 if (result
!= PP_OK
) {
193 Json::Reader
reader(Json::Features::strictMode());
194 if (!reader
.parse(output
, *root
, false)) {
195 return PP_ERROR_FAILED
;
204 // This is a simplistic implementation of the files.update and files.insert
205 // methods defined here:
206 // https://developers.google.com/drive/v2/reference/files/insert
207 // https://developers.google.com/drive/v2/reference/files/update
209 struct InsertFileParams
{
210 // If file_id is empty, create a new file (files.insert). If file_id is not
211 // empty, update that file (files.update)
214 std::string description
;
215 std::string mime_type
;
219 std::string
BuildRequestBody(const InsertFileParams
& params
) {
220 // This generates the multipart-upload request body for InsertFile. See
221 // https://developers.google.com/drive/manage-uploads#multipart for more
226 result
+= "\nContent-Type: application/json; charset=UTF-8\n\n";
228 Json::Value
value(Json::objectValue
);
229 if (!params
.description
.empty())
230 value
["description"] = Json::Value(params
.description
);
232 if (!params
.mime_type
.empty())
233 value
["mimeType"] = Json::Value(params
.mime_type
);
235 if (!params
.title
.empty())
236 value
["title"] = Json::Value(params
.title
);
238 Json::FastWriter writer
;
239 std::string metadata
= writer
.write(value
);
244 result
+= "\nContent-Type: ";
245 result
+= params
.mime_type
;
247 result
+= params
.content
;
254 int32_t InsertFile(pp::Instance
* instance
,
255 const std::string
& auth_token
,
256 const InsertFileParams
& params
,
258 static const char base_url
[] =
259 "https://www.googleapis.com/upload/drive/v2/files";
264 // If file_id is defined, we are actually updating an existing file.
265 if (!params
.file_id
.empty()) {
267 p
.url
+= params
.file_id
;
273 // We always use the multipart upload interface, but see
274 // https://developers.google.com/drive/manage-uploads for other
276 AddQueryParameter(&p
.url
, "uploadType", "multipart", true);
277 // Request a "partial response". See
278 // https://developers.google.com/drive/performance#partial for more
280 AddQueryParameter(&p
.url
, "fields", "id,downloadUrl", false);
281 AddAuthTokenHeader(&p
.request_headers
, auth_token
);
282 AddHeader(&p
.request_headers
,
284 std::string("multipart/related; boundary=") + kBoundary
+ "\n");
285 p
.request_body
= BuildRequestBody(params
);
288 int32_t result
= ReadUrl(instance
, p
, &output
);
289 if (result
!= PP_OK
) {
293 Json::Reader
reader(Json::Features::strictMode());
294 if (!reader
.parse(output
, *root
, false)) {
295 return PP_ERROR_FAILED
;
304 class Instance
: public pp::Instance
{
306 Instance(PP_Instance instance
);
307 virtual bool Init(uint32_t argc
, const char* argn
[], const char* argv
[]);
308 virtual void HandleMessage(const pp::Var
& var_message
);
310 void PostMessagef(const char* format
, ...);
313 void ThreadSetAuthToken(int32_t, const std::string
& auth_token
);
314 void ThreadRequestThunk(int32_t);
315 bool ThreadRequest();
316 bool ThreadGetFileMetadata(const char* title
, Json::Value
* metadata
);
317 bool ThreadCreateFile(const char* title
,
318 const char* description
,
320 Json::Value
* metadata
);
321 bool ThreadUpdateFile(const std::string
& file_id
,
322 const std::string
& content
,
323 Json::Value
* metadata
);
324 bool ThreadDownloadFile(const Json::Value
& metadata
, std::string
* output
);
325 bool GetMetadataKey(const Json::Value
& metadata
,
327 std::string
* output
);
329 pp::SimpleThread worker_thread_
;
330 pp::CompletionCallbackFactory
<Instance
> callback_factory_
;
331 std::string auth_token_
;
332 bool is_processing_request_
;
335 Instance::Instance(PP_Instance instance
)
336 : pp::Instance(instance
),
337 worker_thread_(this),
338 callback_factory_(this),
339 is_processing_request_(false) {}
341 bool Instance::Init(uint32_t /*argc*/,
342 const char * [] /*argn*/,
343 const char * [] /*argv*/) {
344 worker_thread_
.Start();
348 void Instance::HandleMessage(const pp::Var
& var_message
) {
349 const char kTokenMessage
[] = "token:";
350 const size_t kTokenMessageLen
= strlen(kTokenMessage
);
351 const char kGetFileMessage
[] = "getFile";
353 if (!var_message
.is_string()) {
357 std::string message
= var_message
.AsString();
358 printf("Got message: \"%s\"\n", message
.c_str());
359 if (message
.compare(0, kTokenMessageLen
, kTokenMessage
) == 0) {
361 std::string auth_token
= message
.substr(kTokenMessageLen
);
362 worker_thread_
.message_loop().PostWork(callback_factory_
.NewCallback(
363 &Instance::ThreadSetAuthToken
, auth_token
));
364 } else if (message
== kGetFileMessage
) {
366 if (!is_processing_request_
) {
367 is_processing_request_
= true;
368 worker_thread_
.message_loop().PostWork(
369 callback_factory_
.NewCallback(&Instance::ThreadRequestThunk
));
374 void Instance::PostMessagef(const char* format
, ...) {
375 const size_t kBufferSize
= 1024;
376 char buffer
[kBufferSize
];
378 va_start(args
, format
);
379 vsnprintf(&buffer
[0], kBufferSize
, format
, args
);
384 void Instance::ThreadSetAuthToken(int32_t /*result*/,
385 const std::string
& auth_token
) {
386 printf("Got auth token: %s\n", auth_token
.c_str());
387 auth_token_
= auth_token
;
390 void Instance::ThreadRequestThunk(int32_t /*result*/) {
392 is_processing_request_
= false;
395 bool Instance::ThreadRequest() {
396 static int request_count
= 0;
397 static const char kTitle
[] = "hello nacl.txt";
398 Json::Value metadata
;
401 PostMessagef("log:\n Got request (#%d).\n", ++request_count
);
402 PostMessagef("log: Looking for file: \"%s\".\n", kTitle
);
404 if (!ThreadGetFileMetadata(kTitle
, &metadata
)) {
405 PostMessage("log: Not found! Creating a new file...\n");
406 // No data found, write a new file.
407 static const char kDescription
[] = "A file generated by NaCl!";
408 static const char kInitialContent
[] = "Hello, Google Drive!";
410 if (!ThreadCreateFile(kTitle
, kDescription
, kInitialContent
, &metadata
)) {
411 PostMessage("log: Creating the new file failed...\n");
415 PostMessage("log: Found it! Downloading the file...\n");
416 // Found the file, download it's data.
417 if (!ThreadDownloadFile(metadata
, &output
)) {
418 PostMessage("log: Downloading the file failed...\n");
423 output
+= "\nHello, again Google Drive!";
426 if (!GetMetadataKey(metadata
, "id", &file_id
)) {
427 PostMessage("log: Couldn't find the file id...\n");
431 PostMessage("log: Updating the file...\n");
432 if (!ThreadUpdateFile(file_id
, output
, &metadata
)) {
433 PostMessage("log: Failed to update the file...\n");
438 PostMessage("log: Done!\n");
439 PostMessage("log: Downloading the newly written file...\n");
440 if (!ThreadDownloadFile(metadata
, &output
)) {
441 PostMessage("log: Downloading the file failed...\n");
445 PostMessage("log: Done!\n");
450 bool Instance::ThreadGetFileMetadata(const char* title
, Json::Value
* metadata
) {
453 p
.query
= "title = \'";
458 int32_t result
= ListFiles(this, auth_token_
, p
, &root
);
459 if (result
!= PP_OK
) {
460 PostMessagef("log: ListFiles failed with result %d\n", result
);
464 // Extract the first item's metadata.
465 if (!root
.isMember("items")) {
466 PostMessage("log: ListFiles returned no items...\n");
470 Json::Value items
= root
["items"];
471 if (!items
.isValidIndex(0)) {
472 PostMessage("log: Expected items[0] to be valid.\n");
476 *metadata
= items
[0U];
480 bool Instance::ThreadCreateFile(const char* title
,
481 const char* description
,
483 Json::Value
* metadata
) {
486 p
.description
= description
;
487 p
.mime_type
= "text/plain";
490 int32_t result
= InsertFile(this, auth_token_
, p
, metadata
);
491 if (result
!= PP_OK
) {
492 PostMessagef("log: Creating file failed with result %d\n", result
);
499 bool Instance::ThreadUpdateFile(const std::string
& file_id
,
500 const std::string
& content
,
501 Json::Value
* metadata
) {
505 p
.mime_type
= "text/plain";
507 int32_t result
= InsertFile(this, auth_token_
, p
, metadata
);
508 if (result
!= PP_OK
) {
509 PostMessagef("log: Updating file failed with result %d\n", result
);
516 bool Instance::ThreadDownloadFile(const Json::Value
& metadata
,
517 std::string
* output
) {
521 if (!GetMetadataKey(metadata
, "downloadUrl", &p
.url
)) {
525 AddAuthTokenHeader(&p
.request_headers
, auth_token_
);
527 int32_t result
= ReadUrl(this, p
, output
);
528 if (result
!= PP_OK
) {
529 PostMessagef("log: Downloading failed with result %d\n", result
);
536 bool Instance::GetMetadataKey(const Json::Value
& metadata
,
538 std::string
* output
) {
539 Json::Value value
= metadata
[key
];
540 if (!value
.isString()) {
541 PostMessagef("log: Expected metadata.%s to be a string.\n", key
);
545 *output
= value
.asString();
549 class Module
: public pp::Module
{
551 Module() : pp::Module() {}
554 virtual pp::Instance
* CreateInstance(PP_Instance instance
) {
555 return new Instance(instance
);
561 Module
* CreateModule() { return new ::Module(); }