diff --git a/chrome/content/zotero/attachLink.js b/chrome/content/zotero/attachLink.js
new file mode 100644
index 0000000000..c6fe5a3e56
--- /dev/null
+++ b/chrome/content/zotero/attachLink.js
@@ -0,0 +1,60 @@
+/*
+ ***** BEGIN LICENSE BLOCK *****
+
+ Copyright © 2014 Center for History and New Media
+ George Mason University, Fairfax, Virginia, USA
+ http://zotero.org
+
+ This file is part of Zotero.
+
+ Zotero is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Zotero is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with Zotero. If not, see .
+
+ ***** END LICENSE BLOCK *****
+*/
+
+var Zotero_AttachLink = new function() {
+ function getAttachFileLabel() {
+ return window.opener.document
+ .getElementById('zotero-tb-attachment-add-file-link')
+ .label;
+ };
+
+ this.submit = function() {
+ var link = document.getElementById('zotero-attach-uri-input').value;
+ var message = document.getElementById('zotero-attach-uri-message');
+ var cleanURI = Zotero.Attachments.cleanAttachmentURI(link, true);
+
+ if (!cleanURI) {
+ message.textContent = Zotero.getString('pane.items.attach.link.uri.unrecognized');
+ window.sizeToContent();
+ document.getElementById('zotero-attach-uri-input').select();
+ return false;
+ }
+ // Don't allow "file:" links, because using "Attach link to file" is the right way
+ else if (cleanURI.toLowerCase().indexOf('file:') == 0) {
+ message.textContent = Zotero.getString('pane.items.attach.link.uri.file',
+ [getAttachFileLabel()]);
+ window.sizeToContent();
+ document.getElementById('zotero-attach-uri-input').select();
+ return false;
+ }
+ else {
+ window.arguments[0].out = {
+ link: cleanURI,
+ title: document.getElementById('zotero-attach-uri-title').value
+ };
+ return true;
+ }
+ };
+}
\ No newline at end of file
diff --git a/chrome/content/zotero/attachLink.xul b/chrome/content/zotero/attachLink.xul
new file mode 100644
index 0000000000..fd899a8bd1
--- /dev/null
+++ b/chrome/content/zotero/attachLink.xul
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/attachments.js b/chrome/content/zotero/xpcom/attachments.js
index bed2454ecc..89c2005f5d 100644
--- a/chrome/content/zotero/xpcom/attachments.js
+++ b/chrome/content/zotero/xpcom/attachments.js
@@ -34,7 +34,6 @@ Zotero.Attachments = new function(){
this.linkFromFile = linkFromFile;
this.importSnapshotFromFile = importSnapshotFromFile;
this.importFromURL = importFromURL;
- this.linkFromURL = linkFromURL;
this.linkFromDocument = linkFromDocument;
this.importFromDocument = importFromDocument;
this.createMissingAttachment = createMissingAttachment;
@@ -399,38 +398,68 @@ Zotero.Attachments = new function(){
}
+ this.cleanAttachmentURI = function (uri, tryHttp) {
+ uri = uri.trim();
+ if (!uri) return false;
+
+ var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ try {
+ return ios.newURI(uri, null, null).spec // Valid URI if succeeds
+ } catch (e) {
+ if (e instanceof Components.Exception
+ && e.result == Components.results.NS_ERROR_MALFORMED_URI
+ ) {
+ if (tryHttp && /\w\.\w/.test(uri)) {
+ // Assume it's a URL missing "http://" part
+ try {
+ return ios.newURI('http://' + uri, null, null).spec;
+ } catch (e) {}
+ }
+
+ Zotero.debug('cleanAttachmentURI: Invalid URI: ' + uri, 2);
+ return false;
+ }
+ throw e;
+ }
+ }
+
+
/*
* Create a link attachment from a URL
*
- * @param {String} url
+ * @param {String} url Validated URI
* @param {Integer} sourceItemID Parent item
* @param {String} [mimeType] MIME type of page
* @param {String} [title] Title to use for attachment
*/
- function linkFromURL(url, sourceItemID, mimeType, title){
- Zotero.debug('Linking attachment from URL');
-
- /* Throw error on invalid URLs
- We currently accept the following protocols:
- PersonalBrain (brain://)
- DevonThink (x-devonthink-item://)
- Notational Velocity (nv://)
- MyLife Organized (mlo://)
- Evernote (evernote://)
- OneNote (onenote://)
- Kindle (kindle://)
- Logos (logosres:)
- Zotero (zotero://) */
-
- var urlRe = /^((https?|zotero|evernote|onenote|brain|nv|mlo|kindle|x-devonthink-item|ftp):\/\/|logosres:)[^\s]*$/;
- var matches = urlRe.exec(url);
- if (!matches) {
- throw ("Invalid URL '" + url + "' in Zotero.Attachments.linkFromURL()");
- }
+ this.linkFromURL = function (url, sourceItemID, mimeType, title) {
+ Zotero.debug('Linking attachment from ' + url);
// If no title provided, figure it out from the URL
- if (!title){
- title = url.substring(url.lastIndexOf('/')+1);
+ // Web addresses with paths will be whittled to the last element
+ // excluding references and queries. All others are the full string
+ if (!title) {
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ var titleURL = ioService.newURI(url, null, null);
+
+ if (titleURL.scheme == 'http' || titleURL.scheme == 'https') {
+ titleURL = titleURL.QueryInterface(Components.interfaces.nsIURL);
+ if (titleURL.path == '/') {
+ title = titleURL.host;
+ }
+ else if (titleURL.fileName) {
+ title = titleURL.fileName;
+ }
+ else {
+ var dir = titleURL.directory.split('/');
+ title = dir[dir.length - 2];
+ }
+ }
+ else {
+ title = url;
+ }
}
// Override MIME type to application/pdf if extension is .pdf --
@@ -446,7 +475,6 @@ Zotero.Attachments = new function(){
return itemID;
}
-
// TODO: what if called on file:// document?
function linkFromDocument(document, sourceItemID, parentCollectionIDs){
Zotero.debug('Linking attachment from document');
diff --git a/chrome/content/zotero/xpcom/translation/translate_item.js b/chrome/content/zotero/xpcom/translation/translate_item.js
index 11336eccc9..bac6f49d5f 100644
--- a/chrome/content/zotero/xpcom/translation/translate_item.js
+++ b/chrome/content/zotero/xpcom/translation/translate_item.js
@@ -217,29 +217,42 @@ Zotero.Translate.ItemSaver.prototype = {
},
"_saveAttachmentFile":function(attachment, parentID, attachmentCallback) {
- const urlRe = /(([a-z][-+\.a-z0-9]*):\/\/[^\s]*)/i; //according to RFC3986
Zotero.debug("Translate: Adding attachment", 4);
if(!attachment.url && !attachment.path) {
- Zotero.debug("Translate: Ignoring attachment: no path or URL specified", 2);
+ let e = "Translate: Ignoring attachment: no path or URL specified";
+ Zotero.debug(e, 2);
+ attachmentCallback(attachment, false, e);
return false;
}
if(!attachment.path) {
+ let url = Zotero.Attachments.cleanAttachmentURI(attachment.url);
+ if (!url) {
+ let e = "Translate: Invalid attachment URL specified <" + attachment.url + ">";
+ Zotero.debug(e, 2);
+ attachmentCallback(attachment, false, e);
+ return false;
+ }
+ attachment.url = url;
+ url = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService)
+ .newURI(url, null, null); // This cannot fail, since we check above
+
// see if this is actually a file URL
- var m = urlRe.exec(attachment.url);
- var protocol = m ? m[2].toLowerCase() : "file";
- if(protocol == "file") {
+ if(url.scheme == "file") {
attachment.path = attachment.url;
attachment.url = false;
- } else if(protocol != "http" && protocol != "https") {
- Zotero.debug("Translate: Unrecognized protocol "+protocol, 2);
+ } else if(url.scheme != "http" && url.scheme != "https") {
+ let e = "Translate: " + url.scheme + " protocol is not allowed for attachments from translators.";
+ Zotero.debug(e, 2);
+ attachmentCallback(attachment, false, e);
return false;
}
}
if(!attachment.path) {
- // create from URL
+ // At this point, must be a valid HTTP/HTTPS url
attachment.linkMode = "linked_file";
try {
var myID = Zotero.Attachments.linkFromURL(attachment.url, parentID,
@@ -412,33 +425,43 @@ Zotero.Translate.ItemSaver.prototype = {
if(attachment.snapshot === false || !this._saveFiles) {
// if snapshot is explicitly set to false, attach as link
attachment.linkMode = "linked_url";
+ let url, mimeType;
if(attachment.document) {
- try {
- Zotero.Attachments.linkFromURL(attachment.document.location.href, parentID,
- (attachment.mimeType ? attachment.mimeType : attachment.document.contentType),
- title);
- attachmentCallback(attachment, 100);
- } catch(e) {
- Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
- attachmentCallback(attachment, false, e);
- }
- return true;
+ url = attachment.document.location.href;
+ mimeType = attachment.mimeType ? attachment.mimeType : attachment.document.contentType;
} else {
- if(!attachment.mimeType || !title) {
- Zotero.debug("Translate: Either mimeType or title is missing; attaching file will be slower", 3);
- }
-
- try {
- Zotero.Attachments.linkFromURL(attachment.url, parentID,
- (attachment.mimeType ? attachment.mimeType : undefined),
- title);
- attachmentCallback(attachment, 100);
- } catch(e) {
- Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
- attachmentCallback(attachment, false, e);
- }
- return true;
+ url = attachment.url
+ mimeType = attachment.mimeType ? attachment.mimeType : undefined;
}
+
+ let cleanURI = Zotero.Attachments.cleanAttachmentURI(url);
+ if (!cleanURI) {
+ let e = "Translate: Invalid attachment URL specified <" + url + ">";
+ Zotero.debug(e, 2);
+ attachmentCallback(attachment, false, e);
+ return false;
+ }
+ url = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService)
+ .newURI(cleanURI, null, null); // This cannot fail, since we check above
+
+ // Only HTTP/HTTPS links are allowed
+ if(url.scheme != "http" && url.scheme != "https") {
+ let e = "Translate: " + url.scheme + " protocol is not allowed for attachments from translators.";
+ Zotero.debug(e, 2);
+ attachmentCallback(attachment, false, e);
+ return false;
+ }
+
+ try {
+ Zotero.Attachments.linkFromURL(cleanURI, parentID, mimeType, title);
+ attachmentCallback(attachment, 100);
+ } catch(e) {
+ Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
+ attachmentCallback(attachment, false, e);
+ return false;
+ }
+ return true;
} else {
// if snapshot is not explicitly set to false, retrieve snapshot
if(attachment.document) {
diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js
index 2da44db68a..359e6780db 100644
--- a/chrome/content/zotero/zoteroPane.js
+++ b/chrome/content/zotero/zoteroPane.js
@@ -3038,19 +3038,13 @@ var ZoteroPane = new function()
this.displayCannotEditLibraryMessage();
return;
}
- var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
- .getService(Components.interfaces.nsIPromptService);
- var input = {};
- var check = {value : false};
-
- // TODO: Allow title to be specified?
- var result = ps.prompt(null, Zotero.getString('pane.items.attach.link.uri.title'),
- Zotero.getString('pane.items.attach.link.uri'), input, "", {});
- if (!result || !input.value) return false;
-
- // Create a new attachment
- Zotero.Attachments.linkFromURL(input.value, itemID);
+ var io = {};
+ window.openDialog('chrome://zotero/content/attachLink.xul',
+ 'zotero-attach-uri-dialog', 'centerscreen, modal', io);
+ if (io.out) {
+ Zotero.Attachments.linkFromURL(io.out.link, itemID, null, io.out.title);
+ }
}
diff --git a/chrome/content/zotero/zoteroPane.xul b/chrome/content/zotero/zoteroPane.xul
index 7bb90bea8d..0ad63fe3c2 100644
--- a/chrome/content/zotero/zoteroPane.xul
+++ b/chrome/content/zotero/zoteroPane.xul
@@ -174,7 +174,7 @@
-
+
diff --git a/chrome/locale/en-US/zotero/zotero.dtd b/chrome/locale/en-US/zotero/zotero.dtd
index b006aed9d8..98cfc269bf 100644
--- a/chrome/locale/en-US/zotero/zotero.dtd
+++ b/chrome/locale/en-US/zotero/zotero.dtd
@@ -283,4 +283,10 @@
-
\ No newline at end of file
+
+
+
+
+
+
+
diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties
index 5e672d3128..f34b9579eb 100644
--- a/chrome/locale/en-US/zotero/zotero.properties
+++ b/chrome/locale/en-US/zotero/zotero.properties
@@ -195,8 +195,8 @@ tagColorChooser.maxTags = Up to %S tags in each library c
pane.items.loading = Loading items list…
pane.items.columnChooser.moreColumns = More Columns
pane.items.columnChooser.secondarySort = Secondary Sort (%S)
-pane.items.attach.link.uri.title = Attach Link to URI
-pane.items.attach.link.uri = Enter a URI:
+pane.items.attach.link.uri.unrecognized = Zotero did not recognize the URI you entered. Please check the address and try again.
+pane.items.attach.link.uri.file = To attach a link to a file, please use “%S”.
pane.items.trash.title = Move to Trash
pane.items.trash = Are you sure you want to move the selected item to the Trash?
pane.items.trash.multiple = Are you sure you want to move the selected items to the Trash?
diff --git a/chrome/skin/default/zotero/zotero.css b/chrome/skin/default/zotero/zotero.css
index aed820b173..ba3daeb1b5 100644
--- a/chrome/skin/default/zotero/zotero.css
+++ b/chrome/skin/default/zotero/zotero.css
@@ -242,6 +242,10 @@ label.zotero-text-link {
background: rgb(89, 139, 236);
}
+.zotero-message-error
+{
+ font-weight: bold;
+}
#zotero-progress-box
{
@@ -354,4 +358,16 @@ label.zotero-text-link {
#zotero-advanced-search-dialog #zotero-search-buttons
{
margin: 3px 0;
+}
+
+#zotero-attach-uri-container
+{
+ width: 30em;
+ max-width: 30em;
+}
+
+#zotero-attach-uri-message
+{
+ width: 29.5em;
+ max-width: 29.5em;
}
\ No newline at end of file