function addKey(ev, file, fileContent) {
const list = findParent(ev.target, '.cbi-dynlist');
const input = list.querySelector('textarea[type="text"]');
- const key = input.value;
+ let key = (fileContent ?? input.value.trim());
if (!key.length)
return;
+ // Handle remote URL paste
+ if (/^https?:\/\/\S+$/i.test(key) && !fileContent) {
+ ui.addTimeLimitedNotification(_('Fetching key from URL…'), [], 5000, 'info');
+
+ L.Request.request(key, { method: 'GET' }).then(res => {
+ if (res.status !== 200) {
+ ui.addTimeLimitedNotification(_('Failed to fetch key'), [
+ E('p', _('HTTP error %d').format(res.status)),
+ ], 7000, 'warning');
+ return;
+ }
+
+ const fetched = res.responseText?.trim();
+ if (!fetched || fetched.length > 8192) {
+ ui.addTimeLimitedNotification(_('Key file too large'), [
+ E('p', _('Fetched content seems too long. Maximum 8192 bytes.')),
+ ], 7000, 'warning');
+ return;
+ }
+
+ if (!fetched || fetched.length < 32) {
+ ui.addTimeLimitedNotification(_('Invalid or empty key file'), [
+ E('p', _('Fetched content seems empty or too short.')),
+ ], 7000, 'warning');
+ return;
+ }
+
+ // Continue directly with fetched key
+ addKey(ev, null, fetched);
+ }).catch(err => {
+ ui.addTimeLimitedNotification(_('Failed to fetch key'), [
+ E('p', err.message),
+ ], 7000, 'warning');
+ });
+
+ return;
+ }
+
+ // From here on, key content (either pasted, fetched, or dropped)
const formatError = keyEnvironmentCheck(key);
if (formatError) {
ui.addTimeLimitedNotification(_('Invalid key format'), [
'aria-label': _('Paste or drag repository public key'),
class: 'cbi-input-text',
type: 'text',
- placeholder: _('Paste or drag to upload a software repository public key…'),
+ style: 'width: 300px; min-height: 120px; ',
+ placeholder: _('Paste content of a file, or a URL to a key file, or drag and drop here to upload a software repository public key…'),
keydown: function(ev) { if (ev.keyCode === 13) addKey(ev); },
disabled: isReadonlyView
}),