1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "backends/cloud/box/boxuploadrequest.h"
24 #include "backends/cloud/box/boxstorage.h"
25 #include "backends/cloud/box/boxtokenrefresher.h"
26 #include "backends/cloud/iso8601.h"
27 #include "backends/cloud/storage.h"
28 #include "backends/networking/curl/connectionmanager.h"
29 #include "backends/networking/curl/curljsonrequest.h"
30 #include "backends/networking/curl/networkreadstream.h"
31 #include "common/json.h"
32 
33 namespace Cloud {
34 namespace Box {
35 
36 #define BOX_API_FILES "https://upload.box.com/api/2.0/files"
37 
BoxUploadRequest(BoxStorage * storage,Common::String path,Common::String localPath,Storage::UploadCallback callback,Networking::ErrorCallback ecb)38 BoxUploadRequest::BoxUploadRequest(BoxStorage *storage, Common::String path, Common::String localPath, Storage::UploadCallback callback, Networking::ErrorCallback ecb):
39 	Networking::Request(nullptr, ecb), _storage(storage), _savePath(path), _localPath(localPath), _uploadCallback(callback),
40 	_workingRequest(nullptr), _ignoreCallback(false) {
41 	start();
42 }
43 
~BoxUploadRequest()44 BoxUploadRequest::~BoxUploadRequest() {
45 	_ignoreCallback = true;
46 	if (_workingRequest)
47 		_workingRequest->finish();
48 	delete _uploadCallback;
49 }
50 
start()51 void BoxUploadRequest::start() {
52 	_ignoreCallback = true;
53 	if (_workingRequest)
54 		_workingRequest->finish();
55 	_resolvedId = ""; //used to update file contents
56 	_parentId = ""; //used to create file within parent directory
57 	_ignoreCallback = false;
58 
59 	resolveId();
60 }
61 
resolveId()62 void BoxUploadRequest::resolveId() {
63 	//check whether such file already exists
64 	Storage::UploadCallback innerCallback = new Common::Callback<BoxUploadRequest, Storage::UploadResponse>(this, &BoxUploadRequest::idResolvedCallback);
65 	Networking::ErrorCallback innerErrorCallback = new Common::Callback<BoxUploadRequest, Networking::ErrorResponse>(this, &BoxUploadRequest::idResolveFailedCallback);
66 	_workingRequest = _storage->resolveFileId(_savePath, innerCallback, innerErrorCallback);
67 }
68 
idResolvedCallback(Storage::UploadResponse response)69 void BoxUploadRequest::idResolvedCallback(Storage::UploadResponse response) {
70 	_workingRequest = nullptr;
71 	if (_ignoreCallback) return;
72 	_resolvedId = response.value.id();
73 	upload();
74 }
75 
idResolveFailedCallback(Networking::ErrorResponse error)76 void BoxUploadRequest::idResolveFailedCallback(Networking::ErrorResponse error) {
77 	_workingRequest = nullptr;
78 	if (_ignoreCallback) return;
79 
80 	//not resolved => error or no such file
81 	if (error.response.contains("no such file found in its parent directory")) {
82 		//parent's id after the '\n'
83 		Common::String parentId = error.response;
84 		for (uint32 i = 0; i < parentId.size(); ++i)
85 			if (parentId[i] == '\n') {
86 				parentId.erase(0, i + 1);
87 				break;
88 			}
89 
90 		_parentId = parentId;
91 		upload();
92 		return;
93 	}
94 
95 	finishError(error);
96 }
97 
upload()98 void BoxUploadRequest::upload() {
99 	Common::String name = _savePath;
100 	for (uint32 i = name.size(); i > 0; --i) {
101 		if (name[i - 1] == '/' || name[i - 1] == '\\') {
102 			name.erase(0, i);
103 			break;
104 		}
105 	}
106 
107 	Common::String url = BOX_API_FILES;
108 	if (_resolvedId != "")
109 		url += "/" + _resolvedId;
110 	url += "/content";
111 	Networking::JsonCallback callback = new Common::Callback<BoxUploadRequest, Networking::JsonResponse>(this, &BoxUploadRequest::uploadedCallback);
112 	Networking::ErrorCallback failureCallback = new Common::Callback<BoxUploadRequest, Networking::ErrorResponse>(this, &BoxUploadRequest::notUploadedCallback);
113 	Networking::CurlJsonRequest *request = new BoxTokenRefresher(_storage, callback, failureCallback, url.c_str());
114 	request->addHeader("Authorization: Bearer " + _storage->accessToken());
115 
116 	Common::JSONObject jsonRequestParameters;
117 	if (_resolvedId == "") {
118 		Common::JSONObject parentObject;
119 		parentObject.setVal("id", new Common::JSONValue(_parentId));
120 		jsonRequestParameters.setVal("parent", new Common::JSONValue(parentObject));
121 		jsonRequestParameters.setVal("name", new Common::JSONValue(name));
122 	}
123 
124 	Common::JSONValue value(jsonRequestParameters);
125 	request->addFormField("attributes", Common::JSON::stringify(&value));
126 	request->addFormFile("file", _localPath);
127 
128 	_workingRequest = ConnMan.addRequest(request);
129 }
130 
uploadedCallback(Networking::JsonResponse response)131 void BoxUploadRequest::uploadedCallback(Networking::JsonResponse response) {
132 	_workingRequest = nullptr;
133 	if (_ignoreCallback) return;
134 
135 	Networking::ErrorResponse error(this, false, true, "", -1);
136 	Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
137 	if (rq) {
138 		const Networking::NetworkReadStream *stream = rq->getNetworkReadStream();
139 		if (stream) {
140 			long code = stream->httpResponseCode();
141 			error.httpResponseCode = code;
142 		}
143 	}
144 
145 	if (error.httpResponseCode != 200 && error.httpResponseCode != 201)
146 		warning("BoxUploadRequest: looks like an error (bad HTTP code)");
147 
148 	//check JSON and show warnings if it's malformed
149 	Common::JSONValue *json = response.value;
150 	if (json == nullptr) {
151 		error.response = "Failed to parse JSON, null passed!";
152 		finishError(error);
153 		return;
154 	}
155 
156 	if (!json->isObject()) {
157 		error.response = "Passed JSON is not an object!";
158 		finishError(error);
159 		delete json;
160 		return;
161 	}
162 
163 	Common::JSONObject object = json->asObject();
164 	if (Networking::CurlJsonRequest::jsonContainsArray(object, "entries", "BoxUploadRequest")) {
165 		Common::JSONArray entries = object.getVal("entries")->asArray();
166 		if (entries.size() == 0) {
167 			warning("BoxUploadRequest: 'entries' found, but it's empty");
168 		} else if (!Networking::CurlJsonRequest::jsonIsObject(entries[0], "BoxUploadRequest")) {
169 			warning("BoxUploadRequest: 'entries' first item is not an object");
170 		} else {
171 			Common::JSONObject item = entries[0]->asObject();
172 
173 			if (Networking::CurlJsonRequest::jsonContainsString(item, "id", "BoxUploadRequest") &&
174 				Networking::CurlJsonRequest::jsonContainsString(item, "name", "BoxUploadRequest") &&
175 				Networking::CurlJsonRequest::jsonContainsString(item, "type", "BoxUploadRequest") &&
176 				Networking::CurlJsonRequest::jsonContainsString(item, "modified_at", "BoxUploadRequest") &&
177 				Networking::CurlJsonRequest::jsonContainsStringOrIntegerNumber(item, "size", "BoxUploadRequest")) {
178 
179 				//finished
180 				Common::String id = item.getVal("id")->asString();
181 				Common::String name = item.getVal("name")->asString();
182 				bool isDirectory = (item.getVal("type")->asString() == "folder");
183 				uint32 size;
184 				if (item.getVal("size")->isString()) {
185 					size = item.getVal("size")->asString().asUint64();
186 				} else {
187 					size = item.getVal("size")->asIntegerNumber();
188 				}
189 				uint32 timestamp = ISO8601::convertToTimestamp(item.getVal("modified_at")->asString());
190 
191 				finishUpload(StorageFile(id, _savePath, name, size, timestamp, isDirectory));
192 				delete json;
193 				return;
194 			}
195 		}
196 	}
197 
198 	//TODO: check errors
199 	/*
200 	if (object.contains("error")) {
201 		warning("Box returned error: %s", json->stringify(true).c_str());
202 		delete json;
203 		error.response = json->stringify(true);
204 		finishError(error);
205 		return;
206 	}
207 	*/
208 
209 	warning("BoxUploadRequest: no file info to return");
210 	finishUpload(StorageFile(_savePath, 0, 0, false));
211 
212 	delete json;
213 }
214 
notUploadedCallback(Networking::ErrorResponse error)215 void BoxUploadRequest::notUploadedCallback(Networking::ErrorResponse error) {
216 	_workingRequest = nullptr;
217 	if (_ignoreCallback) return;
218 	finishError(error);
219 }
220 
handle()221 void BoxUploadRequest::handle() {}
222 
restart()223 void BoxUploadRequest::restart() { start(); }
224 
finishUpload(StorageFile file)225 void BoxUploadRequest::finishUpload(StorageFile file) {
226 	Request::finishSuccess();
227 	if (_uploadCallback)
228 		(*_uploadCallback)(Storage::UploadResponse(this, file));
229 }
230 
231 } // End of namespace Box
232 } // End of namespace Cloud
233