Fix file upload error when remote attachment has no stored hash
This commit is contained in:
		
					parent
					
						
							
								90a3013802
							
						
					
				
			
			
				commit
				
					
						c9694e93b0
					
				
			
		
					 2 changed files with 112 additions and 29 deletions
				
			
		| 
						 | 
				
			
			@ -374,33 +374,49 @@ Zotero.Sync.Storage.Mode.ZFS.prototype = {
 | 
			
		|||
		}
 | 
			
		||||
		body = body.join('&');
 | 
			
		||||
		
 | 
			
		||||
		try {
 | 
			
		||||
			var req = yield this.apiClient.makeRequest(
 | 
			
		||||
				"POST",
 | 
			
		||||
				uri,
 | 
			
		||||
				{
 | 
			
		||||
					body,
 | 
			
		||||
					headers,
 | 
			
		||||
					// This should include all errors in _handleUploadAuthorizationFailure()
 | 
			
		||||
					successCodes: [200, 201, 204, 403, 404, 412, 413],
 | 
			
		||||
					debug: true
 | 
			
		||||
				}
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
		catch (e) {
 | 
			
		||||
			if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
 | 
			
		||||
				let msg = "Unexpected status code " + e.status + " in " + funcName
 | 
			
		||||
					 + " (" + item.libraryKey + ")";
 | 
			
		||||
				Zotero.logError(msg);
 | 
			
		||||
				Zotero.debug(e.xmlhttp.getAllResponseHeaders());
 | 
			
		||||
				throw new Error(Zotero.Sync.Storage.defaultError);
 | 
			
		||||
		var req;
 | 
			
		||||
		while (true) {
 | 
			
		||||
			try {
 | 
			
		||||
				req = yield this.apiClient.makeRequest(
 | 
			
		||||
					"POST",
 | 
			
		||||
					uri,
 | 
			
		||||
					{
 | 
			
		||||
						body,
 | 
			
		||||
						headers,
 | 
			
		||||
						// This should include all errors in _handleUploadAuthorizationFailure()
 | 
			
		||||
						successCodes: [200, 201, 204, 403, 404, 412, 413],
 | 
			
		||||
						debug: true
 | 
			
		||||
					}
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
			catch (e) {
 | 
			
		||||
				if (e instanceof Zotero.HTTP.UnexpectedStatusException) {
 | 
			
		||||
					let msg = "Unexpected status code " + e.status + " in " + funcName
 | 
			
		||||
						 + " (" + item.libraryKey + ")";
 | 
			
		||||
					Zotero.logError(msg);
 | 
			
		||||
					Zotero.debug(e.xmlhttp.getAllResponseHeaders());
 | 
			
		||||
					throw new Error(Zotero.Sync.Storage.defaultError);
 | 
			
		||||
				}
 | 
			
		||||
				throw e;
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			let result = yield this._handleUploadAuthorizationFailure(req, item);
 | 
			
		||||
			if (result instanceof Zotero.Sync.Storage.Result) {
 | 
			
		||||
				return result;
 | 
			
		||||
			}
 | 
			
		||||
			// If remote attachment exists but has no hash (which can happen for an old (pre-4.0?)
 | 
			
		||||
			// attachment with just an mtime, or after a storage purge), send again with If-None-Match
 | 
			
		||||
			else if (result == "ERROR_412_WITHOUT_VERSION") {
 | 
			
		||||
				if (headers["If-None-Match"]) {
 | 
			
		||||
					throw new Error("412 returned for request with If-None-Match");
 | 
			
		||||
				}
 | 
			
		||||
				delete headers["If-Match"];
 | 
			
		||||
				headers["If-None-Match"] = "*";
 | 
			
		||||
				Zotero.debug("Retrying with If-None-Match");
 | 
			
		||||
			}
 | 
			
		||||
			else {
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			throw e;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		var result = yield this._handleUploadAuthorizationFailure(req, item);
 | 
			
		||||
		if (result instanceof Zotero.Sync.Storage.Result) {
 | 
			
		||||
			return result;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		try {
 | 
			
		||||
| 
						 | 
				
			
			@ -468,10 +484,9 @@ Zotero.Sync.Storage.Mode.ZFS.prototype = {
 | 
			
		|||
			throw new Error(Zotero.Sync.Storage.defaultError);
 | 
			
		||||
		}
 | 
			
		||||
		else if (req.status == 412) {
 | 
			
		||||
			Zotero.debug("412 BUT WE'RE COOL");
 | 
			
		||||
			let version = req.getResponseHeader('Last-Modified-Version');
 | 
			
		||||
			if (!version) {
 | 
			
		||||
				throw new Error("Last-Modified-Version header not provided");
 | 
			
		||||
				return "ERROR_412_WITHOUT_VERSION";
 | 
			
		||||
			}
 | 
			
		||||
			if (version > item.version) {
 | 
			
		||||
				return new Zotero.Sync.Storage.Result({
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -817,7 +817,75 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
 | 
			
		|||
			assert.isFalse(result.localChanges);
 | 
			
		||||
			assert.isFalse(result.remoteChanges);
 | 
			
		||||
			assert.isTrue(result.syncRequired);
 | 
			
		||||
		})
 | 
			
		||||
		});
 | 
			
		||||
		
 | 
			
		||||
		it("should retry with If-None-Match on 412 with missing remote hash", function* () {
 | 
			
		||||
			var { engine, client, caller } = yield setup();
 | 
			
		||||
			var zfs = new Zotero.Sync.Storage.Mode.ZFS({
 | 
			
		||||
				apiClient: client
 | 
			
		||||
			})
 | 
			
		||||
			
 | 
			
		||||
			var file = getTestDataDirectory();
 | 
			
		||||
			file.append('test.png');
 | 
			
		||||
			var item = yield Zotero.Attachments.importFromFile({ file });
 | 
			
		||||
			item.version = 5;
 | 
			
		||||
			item.synced = true;
 | 
			
		||||
			item.attachmentSyncedModificationTime = Date.now();
 | 
			
		||||
			item.attachmentSyncedHash = 'bd4c33e03798a7e8bc0b46f8bda74fac'
 | 
			
		||||
			yield item.saveTx();
 | 
			
		||||
			
 | 
			
		||||
			var contentType = 'image/png';
 | 
			
		||||
			var prefix = Zotero.Utilities.randomString();
 | 
			
		||||
			var suffix = Zotero.Utilities.randomString();
 | 
			
		||||
			var uploadKey = Zotero.Utilities.randomString(32, 'abcdef0123456789');
 | 
			
		||||
			
 | 
			
		||||
			var called = 0;
 | 
			
		||||
			server.respond(function (req) {
 | 
			
		||||
				if (req.method == "POST"
 | 
			
		||||
						&& req.url == `${baseURL}users/1/items/${item.key}/file`
 | 
			
		||||
						&& !req.requestBody.includes('upload=')
 | 
			
		||||
						&& req.requestHeaders["If-Match"] == item.attachmentSyncedHash) {
 | 
			
		||||
					called++;
 | 
			
		||||
					req.respond(
 | 
			
		||||
						412,
 | 
			
		||||
						{
 | 
			
		||||
							"Content-Type": "application/json"
 | 
			
		||||
						},
 | 
			
		||||
						"If-Match set but file does not exist"
 | 
			
		||||
					);
 | 
			
		||||
				}
 | 
			
		||||
				else if (req.method == "POST"
 | 
			
		||||
						&& req.url == `${baseURL}users/1/items/${item.key}/file`
 | 
			
		||||
						&& !req.requestBody.includes('upload=')
 | 
			
		||||
						&& req.requestHeaders["If-None-Match"] == "*") {
 | 
			
		||||
					assert.equal(called++, 1)
 | 
			
		||||
					req.respond(
 | 
			
		||||
						200,
 | 
			
		||||
						{
 | 
			
		||||
							"Content-Type": "application/json"
 | 
			
		||||
						},
 | 
			
		||||
						JSON.stringify({
 | 
			
		||||
							url: baseURL + "pretend-s3/1",
 | 
			
		||||
							contentType: contentType,
 | 
			
		||||
							prefix: prefix,
 | 
			
		||||
							suffix: suffix,
 | 
			
		||||
							uploadKey: uploadKey
 | 
			
		||||
						})
 | 
			
		||||
					);
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
			
 | 
			
		||||
			var stub = sinon.stub(zfs, "_uploadFile");
 | 
			
		||||
			
 | 
			
		||||
			yield zfs._processUploadFile({
 | 
			
		||||
				name: item.libraryKey
 | 
			
		||||
			});
 | 
			
		||||
			
 | 
			
		||||
			assert.equal(called, 2);
 | 
			
		||||
			assert.ok(stub.called);
 | 
			
		||||
			
 | 
			
		||||
			stub.restore();
 | 
			
		||||
		});
 | 
			
		||||
		
 | 
			
		||||
		it("should handle 413 on quota limit", function* () {
 | 
			
		||||
			var { engine, client, caller } = yield setup();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue