mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-07-17 22:14:53 +00:00
[Imapsync] Use dropdown for custom params
This commit is contained in:
@@ -5,6 +5,7 @@ use LockFile::Simple qw(lock trylock unlock);
|
||||
use Proc::ProcessTable;
|
||||
use Data::Dumper qw(Dumper);
|
||||
use IPC::Run 'run';
|
||||
use JSON::PP;
|
||||
use File::Temp;
|
||||
use Try::Tiny;
|
||||
use POSIX ();
|
||||
@@ -12,23 +13,6 @@ use sigtrap 'handler' => \&sig_handler, qw(INT TERM KILL QUIT);
|
||||
|
||||
sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };
|
||||
|
||||
sub qqw($) {
|
||||
my @params = ();
|
||||
my @values = split(/(?=--)/, $_[0]);
|
||||
foreach my $val (@values) {
|
||||
my @tmpparam = split(/ /, $val, 2);
|
||||
foreach my $tmpval (@tmpparam) {
|
||||
if ($tmpval ne '') {
|
||||
push @params, $tmpval;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach my $val (@params) {
|
||||
$val=trim($val);
|
||||
}
|
||||
return @params;
|
||||
}
|
||||
|
||||
# Move a finished job to the back of the queue, keeping order_id contiguous (1..N).
|
||||
# Called only from the parent after reaping a child, so all rotations are serialized.
|
||||
sub rotate_to_back {
|
||||
@@ -214,7 +198,18 @@ foreach my $row (@rows) {
|
||||
|
||||
print $passfile2 trim($master_pass) . "\n";
|
||||
|
||||
my @custom_params_a = qqw($custom_params);
|
||||
# Structured custom params: JSON array of {o,v}. Build one argv token per pair as "--opt=value"
|
||||
# (value glued to its option) so a value can never become a separate option; passed via
|
||||
# IPC::Run list form (execvp, no shell). Only allowlisted option names are stored (validated on write).
|
||||
my @custom_params_a;
|
||||
my $cp_pairs = eval { decode_json(defined($custom_params) && $custom_params ne '' ? $custom_params : '[]') };
|
||||
if (ref($cp_pairs) eq 'ARRAY') {
|
||||
for my $p (@$cp_pairs) {
|
||||
next unless ref($p) eq 'HASH' && defined $p->{o} && $p->{o} ne '';
|
||||
(my $ov = defined($p->{v}) ? $p->{v} : '') =~ s/\x00//g;
|
||||
push @custom_params_a, ($ov eq '' ? "--$p->{o}" : "--$p->{o}=$ov");
|
||||
}
|
||||
}
|
||||
my $custom_params_ref = \@custom_params_a;
|
||||
|
||||
# Effective per-process bandwidth cap (bytes/s): per-job value, capped by the global limit.
|
||||
|
||||
@@ -1520,7 +1520,7 @@ paths:
|
||||
timeout1: "600"
|
||||
timeout2: "600"
|
||||
exclude: "(?i)spam|(?i)junk"
|
||||
custom_params: "--dry"
|
||||
custom_params: '[{"o":"dry","v":""}]'
|
||||
delete2duplicates: "1"
|
||||
delete1: "1"
|
||||
delete2: "0"
|
||||
@@ -1569,7 +1569,13 @@ paths:
|
||||
description: exclude objects (regex)
|
||||
type: string
|
||||
custom_params:
|
||||
description: custom parameters
|
||||
description: >-
|
||||
JSON array of allowlisted imapsync options as
|
||||
option/value pairs, e.g.
|
||||
`[{"o":"folder","v":"INBOX"},{"o":"dry","v":""}]`. Only
|
||||
option names from `GET /get/syncjob_options` are accepted;
|
||||
values may contain any character and are passed as a single
|
||||
argument.
|
||||
type: string
|
||||
delete2duplicates:
|
||||
description: delete duplicates on destination (--delete2duplicates)
|
||||
@@ -4063,7 +4069,13 @@ paths:
|
||||
etc.)
|
||||
type: boolean
|
||||
custom_params:
|
||||
description: Custom parameters passed to imapsync command
|
||||
description: >-
|
||||
JSON array of allowlisted imapsync options as
|
||||
option/value pairs, e.g.
|
||||
`[{"o":"folder","v":"INBOX"},{"o":"dry","v":""}]`. Only
|
||||
option names from `GET /get/syncjob_options` are
|
||||
accepted; values may contain any character and are
|
||||
passed as a single argument.
|
||||
type: string
|
||||
delete1:
|
||||
description: Delete from source when completed
|
||||
@@ -6216,6 +6228,38 @@ paths:
|
||||
flags — they are never returned over the JSON API.
|
||||
operationId: Get sync job sources
|
||||
summary: Get sync job sources
|
||||
/api/v1/get/syncjob_options:
|
||||
get:
|
||||
parameters:
|
||||
- description: e.g. api-key-string
|
||||
example: api-key-string
|
||||
in: header
|
||||
name: X-API-Key
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
examples:
|
||||
response:
|
||||
value:
|
||||
- dry
|
||||
- folder
|
||||
- delete2folders
|
||||
- maxsize
|
||||
description: OK
|
||||
headers: {}
|
||||
tags:
|
||||
- Sync jobs
|
||||
description: >-
|
||||
Returns the allowlist of imapsync option names accepted in a sync job's
|
||||
`custom_params`. Used by the UI to populate the option autocompletion.
|
||||
operationId: Get sync job options
|
||||
summary: Get sync job options
|
||||
"/api/v1/get/tls-policy-map/{id}":
|
||||
get:
|
||||
parameters:
|
||||
|
||||
@@ -360,6 +360,21 @@ function imapsync_get_settings() {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
function imapsync_normalize_custom_params($input) {
|
||||
$pairs = json_decode((string)$input, true);
|
||||
if (!is_array($pairs)) return '[]';
|
||||
$allow = $GLOBALS['IMAPSYNC_OPTIONS']['whitelist'];
|
||||
$out = array();
|
||||
foreach ($pairs as $p) {
|
||||
if (!is_array($p)) continue;
|
||||
$o = ltrim(strtolower(trim((string)($p['o'] ?? ''))), '-');
|
||||
if ($o === '') continue;
|
||||
if (!in_array($o, $allow, true)) return false; // disallowed option -> reject the whole set
|
||||
$out[] = array('o' => $o, 'v' => str_replace("\0", '', (string)($p['v'] ?? '')));
|
||||
}
|
||||
return json_encode($out);
|
||||
}
|
||||
|
||||
function imapsync_set_setting($name, $value) {
|
||||
// Admin-only writer for a global syncjob setting.
|
||||
global $pdo;
|
||||
@@ -508,36 +523,15 @@ function syncjob($_action, $_type, $_data = null, $_extra = null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate custom params
|
||||
foreach (explode('-', $custom_params) as $param){
|
||||
if(empty($param)) continue;
|
||||
|
||||
// extract option
|
||||
if (str_contains($param, '=')) $param = explode('=', $param)[0];
|
||||
else $param = rtrim($param, ' ');
|
||||
// remove first char if first char is -
|
||||
if ($param[0] == '-') $param = ltrim($param, $param[0]);
|
||||
|
||||
if (str_contains($param, ' ')) {
|
||||
// bad char
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'bad character SPACE'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if param is whitelisted
|
||||
if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){
|
||||
// bad option
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'bad option '. $param
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// Structured custom params: validate option names against the allowlist, store as JSON pairs
|
||||
$custom_params = imapsync_normalize_custom_params($custom_params);
|
||||
if ($custom_params === false) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'imapsync_custom_param_invalid'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (empty($subfolder2)) {
|
||||
$subfolder2 = "";
|
||||
@@ -879,36 +873,15 @@ function syncjob($_action, $_type, $_data = null, $_extra = null) {
|
||||
$password1 = '';
|
||||
}
|
||||
|
||||
// validate custom params
|
||||
foreach (explode('-', $custom_params) as $param){
|
||||
if(empty($param)) continue;
|
||||
|
||||
// extract option
|
||||
if (str_contains($param, '=')) $param = explode('=', $param)[0];
|
||||
else $param = rtrim($param, ' ');
|
||||
// remove first char if first char is -
|
||||
if ($param[0] == '-') $param = ltrim($param, $param[0]);
|
||||
|
||||
if (str_contains($param, ' ')) {
|
||||
// bad char
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'bad character SPACE'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if param is whitelisted
|
||||
if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){
|
||||
// bad option
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'bad option '. $param
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// Structured custom params: validate option names against the allowlist, store as JSON pairs
|
||||
$custom_params = imapsync_normalize_custom_params($custom_params);
|
||||
if ($custom_params === false) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => 'imapsync_custom_param_invalid'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (empty($subfolder2)) {
|
||||
$subfolder2 = "";
|
||||
|
||||
@@ -4,7 +4,7 @@ function init_db_schema()
|
||||
try {
|
||||
global $pdo;
|
||||
|
||||
$db_version = "16072026_1000";
|
||||
$db_version = "17072026_1000";
|
||||
|
||||
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
@@ -886,7 +886,7 @@ function init_db_schema()
|
||||
"delete2" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||
"automap" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||
"skipcrossduplicates" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||
"custom_params" => "VARCHAR(512) NOT NULL DEFAULT ''",
|
||||
"custom_params" => "TEXT",
|
||||
"timeout1" => "SMALLINT NOT NULL DEFAULT '600'",
|
||||
"timeout2" => "SMALLINT NOT NULL DEFAULT '600'",
|
||||
"subscribeall" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
||||
@@ -1533,6 +1533,28 @@ function init_db_schema()
|
||||
$pdo->query("UPDATE `imapsync` SET `order_id` = (@r := @r + 1) ORDER BY `id`");
|
||||
}
|
||||
|
||||
// Syncjobs: migrate custom_params from raw imapsync string to structured JSON pairs.
|
||||
// Old values could not contain spaces (they were rejected), so a whitespace split is safe.
|
||||
$allow = $GLOBALS['IMAPSYNC_OPTIONS']['whitelist'];
|
||||
$cp_upd = $pdo->prepare("UPDATE `imapsync` SET `custom_params` = :cp WHERE `id` = :id");
|
||||
foreach ($pdo->query("SELECT `id`, `custom_params` FROM `imapsync`")->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
||||
$cp = trim((string)$r['custom_params']);
|
||||
if ($cp !== '' && $cp[0] === '[') continue; // already migrated (JSON)
|
||||
$pairs = array();
|
||||
if ($cp !== '') {
|
||||
$tokens = preg_split('/\s+/', $cp, -1, PREG_SPLIT_NO_EMPTY);
|
||||
for ($i = 0; $i < count($tokens); $i++) {
|
||||
if (strpos($tokens[$i], '--') !== 0) continue;
|
||||
$opt = ltrim($tokens[$i], '-'); $val = '';
|
||||
if (strpos($opt, '=') !== false) { list($opt, $val) = explode('=', $opt, 2); }
|
||||
elseif ($i + 1 < count($tokens) && strpos($tokens[$i + 1], '--') !== 0) { $val = $tokens[++$i]; }
|
||||
if (!in_array(strtolower($opt), $allow)) continue; // drop options no longer allowed
|
||||
$pairs[] = array('o' => strtolower($opt), 'v' => $val);
|
||||
}
|
||||
}
|
||||
$cp_upd->execute(array(':cp' => json_encode($pairs), ':id' => $r['id']));
|
||||
}
|
||||
|
||||
// Inject admin if not exists
|
||||
$stmt = $pdo->query("SELECT NULL FROM `admin`");
|
||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
|
||||
@@ -285,10 +285,7 @@ $IMAPSYNC_OPTIONS = array(
|
||||
'whitelist' => array(
|
||||
'abort',
|
||||
'authmd51',
|
||||
'authmd52',
|
||||
'authmech2',
|
||||
'authuser1',
|
||||
'authuser2',
|
||||
'debug',
|
||||
'debugcontent',
|
||||
'debugcrossduplicates',
|
||||
@@ -296,20 +293,15 @@ $IMAPSYNC_OPTIONS = array(
|
||||
'debugfolders',
|
||||
'debugimap',
|
||||
'debugimap1',
|
||||
'debugimap2',
|
||||
'debugmemory',
|
||||
'debugssl',
|
||||
'delete1emptyfolders',
|
||||
'delete2folders',
|
||||
'disarmreadreceipts',
|
||||
'domain1',
|
||||
'domain2',
|
||||
'domino1',
|
||||
'domino2',
|
||||
'dry',
|
||||
'errorsmax',
|
||||
'exchange1',
|
||||
'exchange2',
|
||||
'exitwhenover',
|
||||
'expunge1',
|
||||
'f1f2',
|
||||
@@ -319,7 +311,6 @@ $IMAPSYNC_OPTIONS = array(
|
||||
'folderlast',
|
||||
'folderrec',
|
||||
'gmail1',
|
||||
'gmail2',
|
||||
'idatefromheader',
|
||||
'include',
|
||||
'inet4',
|
||||
@@ -329,10 +320,7 @@ $IMAPSYNC_OPTIONS = array(
|
||||
'justfoldersizes',
|
||||
'justlogin',
|
||||
'keepalive1',
|
||||
'keepalive2',
|
||||
'log',
|
||||
'logdir',
|
||||
'logfile',
|
||||
'maxbytesafter',
|
||||
'maxlinelength',
|
||||
'maxmessagespersecond',
|
||||
@@ -342,43 +330,24 @@ $IMAPSYNC_OPTIONS = array(
|
||||
'minsize',
|
||||
'noabletosearch',
|
||||
'noabletosearch1',
|
||||
'noabletosearch2',
|
||||
'noexpunge1',
|
||||
'noexpunge2',
|
||||
'nofoldersizesatend',
|
||||
'noid',
|
||||
'nolog',
|
||||
'nomixfolders',
|
||||
'noresyncflags',
|
||||
'nossl1',
|
||||
'nossl2',
|
||||
'nosyncacls',
|
||||
'notls1',
|
||||
'notls2',
|
||||
'nouidexpunge2',
|
||||
'nousecache',
|
||||
'oauthaccesstoken2',
|
||||
'oauthdirect2',
|
||||
'office1',
|
||||
'office2',
|
||||
'pidfile',
|
||||
'pidfilelocking',
|
||||
'prefix1',
|
||||
'prefix2',
|
||||
'proxyauth1',
|
||||
'proxyauth2',
|
||||
'resyncflags',
|
||||
'resynclabels',
|
||||
'search',
|
||||
'search1',
|
||||
'search2',
|
||||
'sep1',
|
||||
'sep2',
|
||||
'showpasswords',
|
||||
'skipemptyfolders',
|
||||
'ssl2',
|
||||
'sslargs1',
|
||||
'sslargs2',
|
||||
'subfolder1',
|
||||
'subscribe',
|
||||
'subscribed',
|
||||
@@ -389,7 +358,6 @@ $IMAPSYNC_OPTIONS = array(
|
||||
'tests',
|
||||
'testslive',
|
||||
'testslive6',
|
||||
'tls2',
|
||||
'truncmess',
|
||||
'usecache',
|
||||
'useheader',
|
||||
|
||||
@@ -480,7 +480,88 @@ function imapsyncSyncSourceOauthToggle($form) {
|
||||
$form.find('.imapsync-source-redirect-uri').val(window.location.origin + '/syncjob-oauth');
|
||||
}
|
||||
|
||||
// Allowlisted imapsync option names for the custom-params selects. Loaded once and cached.
|
||||
var imapsyncCpOptions = null;
|
||||
function imapsyncCpEnsureOptions(cb) {
|
||||
if (imapsyncCpOptions !== null) { cb(); return; }
|
||||
$.get("/api/v1/get/syncjob_options", function(opts) {
|
||||
imapsyncCpOptions = Array.isArray(opts) ? opts : [];
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
// Append one row to an editor; option is a bootstrap-select (not free-text), value is arbitrary.
|
||||
function imapsyncCpAddRow($editor, o, v) {
|
||||
var $row = $(
|
||||
'<div class="input-group mb-2 imapsync-cp-row">' +
|
||||
'<select class="form-control imapsync-cp-opt" data-width="35%"></select>' +
|
||||
'<input type="text" class="form-control imapsync-cp-val">' +
|
||||
'<button type="button" class="btn btn-secondary imapsync-cp-remove"><i class="bi bi-trash"></i></button>' +
|
||||
'</div>'
|
||||
);
|
||||
var $sel = $row.find('.imapsync-cp-opt')
|
||||
.attr('title', (lang.syncjobs && lang.syncjobs.custom_param_option) || 'option');
|
||||
(imapsyncCpOptions || []).forEach(function(name) {
|
||||
$sel.append($('<option></option>').attr('value', name).text(name));
|
||||
});
|
||||
// keep a stored value that is no longer allowlisted visible instead of silently dropping it
|
||||
if (o && (imapsyncCpOptions || []).indexOf(o) === -1) {
|
||||
$sel.append($('<option></option>').attr('value', o).text(o));
|
||||
}
|
||||
$sel.val(o || '');
|
||||
$row.find('.imapsync-cp-val').attr('placeholder', (lang.syncjobs && lang.syncjobs.custom_param_value) || 'value').val(v || '');
|
||||
$editor.find('.imapsync-cp-rows').append($row);
|
||||
$sel.selectpicker();
|
||||
}
|
||||
|
||||
// Build the rows of an editor from its hidden JSON value.
|
||||
function imapsyncCpBuild($editor) {
|
||||
var $hidden = $editor.siblings('.imapsync-cp-value');
|
||||
$editor.find('.imapsync-cp-opt').selectpicker('destroy');
|
||||
$editor.find('.imapsync-cp-rows').empty();
|
||||
var pairs = [];
|
||||
try { pairs = JSON.parse($hidden.val() || '[]'); } catch (e) { pairs = []; }
|
||||
if (!Array.isArray(pairs)) pairs = [];
|
||||
pairs.forEach(function(p) {
|
||||
if (p && typeof p === 'object') imapsyncCpAddRow($editor, p.o, p.v);
|
||||
});
|
||||
}
|
||||
|
||||
// Re-serialize an editor's rows back into its hidden JSON value (option name required).
|
||||
function imapsyncCpSerialize($editor) {
|
||||
var out = [];
|
||||
$editor.find('.imapsync-cp-row').each(function() {
|
||||
var o = $.trim($(this).find('.imapsync-cp-opt').val() || '').replace(/^-+/, '');
|
||||
if (o === '') return;
|
||||
out.push({ o: o, v: $(this).find('.imapsync-cp-val').val() || '' });
|
||||
});
|
||||
$editor.siblings('.imapsync-cp-value').val(JSON.stringify(out));
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
// Custom-params editor: keep the hidden JSON in sync with the rows
|
||||
$(document).on('change', '.imapsync-cp-opt', function() {
|
||||
imapsyncCpSerialize($(this).closest('.imapsync-cp-editor'));
|
||||
});
|
||||
$(document).on('input', '.imapsync-cp-val', function() {
|
||||
imapsyncCpSerialize($(this).closest('.imapsync-cp-editor'));
|
||||
});
|
||||
$(document).on('click', '.imapsync-cp-add', function() {
|
||||
var $editor = $(this).closest('.imapsync-cp-editor');
|
||||
imapsyncCpEnsureOptions(function() { imapsyncCpAddRow($editor); });
|
||||
});
|
||||
$(document).on('click', '.imapsync-cp-remove', function() {
|
||||
var $editor = $(this).closest('.imapsync-cp-editor');
|
||||
$(this).closest('.imapsync-cp-row').find('.imapsync-cp-opt').selectpicker('destroy');
|
||||
$(this).closest('.imapsync-cp-row').remove();
|
||||
imapsyncCpSerialize($editor);
|
||||
});
|
||||
|
||||
// Edit page (syncjob.twig): build rows from the stored value on load
|
||||
imapsyncCpEnsureOptions(function() {
|
||||
$('.imapsync-cp-editor').each(function() { imapsyncCpBuild($(this)); });
|
||||
});
|
||||
|
||||
// Syncjob form: toggle password row / OAuth "connect" button by the selected source's flow
|
||||
$(document).on('change', 'select.imapsync-source-select', function() {
|
||||
var $sel = $(this);
|
||||
@@ -543,5 +624,9 @@ $(document).ready(function() {
|
||||
// Add-syncjob modal opens: (re)load the source dropdown from the API
|
||||
$(document).on('shown.bs.modal', '#addSyncJobModalAdmin, #addSyncJobModal', function() {
|
||||
populateImapsyncSourceSelects();
|
||||
var $modal = $(this);
|
||||
imapsyncCpEnsureOptions(function() {
|
||||
$modal.find('.imapsync-cp-editor').each(function() { imapsyncCpBuild($(this)); });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1049,6 +1049,10 @@ if (isset($_GET['query'])) {
|
||||
}
|
||||
process_get_return($data);
|
||||
break;
|
||||
case "syncjob_options":
|
||||
// allowlisted imapsync option names, for the custom-params datalist in the UI
|
||||
process_get_return($GLOBALS['IMAPSYNC_OPTIONS']['whitelist']);
|
||||
break;
|
||||
case "syncjob_source":
|
||||
switch ($object) {
|
||||
case "all":
|
||||
|
||||
@@ -560,6 +560,7 @@
|
||||
"imapsync_source_port_invalid": "Ungültiger Port",
|
||||
"imapsync_source_enc_invalid": "Ungültige Verschlüsselungsmethode",
|
||||
"imapsync_source_auth_invalid": "Ungültige Authentifizierungsmethode",
|
||||
"imapsync_custom_param_invalid": "Eigene Parameter enthalten eine nicht erlaubte imapsync-Option",
|
||||
"imapsync_source_oauth_required": "OAuth2 erfordert Token-Endpoint, Client-ID, Client-Secret und Scope",
|
||||
"imapsync_source_oauth_endpoint_invalid": "Ungültige OAuth2-Endpoint-URL",
|
||||
"imapsync_source_oauth_extra_invalid": "OAuth2-Extra-Parameter müssen gültiges JSON sein",
|
||||
@@ -1290,7 +1291,10 @@
|
||||
"timeout2": "Timeout für Verbindung zum lokalen Host",
|
||||
"maxbytespersecond": "Max. Übertragungsrate in Bytes/s (0 für unlimitiert)",
|
||||
"custom_params": "Eigene Parameter",
|
||||
"custom_params_hint": "Richtig: --param=xy, falsch: --param xy",
|
||||
"custom_params_hint": "Pro Zeile eine imapsync-Option. Es werden nur erlaubte Optionen akzeptiert; der Wert darf beliebige Zeichen enthalten (Leerzeichen, Regex, …) und wird als ein einzelnes Argument übergeben.",
|
||||
"custom_param_option": "Option",
|
||||
"custom_param_value": "Wert (optional)",
|
||||
"custom_param_add": "Parameter hinzufügen",
|
||||
"order_id": "Position",
|
||||
"order_id_hint": "Kleinere Position läuft zuerst; Ändern sortiert die globale Warteschlange um.",
|
||||
"set_position": "Position setzen"
|
||||
|
||||
@@ -560,6 +560,7 @@
|
||||
"imapsync_source_port_invalid": "Invalid port",
|
||||
"imapsync_source_enc_invalid": "Invalid encryption method",
|
||||
"imapsync_source_auth_invalid": "Invalid authentication method",
|
||||
"imapsync_custom_param_invalid": "Custom parameters contain a disallowed imapsync option",
|
||||
"imapsync_source_oauth_required": "OAuth2 requires token endpoint, client ID, client secret and scope",
|
||||
"imapsync_source_oauth_endpoint_invalid": "Invalid OAuth2 endpoint URL",
|
||||
"imapsync_source_oauth_extra_invalid": "OAuth2 extra parameters must be valid JSON",
|
||||
@@ -1297,7 +1298,10 @@
|
||||
"timeout2": "Timeout for connection to local host",
|
||||
"maxbytespersecond": "Max. bytes per second <br><small>(0 = unlimited)</small>",
|
||||
"custom_params": "Custom parameters",
|
||||
"custom_params_hint": "Right: --param=xy, wrong: --param xy",
|
||||
"custom_params_hint": "Add one imapsync option per row. Only allowlisted options are accepted; the value may contain any character (spaces, regex, …) and is passed as a single argument.",
|
||||
"custom_param_option": "Option",
|
||||
"custom_param_value": "Value (optional)",
|
||||
"custom_param_add": "Add parameter",
|
||||
"order_id": "Position",
|
||||
"order_id_hint": "Lower position runs first; setting it re-sorts the global queue.",
|
||||
"set_position": "Set position"
|
||||
|
||||
@@ -90,10 +90,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-2" for="custom_params">{{ lang.syncjobs.custom_params }}</label>
|
||||
<label class="control-label col-sm-2">{{ lang.syncjobs.custom_params }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="custom_params" id="custom_params" value="{{ result.custom_params }}" placeholder="--some-param=xy --other-param=yx">
|
||||
<small class="text-muted">{{ lang.syncjobs.custom_params_hint }}</small>
|
||||
<div class="imapsync-cp-editor">
|
||||
<div class="imapsync-cp-rows"></div>
|
||||
<button type="button" class="btn btn-sm btn-secondary imapsync-cp-add"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.custom_param_add }}</button>
|
||||
</div>
|
||||
<input type="hidden" name="custom_params" class="imapsync-cp-value" value="{{ result.custom_params }}">
|
||||
<small class="text-muted d-block mt-1">{{ lang.syncjobs.custom_params_hint }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
|
||||
@@ -1028,10 +1028,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-2 text-sm-end" for="custom_params">{{ lang.syncjobs.custom_params }}</label>
|
||||
<label class="control-label col-sm-2 text-sm-end">{{ lang.syncjobs.custom_params }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="custom_params" placeholder="--some-param=xy --other-param=yx">
|
||||
<small class="text-muted">{{ lang.syncjobs.custom_params_hint }}</small>
|
||||
<div class="imapsync-cp-editor">
|
||||
<div class="imapsync-cp-rows"></div>
|
||||
<button type="button" class="btn btn-sm btn-secondary imapsync-cp-add"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.custom_param_add }}</button>
|
||||
</div>
|
||||
<input type="hidden" name="custom_params" class="imapsync-cp-value" value="">
|
||||
<small class="text-muted d-block mt-1">{{ lang.syncjobs.custom_params_hint }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
|
||||
@@ -112,9 +112,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-2" for="custom_params">{{ lang.syncjobs.custom_params }}</label>
|
||||
<label class="control-label col-sm-2">{{ lang.syncjobs.custom_params }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="custom_params" placeholder="--delete2folders --otheroption">
|
||||
<div class="imapsync-cp-editor">
|
||||
<div class="imapsync-cp-rows"></div>
|
||||
<button type="button" class="btn btn-sm btn-secondary imapsync-cp-add"><i class="bi bi-plus-lg"></i> {{ lang.syncjobs.custom_param_add }}</button>
|
||||
</div>
|
||||
<input type="hidden" name="custom_params" class="imapsync-cp-value" value="">
|
||||
<small class="text-muted d-block mt-1">{{ lang.syncjobs.custom_params_hint }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
|
||||
Reference in New Issue
Block a user