inital import

This commit is contained in:
2025-12-11 17:07:30 +01:00
commit 40354e492d
6 changed files with 23363 additions and 0 deletions

74
Dockerfile Normal file
View File

@@ -0,0 +1,74 @@
## Dockerfile ofr building a docker imapsync image with WebUI
# docker build -t mrickl/imapsyncwithwebui
#Documentation cooming Soon
FROM debian:stable
LABEL maintainer="deadmanIsARabbit" \
description="Imapsync with WebUI"
RUN sed -i 's/Components: main/Components: main contrib non-free-firmware non-free/g' /etc/apt/sources.list.d/debian.sources
RUN set -xe && \
apt-get update && \
apt-get install -y \
libauthen-ntlm-perl \
libcgi-pm-perl \
libcrypt-openssl-rsa-perl \
libdata-uniqid-perl \
libencode-imaputf7-perl \
libfile-copy-recursive-perl \
libfile-tail-perl \
libhttp-daemon-perl \
libhttp-daemon-ssl-perl \
libhttp-message-perl \
libio-socket-inet6-perl \
libio-socket-ssl-perl \
libio-tee-perl \
libhtml-parser-perl \
libjson-webtoken-perl \
libmail-imapclient-perl \
libmodule-scandeps-perl \
libnet-server-perl \
libnet-dns-perl \
libparse-recdescent-perl \
libproc-processtable-perl \
libreadonly-perl \
libregexp-common-perl \
libsys-meminfo-perl \
libterm-readkey-perl \
libtest-mockobject-perl \
libunicode-string-perl \
liburi-perl \
libwww-perl \
make \
time \
cpanminus \
wget \
curl \
procps \
lighttpd
ENV LIGHTHTTPD_RUN_USER www-data
ENV LIGHTHTTPD_RUN_GROUP www-data
RUN echo "server.stream-response-body = 1" >> /etc/lighttpd/lighttpd.conf
RUN lighty-enable-mod cgi
RUN service lighttpd force-reload
RUN mkdir -p /usr/lib/cgi-bin
RUN rm -f /var/www/html/index.html
#always pull the latest version of imapsync
RUN curl https://raw.githubusercontent.com/imapsync/imapsync/master/imapsync > /usr/lib/cgi-bin/imapsync
RUN chmod +x /usr/lib/cgi-bin/imapsync
#copy the webui files
COPY html /var/www/html
RUN ln -s /var/www/html/imapsync_form_extra.html /var/www/html/index.html
EXPOSE 80
CMD ["lighttpd", "-D", "-f", "etc/lighttpd/lighttpd.conf"]

38
README.md Normal file
View File

@@ -0,0 +1,38 @@
# Imapsync with WebUI
(forked from [mrickl/ImapsyncWebUI](https://github.com/mrickl/ImapsyncWebUI))
## German
Dies ist das Repostiory für das Docker Container "Imapsyncwithwebui"
Imapsync with WebUI ist eine Kombination aus dem Imapsync von https://imapsync.lamiral.info/ und seiner WebUI.
Da es bisher nur das Imapsync als Docker gab, aber bisher nicht in Kombination mit der WebUI habe ich dieses Docker Container gebaut.
Der Webserver Port innerhalb des Containers ist Port 80.
Installtion des Containers:
## English
This is the repostiory for the Docker container "Imapsyncwithwebui".
Imapsync with WebUI is a combination of the Imapsync from https://imapsync.lamiral.info/ and his WebUI.
Since so far there was only the Imapsync as Docker, but so far not in combination with the WebUI .
The web server port inside the container is port 80.
Installtion of the container:
## Installation
### Build
> docker build --tag 'deadmanIsARabbit/imapsyncwithwebui' .
### Run
> docker run -d --name imapsync -p 80:80 deadmanIsARabbit/imapsyncwithwebui
## Information
Der "Abort" Button hat im Docker Container keine Funktion, aufgrund der im Docker befindlichen /.dockerenv Datei. Hierfür gibt es allerdings einen Workaround, wodurch das Abbrechen der Sync Jobs möglich ist. Ich bin bereits an einer Lösung dran damit der Workaround nicht benötigt wird
The "Abort" button has no function in the Docker container, because of the /.dockerenv file located in Docker. However, there is a workaround for this, which makes it possible to abort the sync jobs. I am already working on a solution so that the workaround is not needed.
> docker exec imapsync rm ./dockerenv

41
html/imapsync_form.css Normal file
View File

@@ -0,0 +1,41 @@
/* $Id: imapsync_form.css,v 1.8 2019/11/25 13:49:18 gilles Exp gilles $ */
#account1 {
background-color: DodgerBlue ;
/* background-color: Fuchsia ; */
/* background-color: RoyalBlue ; */
}
#account2 {
background-color: DarkTurquoise ;
}
#parameters {
/* background-color: GreenYellow ; */
/* background-color: White ; */
}
#progress-txt {
text-align: center ;
font-size: 1.4em ;
margin: 0 0 0px ;
height: 50px ;
}
.progress {
/* text-align: center ; */
/* font-size: 1.4em ; */
margin: 0 0 0px ;
}
.progress-bar {
text-align: center ;
font-size: 1.4em ;
margin: 0 0 0px ;
}
.padd0 {
padding-left: 0px ;
padding-right: 0px ;
}

