diff --git a/chrome/content/zotero/xpcom/attachments.js b/chrome/content/zotero/xpcom/attachments.js
index 2284db05b0..9460cd2759 100644
--- a/chrome/content/zotero/xpcom/attachments.js
+++ b/chrome/content/zotero/xpcom/attachments.js
@@ -1432,6 +1432,12 @@ Zotero.Attachments = new function(){
? elem.getAttribute(attribute)
: elem.textContent;
if (!val) return [];
+
+ // Handle relative paths
+ val = Services.io.newURI(
+ val, null, Services.io.newURI(url)
+ ).spec;
+
return [{
accessMethod: name,
url: val,
diff --git a/test/tests/attachmentsTest.js b/test/tests/attachmentsTest.js
index 1ae99d3e73..9cfd9ebc6a 100644
--- a/test/tests/attachmentsTest.js
+++ b/test/tests/attachmentsTest.js
@@ -835,6 +835,24 @@ describe("Zotero.Attachments", function() {
Zotero.File.pathToFile(OS.Path.join(getTestDataDirectory().path, 'test.pdf'))
);
+ // Generate a page with a relative PDF URL
+ httpd.registerPathHandler(
+ "/" + doi4,
+ {
+ handle: function (request, response) {
+ response.setStatusLine(null, 200, "OK");
+ response.write(`
+
+ Page Title
+
+
+ Download PDF
+
+ `);
+ }
+ }
+ );
+
requestStubCallTimes = [];
});
@@ -1165,6 +1183,44 @@ describe("Zotero.Attachments", function() {
assert.equal(await OS.File.stat(attachment.getFilePath()).size, pdfSize);
});
+ it("should handle a custom resolver with a relative PDF path in HTML mode", async function () {
+ var doi = doi4;
+ var item = createUnsavedDataObject('item', { itemType: 'journalArticle' });
+ item.setField('title', 'Test');
+ item.setField('DOI', doi);
+ await item.saveTx();
+
+ var resolvers = [{
+ name: 'Custom',
+ method: 'get',
+ // Registered with httpd.js in beforeEach()
+ url: baseURL + "{doi}",
+ mode: 'html',
+ selector: '#pdf-link',
+ attribute: 'href'
+ }];
+ Zotero.Prefs.set('findPDFs.resolvers', JSON.stringify(resolvers));
+
+ var attachment = await Zotero.Attachments.addAvailablePDF(item);
+
+ assert.equal(requestStub.callCount, 4);
+ var call = requestStub.getCall(0);
+ assert.isTrue(call.calledWith('GET', 'https://doi.org/' + doi));
+ var call = requestStub.getCall(1);
+ assert.isTrue(call.calledWith('GET', pageURL4));
+ call = requestStub.getCall(2);
+ assert.isTrue(call.calledWith('POST', ZOTERO_CONFIG.SERVICES_URL + 'oa/search'));
+ var call = requestStub.getCall(3);
+ assert.isTrue(call.calledWith('GET', baseURL + doi4));
+
+ assert.ok(attachment);
+ var json = attachment.toJSON();
+ assert.equal(json.url, pdfURL);
+ assert.equal(json.contentType, 'application/pdf');
+ assert.equal(json.filename, 'Test.pdf');
+ assert.equal(await OS.File.stat(attachment.getFilePath()).size, pdfSize);
+ });
+
it("should handle a custom resolver in JSON mode with URL strings", async function () {
var doi = doi4;
var item = createUnsavedDataObject('item', { itemType: 'journalArticle' });