851
html/imapsync_form.js Normal file
View File

@@ -0,0 +1,851 @@
// $Id: proximapsync_form.js,v 1.15 2025/10/18 14:16:20 gilles Exp gilles $
/*jslint browser: true*/ /*global $*/
/*
1) How the hell is structured this code?
2) What is its behavior?
*/
var readyStateStr = {
"0": "Request not initialized",
"1": "Server connection established",
"2": "Response headers received",
"3": "Processing request",
"4": "Finished and response is ready"
};
var refresh_interval_ms = 2000;
var refresh_interval_s = refresh_interval_ms / 1000;
var test = {
counter_all: 0,
counter_ok: 0,
counter_nok: 0,
failed_tests: ""
};
var is = function is(expected, given, comment) {
test.counter_all += 1;
var message = test.counter_all + " - ["
+ expected
+ "] === ["
+ given
+ "] "
+ comment
+ "\n";
if (expected === given) {
test.counter_ok += 1;
message = "ok " + message;
}
else {
test.counter_nok += 1;
test.failed_tests += "nb " + message + "\n";
message = "not ok " + message;
}
$("#tests").append(message);
};
var note = function note(message) {
$("#tests").append(message);
};
var tests_last_x_lines = function tests_last_x_lines() {
is("", last_x_lines(), "last_x_lines: no args => empty string");
is("", last_x_lines(""), "last_x_lines: empty string => empty string");
is("abc", last_x_lines("abc"), "last_x_lines: abc => abc");
is("abc\ndef", last_x_lines("abc\ndef"), "last_x_lines: abc\ndef => abc\ndef");
is("def", last_x_lines("abc\ndef", -1), "last_x_lines: abc\ndef -1 => def\n");
is("", last_x_lines("abc\ndef", 0), "last_x_lines: abc\ndef 0 => empty string");
is("abc\ndef", last_x_lines("abc\ndef", -10), "last_x_lines: last 10 of 2 lines => 2 lines");
is("4\n5\n", last_x_lines("1\n2\n3\n4\n5\n", -3), "last_x_lines: last 3 lines of 5 lines");
is("3\n4\n5", last_x_lines("1\n2\n3\n4\n5", -3), "last_x_lines: last 3 lines of 5 lines");
};
var last_x_lines = function last_x_lines(string, num) {
if (undefined === string || 0 === num) {
return "";
}
return string.split(/\r?\n/).slice(num).join("\n");
};
var last_eta = function last_eta(string) {
// return the last occurrence of the substring "ETA: ...\n"
// or "ETA: unknown" or ""
var eta;
var last_found;
if (undefined === string) {
return "";
}
var eta_re = /ETA:.*\n/g;
eta = string.match(eta_re);
if (eta) {
last_found = eta[eta.length - 1];
return last_found;
}
else {
return "ETA: unknown";
}
}
var tests_last_eta = function tests_last_eta() {
is("", last_eta(), "last_eta: no args => empty string");
is(
"ETA: unknown",
last_eta(""),
"last_eta: empty => empty string");
is("ETA: unknown",
last_eta("ETA"),
"last_eta: ETA => empty string");
is("ETA: unknown", last_eta("ETA: but no CR"),
"last_eta: ETA: but no CR => empty string");
is(
"ETA: with CR\n",
last_eta("Blabla ETA: with CR\n"),
"last_eta: ETA: with CR => ETA: with CR"
);
is(
"ETA: 2 with CR\n",
last_eta("Blabla ETA: 1 with CR\nBlabla ETA: 2 with CR\n"),
"last_eta: several ETA: with CR => ETA: 2 with CR"
);
}
var tests_decompose_eta_line = function tests_decompose_eta_line() {
var eta_obj;
var eta_str = "ETA: Wed Jul 3 14:55:27 2019 1234 s 123/4567 msgs left\n";
eta_obj = decompose_eta_line("");
is(
"",
eta_obj.str,
"decompose_eta_line: no match => undefined"
);
eta_obj = decompose_eta_line(eta_str);
is(
eta_str,
eta_str,
"decompose_eta_line: str is str"
);
is(
eta_str,
eta_obj.str,
"decompose_eta_line: str back"
);
is(
"Wed Jul 3 14:55:27 2019",
eta_obj.date,
"decompose_eta_line: date"
);
is(
"1234",
eta_obj.seconds_left,
"decompose_eta_line: seconds_left"
);
is(
"123",
eta_obj.msgs_left,
"decompose_eta_line: msgs_left"
);
is(
"4567",
eta_obj.msgs_total,
"decompose_eta_line: msgs_total"
);
is(
"4444",
eta_obj.msgs_done(),
"decompose_eta_line: msgs_done"
);
is(
"97.31",
eta_obj.percent_done(),
"decompose_eta_line: percent_done"
);
is(
"2.69",
eta_obj.percent_left(),
"decompose_eta_line: percent_left"
);
};
var decompose_eta_line = function decompose_eta_line(eta_str) {
var eta_obj;
var eta_array;
var regex_eta = /^ETA:\s+(.*?)\s+([0-9]+)\s+s\s+([0-9]+)\/([0-9]+)\s+msgs\s+left\n?$/;
eta_array = regex_eta.exec(eta_str);
if (null !== eta_array) {
eta_obj = {
str: eta_str,
date: eta_array[1],
seconds_left: eta_array[2],
msgs_left: eta_array[3],
msgs_total: eta_array[4],
msgs_done: function () {
var diff = eta_obj.msgs_total - eta_obj.msgs_left;
return (diff.toString());
},
percent_done: function () {
var percent;
if (0 === eta_obj.msgs_total) {
return "0";
}
else {
percent = (eta_obj.msgs_total - eta_obj.msgs_left) / eta_obj.msgs_total * 100;
return (percent.toFixed(2));
}
},
percent_left: function () {
var percent;
if (0 === eta_obj.msgs_total) {
return "0";
}
else {
percent = (eta_obj.msgs_left / eta_obj.msgs_total * 100);
return (percent.toFixed(2));
}
}
};
}
else {
eta_obj = {
str: "",
date: "?",
seconds_left: "?",
msgs_left: "?",
msgs_total: "?",
msgs_done: "?",
percent_done: function () { return ""; },
percent_left: function () { return ""; }
};
}
return eta_obj;
};
var extract_eta = function extract_eta(xhr) {
var eta_obj;
var slice_length;
var slice_log;
var eta_str;
if (!xhr || typeof xhr.responseText !== "string") {
return {
str: "",
date: "?",
seconds_left: "?",
msgs_left: "?",
msgs_total: "?",
msgs_done: "?",
percent_done: function () { return ""; },
percent_left: function () { return ""; }
};
}
// wie bisher: nur den letzten Teil des Logs betrachten
if (xhr.readyState === 4) {
slice_length = -24000;
} else {
slice_length = -2400;
}
slice_log = xhr.responseText.slice(slice_length);
// 1. Versuch: originale ETA-Zeile (ETA: ... msgs left)
var eta_re = /ETA:.*\n/g;
var all_etas = slice_log.match(eta_re);
if (all_etas && all_etas.length) {
eta_str = all_etas[all_etas.length - 1];
eta_obj = decompose_eta_line(eta_str);
if (eta_obj && eta_obj.str && eta_obj.str.length) {
return eta_obj;
}
}
// 2. Fallback: nur "NNN/TTT msgs left" ohne strengen ETA-Header
var msgs_re = /(\d+)\s*\/\s*(\d+)\s+msgs\s+left/g;
var m;
var last = null;
while ((m = msgs_re.exec(slice_log)) !== null) {
last = m;
}
if (last) {
var left = parseInt(last[1], 10);
var total = parseInt(last[2], 10);
if (!isFinite(left) || !isFinite(total) || total === 0) {
// zur Sicherheit: zurück auf "unknown"
} else {
eta_obj = {
// künstliche ETA-String-Repräsentation
str: "ETA: ? ? s " + left + "/" + total + " msgs left\n",
date: "?",
seconds_left: "?",
msgs_left: left,
msgs_total: total,
msgs_done: function () {
return (eta_obj.msgs_total - eta_obj.msgs_left);
},
percent_done: function () {
var percent = (eta_obj.msgs_total - eta_obj.msgs_left) /
eta_obj.msgs_total * 100;
return percent.toFixed(2);
},
percent_left: function () {
var percent = eta_obj.msgs_left / eta_obj.msgs_total * 100;
return percent.toFixed(2);
}
};
return eta_obj;
}
}
// 3. Kein Fortschritts-Muster gefunden → "unknown"
return {
str: "",
date: "?",
seconds_left: "?",
msgs_left: "?",
msgs_total: "?",
msgs_done: "?",
percent_done: function () { return ""; },
percent_left: function () { return ""; }
};
};
var progress_bar_update = function progress_bar_update(eta_obj) {
if (eta_obj.str.length) {
$("#progress-bar-done").css("width", eta_obj.percent_done() + "%").attr("aria-valuenow", eta_obj.percent_done());
$("#progress-bar-left").css("width", eta_obj.percent_left() + "%").attr("aria-valuenow", eta_obj.percent_left());
$("#progress-bar-done").text(eta_obj.percent_done() + "% " + "done");
$("#progress-bar-left").text(eta_obj.percent_left() + "% " + "left");
}
else {
$("#progress-bar-done").text("unknown % " + "done");
$("#progress-bar-left").text("unknown % " + "left");
}
return;
};
var refreshLog = function refreshLog(xhr) {
var eta_obj;
var eta_str;
if (!xhr) {
return;
}
// ETA aus dem aktuellen Response-Text holen
eta_obj = extract_eta(xhr);
progress_bar_update(eta_obj);
if (xhr.readyState === 4) {
// Ende des Syncs
$("#progress-txt").text(
"Ended. It remains "
+ eta_obj.msgs_left + " messages to be synced"
);
} else {
eta_str = eta_obj.str + " (refresh done every " + refresh_interval_s + " s)";
eta_str = eta_str.replace(/(\r\n|\n|\r)/gm, ""); // Zeilenumbrüche trimmen
$("#progress-txt").text(eta_str);
}
};
var handleRun = function handleRun(xhr) {
if (!xhr) {
return;
}
var time = new Date();
// XHR-Status in #console schreiben
$("#console").append(
"Status: " + xhr.status + " " + xhr.statusText + "\n"
+ "State: " + readyStateStr[xhr.readyState] + "\n"
+ "Time: " + time + "\n\n"
);
// Progress/ETA aktualisieren
refreshLog(xhr);
if (xhr.readyState === 4) {
// Request fertig → Buttons wieder sinnvoll setzen
$("#bt-sync").prop("disabled", false);
$("#bt-abort").prop("disabled", true);
}
};
var imapsync = function imapsync() {
var querystring = $("#form").serialize();
//console.log(querystring);
$("#abort").text("\n\n\n"); // clean abort console
var consoleEl = document.getElementById("console");
var outputEl = document.getElementById("output");
// Startzustand
outputEl.textContent = "Here comes the log!\n\n";
// Zusätzliche Flags aus dem Original
if ("imap.gmail.com" === $("#host1").val()) {
querystring = querystring + "&gmail1=on";
}
if ("imap.gmail.com" === $("#host2").val()) {
querystring = querystring + "&gmail2=on";
}
if ("outlook.office365.com" === $("#host1").val()) {
querystring = querystring + "&office1=on";
}
if ("outlook.office365.com" === $("#host2").val()) {
querystring = querystring + "&office2=on";
}
if ("export.imap.mail.yahoo.com" === $("#host1").val()) {
querystring = querystring + "&yahoo1=on";
}
// URL aus dem Formular (HTML: action="/cgi-bin/imapsync")
var url = $("#form").attr("action") || "/cgi-bin/imapsync";
var xhr = new XMLHttpRequest();
var lastLength = 0; // wie weit responseText schon verarbeitet wurde
// Hilfsfunktion: nur neue Chunks anhängen
var appendChunk = function () {
var full = xhr.responseText || "";
var chunk = full.substring(lastLength);
if (chunk.length > 0) {
lastLength = full.length;
outputEl.textContent += chunk;
// immer ans Ende scrollen
outputEl.scrollTop = outputEl.scrollHeight;
}
};
xhr.open("POST", url, true);
xhr.setRequestHeader(
"Content-type",
"application/x-www-form-urlencoded"
);
// readyState-Änderung → Status + Progress
xhr.onreadystatechange = function () {
handleRun(xhr);
};
// bei jeder neuen Datenportion
xhr.onprogress = function () {
appendChunk(); // neue Log-Zeilen nach #output
handleRun(xhr); // Fortschrittsbalken + #console updaten
console.log("onprogress");
};
// am Ende sicherstellen, dass auch der letzte Rest dran ist
xhr.onload = function () {
appendChunk();
handleRun(xhr);
console.log("onload");
};
xhr.onerror = function () {
var msg = "\n--- XHR Fehler ---\n" +
"readyState=" + xhr.readyState + "\n" +
"status=" + xhr.status + " " + xhr.statusText + "\n";
outputEl.textContent += msg;
// Buttons wieder freigeben
$("#bt-sync").prop("disabled", false);
$("#bt-abort").prop("disabled", true);
};
xhr.send(querystring);
};
var handleAbort = function handleAbort(xhr) {
const time = new Date();
$("#abort").text(
"Status: " + xhr.status + " " + xhr.statusText + "\n"
+ "State: " + readyStateStr[xhr.readyState] + "\n"
+ "Time: " + time + "\n"
);
if (xhr.readyState === 4) {
$("#abort").append(xhr.responseText);
$("#bt-sync").prop("disabled", false);
$("#bt-abort").prop("disabled", false); // back for next abort
}
}
var abort = function abort() {
var querystring = $("#form").serialize() + "&abort=on";
var xhr;
xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
handleAbort(xhr);
};
xhr.open("POST", "/cgi-bin/imapsync", true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send(querystring);
}
var store = function store(id) {
var stored;
//$( "#tests" ).append( "Eco: " + id + " type is " + $( id ).attr( "type" ) + "\n" ) ;
if ("text" === $(id).attr("type") || "password" === $(id).attr("type")) {
localStorage.setItem(id, $(id).val());
stored = $(id).val();
}
else if ("checkbox" === $(id).attr("type")) {
//$( "#tests" ).append( "Eco: " + id + " checked is " + $( id )[0].checked + "\n" ) ;
localStorage.setItem(id, $(id)[0].checked);
stored = $(id)[0].checked;
}
return stored;
}
var retrieve = function retrieve(id) {
var retrieved;
//$( "#tests" ).append( "Eco: " + id + " type is " + $( id ).attr( "type" ) + " length is " + $( id ).length + "\n" ) ;
if ("text" === $(id).attr("type") || "password" === $(id).attr("type")) {
$(id).val(localStorage.getItem(id));
retrieved = $(id).val();
}
else if ("checkbox" === $(id).attr("type")) {
//$( "#tests" ).append( "Eco: " + id + " getItem is " + localStorage.getItem( id ) + "\n" ) ;
$(id)[0].checked = JSON.parse(localStorage.getItem(id));
retrieved = $(id)[0].checked;
}
return retrieved;
}
var tests_store_retrieve = function tests_store_retrieve() {
if ($("#tests").length !== 0) {
is(1, 1, "one equals one");
// isnot( 0, 1, "zero differs one" ) ;
// no exist
is(undefined, store("#test_noexists"),
"store: #test_noexists");
is(undefined, retrieve("#test_noexists"),
"retrieve: #test_noexists");
is(undefined, retrieve("#test_noexists2"),
"retrieve: #test_noexists2");
// input text
$("#test_text").val("foo");
is("foo", $("#test_text").val(), "#test_text val = foo");
is("foo", store("#test_text"), "store: #test_text");
$("#test_text").val("bar");
is("bar", $("#test_text").val(), "#test_text val = bar");
is("foo", retrieve("#test_text"), "retrieve: #test_text = foo");
is("foo", $("#test_text").val(), "#test_text val = foo");
// input check button
$("#test_checkbox").prop("checked", true);
is(true, store("#test_checkbox"), "store: #test_checkbox checked");
$("#test_checkbox").prop("checked", false);
is(true, retrieve("#test_checkbox"), "retrieve: #test_checkbox = true");
$("#test_checkbox").prop("checked", false);
is(false, store("#test_checkbox"), "store: #test_checkbox not checked");
$("#test_checkbox").prop("checked", true);
is(false, retrieve("#test_checkbox"), "retrieve: #test_checkbox = false");
}
}
var store_form = function store_form() {
if (Storage !== "undefined") {
// Code for localStorage.
store("#user1");
store("#password1");
store("#host1");
store("#subfolder1");
store("#showpassword1");
store("#user2");
store("#password2");
store("#host2");
store("#subfolder2");
store("#showpassword2");
store("#dry");
store("#justlogin");
store("#justfolders");
store("#justfoldersizes");
localStorage.account1_background_color = $("#account1").css("background-color");
localStorage.account2_background_color = $("#account2").css("background-color");
}
}
var show_extra_if_needed = function show_extra_if_needed() {
if ($("#subfolder1").length && $("#subfolder1").val().length > 0) {
$(".extra_param").show();
}
if ($("#subfolder2").length && $("#subfolder2").val().length > 0) {
$(".extra_param").show();
}
}
var retrieve_form = function retrieve_form() {
if (Storage !== "undefined") {
// Code for localStorage.
retrieve("#user1");
retrieve("#password1");
// retrieve("#showpassword1") ;
retrieve("#host1");
retrieve("#subfolder1");
retrieve("#user2");
retrieve("#password2");
// retrieve("#showpassword2") ;
retrieve("#host2");
retrieve("#subfolder2");
retrieve("#dry");
retrieve("#justlogin");
retrieve("#justfolders");
retrieve("#justfoldersizes");
// In case, how to restore the original color from css file.
// localStorage.removeItem( "account1_background_color" ) ;
// localStorage.removeItem( "account2_background_color" ) ;
if (localStorage.account1_background_color) {
$("#account1").css("background-color",
localStorage.account1_background_color);
}
if (localStorage.account2_background_color) {
$("#account2").css("background-color",
localStorage.account2_background_color);
}
// Show the extra parameters if they are not empty because it would
// be dangerous to retrieve them without showing them
show_extra_if_needed();
}
}
var showpassword = function showpassword(id, button) {
var x = document.getElementById(id);
if (button.checked) {
x.type = "text";
} else {
x.type = "password";
}
}
var tests_cryptojs = function tests_cryptojs() {
if ($("#tests").length !== 0) {
if (typeof CryptoJS === 'undefined') {
is(true, typeof CryptoJS !== 'undefined', "CryptoJS is available");
note("CryptoJS is not available on this site. Ask the admin to fix this.\n");
}
else if (typeof CryptoJS.SHA256 !== "function") {
is("function", typeof CryptoJS.SHA256, "CryptoJS.SHA256 is a function");
note("CryptoJS.SHA256 function is not available on this site. Ask the admin to fix this.\n");
}
else {
// safe to use the function
is("function", typeof CryptoJS.SHA256, "CryptoJS.SHA256 is a function");
is("2f77668a9dfbf8d5848b9eeb4a7145ca94c6ed9236e4a773f6dcafa5132b2f91", sha256("Message"), "sha256 Message");
is("26429a356b1d25b7d57c0f9a6d5fed8a290cb42374185887dcd2874548df0779", sha256("caca"), "sha256 caca");
is("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", sha256(""), "sha256 ''");
is(tmphash(), tmphash(), "tmphash");
$("#user1").val("test1");
$("#password1").val("secret1");
$("#host1").val("test1.lamiral.info");
$("#user2").val("test2");
$("#password2").val("secret2");
$("#host2").val("test2.lamiral.info");
is("20d2b4917cf69114876b4c8779af543e89c5871c6ada68107619722e55af1101", tmphash(), "tmphash like testslive");
$("#user1").val("");
$("#password1").val("");
$("#host1").val("");
$("#user2").val("");
$("#password2").val("");
$("#host2").val("");
}
}
}
var sha256 = function sha256(string) {
var hash = CryptoJS.SHA256(string);
var hash_hex = hash.toString(CryptoJS.enc.Hex);
return (hash_hex);
}
var tmphash = function tmphash() {
var string = "";
string = string.concat(
$("#user1").val(), $("#password1").val(), $("#host1").val(),
$("#user2").val(), $("#password2").val(), $("#host2").val(),
)
return (sha256(string));
}
var init = function init() {
// in case of a manual refresh, start with
$("#bt-sync").prop("disabled", false);
$("#bt-abort").prop("disabled", false);
$("#progress-bar-left").css("width", 100 + "%").attr("aria-valuenow", 100);
$("#showpassword1").on('click', function (event) {
var button = event.target;
showpassword("password1", button);
}
);
$("#showpassword2").on('click', function (event) {
//$("#tests").append( "\nthat1=" + JSON.stringify( event.target, undefined, 4 ) ) ;
var button = event.target;
showpassword("password2", button);
}
);
$("#bt-sync").on('click', function () {
$("#bt-sync").prop("disabled", true);
$("#bt-abort").prop("disabled", false);
$("#progress-txt").text("ETA: coming soon");
store_form();
imapsync();
}
);
$("#bt-abort").on('click', function () {
$("#bt-sync").prop("disabled", true);
$("#bt-abort").prop("disabled", true);
abort();
}
);
var swap = function swap(p1, p2) {
var temp = $(p2).val();
$(p2).val($(p1).val());
$(p1).val(temp);
};
$("#swap").on('click', function () {
// swaping colors can't use swap()
var temp1 = $("#account1").css("background-color");
var temp2 = $("#account2").css("background-color");
$("#account1").css("background-color", temp2);
$("#account2").css("background-color", temp1);
swap($("#user1"), $("#user2"));
swap($("#password1"), $("#password2"));
swap($("#host1"), $("#host2"));
swap($("#subfolder1"), $("#subfolder2"));
var temp = $("#showpassword1")[0].checked;
$("#showpassword1")[0].checked = $("#showpassword2")[0].checked;
$("#showpassword2")[0].checked = temp;
showpassword("password1", $("#showpassword1")[0]);
showpassword("password2", $("#showpassword2")[0]);
}
);
}
var tests_bilan = function tests_bilan(nb_attended_test) {
// attended number of tests: nb_attended_test
$("#tests").append("1.." + test.counter_all + "\n");
if (test.counter_nok > 0) {
$("#tests").append(
"\nFAILED tests \n"
+ test.failed_tests
);
$("#tests").collapse("show");
}
// Summary of tests: failed 0 tests, run xx tests,
// expected to run yy tests.
if (test.counter_all !== nb_attended_test) {
$("#tests").append("# Looks like you planned "
+ nb_attended_test
+ " tests but ran "
+ test.counter_all + ".\n"
);
$("#tests").collapse("show");
}
};
var tests = function tests(nb_attended_test) {
if ($("#tests").length !== 0) {
tests_store_retrieve();
tests_last_eta();
tests_decompose_eta_line();
tests_last_x_lines();
// tests_cryptojs( ) ;
// The following test can be used to check that if a test fails
// then all the tests are shown to the user.
//is( 0, 1, "this test always fails" ) ;
tests_bilan(nb_attended_test);
// If you want to always see the tests, uncomment the following
// line
//$("#tests").collapse("show") ;
}
}
$(document).ready(
function () {
"use strict";
// Bootstrap popover and tooltip
$("[data-toggle='tooltip']").tooltip();
init();
//tests(38);
retrieve_form();
}
);

View File

@@ -0,0 +1,375 @@
<!DOCTYPE html>
<!-- $Id: imapsync_form_extra.html,v 1.4 2019/11/07 11:16:25 gilles Exp gilles $ -->
<html lang="en" id="top">
<head>
<title>Imapsync online</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="imapsync_form.css">
<link rel="license" href="https://imapsync.lamiral.info/NOLIMIT">
<noscript>
<link rel="stylesheet" href="noscript.css">
</noscript>
</head>
<body>
<div class="scripton">
<!-- will appear if some tests fail -->
<pre id="tests" class="collapse"></pre>
<!-- hidden stuff that must exit for the tests -->
<div class="d-none">
<input type="checkbox" id="test_checkbox">
<input type="text" id="test_text">
<input type="radio" id="test_radio1" name="test_radio" value="first">
<input type="radio" id="test_radio2" name="test_radio" value="second">
</div>
</div>
<div class="container">
<div class="row">
<div class="text-center">
<a href="https://imapsync.lamiral.info/">
<img alt="Imapsync home" title="Imapsync home page"
src="https://imapsync.lamiral.info/X/logo_imapsync_Xn.png" height="38" width="60">
</a>
<a href="#top" title="Top of the page" class="btn btn-info " role="button">Top</a>
<a href="#bottom" title="Bottom of the page" class="btn btn-info active" role="button">Bottom</a>
</div>
</div>
<div class="row mb-3">
<div class="col">
<h1 class="text-center">Imapsync online</h1>
<p class="text-center"> <strong>Copy</strong>/synchronize a <strong>complete</strong> mailbox to
another,
without <strong>duplicates!</strong></p>
</div>
</div>
<form id="form" action="/cgi-bin/imapsync" method="post" autocomplete="on">
<div id="form_row" class="row h-100">
<div id="account1" class="col col-md-5 order-0 py-3">
<fieldset>
<legend class="text-center fs-2 text-light">IMAP source mailbox</legend>
<label for="user1" class="form-check-label text-light">Login</label>
<div class="input-group form-group">
<span class="input-group-text"><i class="bi bi-person"></i></span>
<input data-toggle="tooltip" data-placement="bottom"
title="It is usually an email address or its left part before @" type="text"
class="form-control input-lg" id="user1" name="user1" tabindex="1"
placeholder="Enter login name">
</div>
<label class="form-check-label text-light" for="password1">Password</label>
<div class="input-group form-group">
<span class="input-group-text"><i class="bi bi-lock"></i></span>
<input data-toggle="tooltip" data-placement="bottom"
title="Passwords are not stored on the server" type="password"
class="form-control input-lg" id="password1" name="password1" tabindex="2"
placeholder="Enter password">
</div>
<div class="text-end">
<label class="form-check-label text-light" for="showpassword1">
show password
</label>
<input class="form-check-input" type="checkbox" id="showpassword1">
</div>
<label for="host1" class="form-check-label text-light">Server</label>
<div class="input-group form-group">
<span class="input-group-text"><i class="bi bi-cloudy"></i></span>
<input data-toggle="tooltip" data-placement="bottom"
title="IMAP transfers are done with encryption if the servers support it."
list="servers1" type="text" class="form-control input-lg" id="host1" name="host1"
tabindex="3" placeholder="Enter imap source server name or IP address">
<datalist id="servers1">
<option value="imap.gmail.com">
<option value="outlook.office365.com">
<option value="imap.mail.yahoo.com">
</datalist>
</div>
<div class="form-group form-check collapse extra_param">
<input class="form-check-input" data-toggle="tooltip" data-placement="bottom"
title="Be careful with this option" type="checkbox" id="delete1" name="delete1">
<label class="form-check-label text-light" for="delete1">Move sync. Deletes messages on
source
mailbox after a successful transfer.</label>
</div>
<div class="form-group collapse extra_param">
<label for="subfolder1" class="form-check-label text-light"> Sub-folder</label>
<div class="input-group form-group">
<span class="input-group-text"><i class="bi bi-folder2-open"></i></i></span>
<input data-toggle="tooltip" data-placement="bottom"
title="A subfolder where all the source mailbox comes from." type="text"
class="form-control input-lg" id="subfolder1" name="subfolder1"
placeholder="Enter sub-folder name">
</div>
</div>
</fieldset>
</div>
<div id="parameters" class="col col-md-2 order-2 order-lg-1 d-flex justify-content-between flex-column">
<div class="form-check">
<label class="form-check-label" for="dry">Dry run.</label>
<input class="form-check-input" data-toggle="tooltip" data-placement="bottom"
title="Shows what would be done without really doing it." type="checkbox" id="dry"
name="dry">
</div>
<div class="form-check">
<label class="form-check-label" for="justlogin">Login only.</label>
<input class="form-check-input" data-toggle="tooltip" data-placement="bottom"
title="Checks credentials without syncing anything." type="checkbox" id="justlogin"
name="justlogin">
</label>
</div>
<div class="form-check">
<label class="form-check-label" for="justfoldersizes">Just folders sizes.</label>
<input class="form-check-input" data-toggle="tooltip" data-placement="bottom"
title="Shows folders sizes and exits." type="checkbox" id="justfoldersizes"
name="justfoldersizes">
</label>
</div>
<div class="form-check">
<label class="form-check-label" for="justfolders">Folders only.</label>
<input class="form-check-input" data-toggle="tooltip" data-placement="bottom"
title="Just create the folder hierarchy, messages are not synced." type="checkbox"
id="justfolders" name="justfolders">
</label>
</div>
<div id="button_extra_param" class="text-center scripton">
<button type="button" class="btn btn-primary btn-block" data-bs-toggle="collapse"
data-bs-target=".extra_param" aria-expanded="false" aria-controls="extra_param">
Show / Hide extra parameters</button>
</div>
<div id="button_swap" class="text-center scripton">
<button type="button" class="btn btn-primary btn-block" id="swap">
Swap Source <i class="bi bi-arrow-left-right"></i> Destination
</button>
</div>
</div>
<div id="account2" class="col col-md-5 order-1 order-lg-2 py-3">
<fieldset>
<legend class="text-center h2 text-light">IMAP destination mailbox</legend>
<label for="user2" class="form-check-label text-light">Login</label>
<div class="input-group form-group">
<span class="input-group-text"><i class="bi bi-person"></i></span>
<input data-toggle="tooltip" data-placement="bottom"
title="It is usually an email address or its left part before @" type="text"
class="form-control input-lg" id="user2" name="user2" tabindex="6"
placeholder="Enter login name">
</div>
<label for="password2" class="form-check-label text-light">Password</label>
<div class="input-group form-group">
<span class="input-group-text"><i class="bi bi-lock"></i></span>
<input data-toggle="tooltip" data-placement="bottom"
title="Passwords are not stored on the server" type="password"
class="form-control input-lg" id="password2" name="password2" tabindex="7"
placeholder="Enter password">
</div>
<div class="text-end">
<label class="form-check-label text-light" for="showpassword2">
show password
</label>
<input class="form-check-input" type="checkbox" id="showpassword2">
</div>
<label for="host2" class="form-check-label text-light">Server</label>
<div class="input-group form-group">
<span class="input-group-text"><i class="bi bi-cloudy"></i></span>
<input data-toggle="tooltip" data-placement="bottom"
title="IMAP transfers are done with encryption if the servers support it."
list="servers2" type="text" class="form-control input-lg" id="host2" name="host2"
tabindex="8" placeholder="Enter imap destination server name or IP address">
<datalist id="servers2">
<option value="imap.gmail.com">
<option value="outlook.office365.com">
<option value="imap.mail.yahoo.com">
</datalist>
</div>
<!-- -->
<div class="form-group form-check collapse extra_param">
<input class="form-check-input" data-toggle="tooltip" data-placement="bottom"
title="Be careful with this option" type="checkbox" id="delete2" name="delete2"
tabindex="9">
<label class="form-check-label text-light" for="delete2">Strict sync. Deletes
messages on destination mailbox that are not at the source mailbox. </label>
</div>
<div class="form-group collapse extra_param" id="extra_subfolder2">
<label for="subfolder2" class="form-check-label text-light">Sub-folder</label>
<div class="input-group form-group">
<span class="input-group-text"><i class="bi bi-folder2-open"></i></span>
<input data-toggle="tooltip" data-placement="bottom"
title="A subfolder where all the source mailbox will go." type="text"
class="form-control input-lg" id="subfolder2" name="subfolder2"
placeholder="Enter sub-folder name">
</div>
</div>
<!-- -->
<div>
<br>
</div>
</fieldset>
</div>
</div>
<input type="hidden" name="automap" value="on">
<input type="hidden" name="addheader" value="on">
<!-- -#->
<input type="hidden" name="simulong" value="360">
<!-#- -->
<a id="buttons"></a>
<hr>
<!-- Classical button to go to the log only -->
<noscript>
<div class="row">
<div class="col-sm-12 padd0">
<button type="submit" class="btn btn-success btn-lg center-block btn-block">Go sync!</button>
</div>
</div>
</noscript>
<!-- Javascript buttons using xhr -->
<div class="row scripton">
<div class="col-sm-6 padd0">
<button id="bt-sync" type="button" class="btn btn-success btn-lg center-block btn-block"
tabindex="11" data-toggle="tooltip" data-placement="top"
title="Launch the sync! You can abort the sync with the red Abort button nearby or by closing the tab/window.">
Sync or resync!<br>
<i class="bi bi-envelope"></i>
<i class="bi bi-arrow-right"></i>
<i class="bi bi-envelope"></i>
</button>
</div>
<div class="col-sm-6 padd0">
<button id="bt-abort" type="button" class="btn btn-danger btn-lg center-block btn-block"
tabindex="12" data-toggle="tooltip" data-placement="top"
title="Abort the sync! You can restart the sync later, no duplicates should happen.">
Abort!<br>
<i class="bi bi-scissors"></i>
</button>
</div>
</div>
</form>
<div class="row scripton" id="consoles">
<pre id="progress-txt">ETA: Estimation Time of Arrival</pre>
<div class="progress progress-stacked">
<div id="progress-bar-done" class="progress-bar bg-success" role="progressbar" aria-valuenow="0">
Done
</div>
<div id="progress-bar-left" class="progress-bar bg-info" role="progressbar" aria-valuenow="100">
Todo
</div>
</div>
<div class="col-sm-6 well">
<h2 class="text-center">Console of imapsync launch</h2>
<pre id="console">
</pre>
<a id="link_to_bottom" href="#bottom">Bottom of imapsync log</a>
</div>
<div class="col-sm-6 well">
<h2 class="text-center">Console of abort</h2>
<pre id="abort">
</pre>
</div>
</div>
</div>
<div class="container overflow-y-scroll vh-25">
<h2 class="text-center scripton">Log of imapsync run</h2>
<pre id="output" class="scripton"></pre>
<a id="bottom"></a>
<hr>
<p class="text-center">Feel free to contact
<strong><a href="https://imapsync.lamiral.info/#AUTHOR" target="_blank">Gilles LAMIRAL</a></strong>
</p>
</div>
<div class="container-fluid">
<div class="row">
<div class="text-center">
<a href="https://imapsync.lamiral.info/">
<img alt="Imapsync home page" src="https://imapsync.lamiral.info/X/logo_imapsync_Xn.png" height="38"
width="60">
</a>
<a href="#top" title="Top of the page" class="btn btn-info " role="button">Top</a>
<a href="#bottom" title="Bottom of the page" class="btn btn-info active" role="button">Bottom</a>
<p>Terms and conditions for anything: <a href="https://imapsync.lamiral.info/LICENSE">No limits to do
anything with this work and this license!</a></p>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
crossorigin="anonymous"></script>
<script src="imapsync_form.js">
</script>
</body>
</html>

21984
imapsync Normal file

File diff suppressed because it is too large Load Diff