Skip to content

Instantly share code, notes, and snippets.

@jonasborn
Created May 30, 2025 00:21
Show Gist options
  • Select an option

  • Save jonasborn/69cc30d18756c170013be6fbdc906274 to your computer and use it in GitHub Desktop.

Select an option

Save jonasborn/69cc30d18756c170013be6fbdc906274 to your computer and use it in GitHub Desktop.
IMAP-over-PHP
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<title>IMAP-TLS (letzte 5 Mails)</title>
<style>
body{font-family:sans-serif}
pre {background:#111;color:#0f0;padding:1em;height:60vh;overflow:auto}
</style>
</head>
<body>
<h1>IMAP-TLS POC</h1>
<p>Hole die letzten fünf Nachrichten …</p>
<pre id="log"></pre>
<script>
/* ---------- Session-ID ---------- */
function newSID(){
const a = new Uint8Array(12);
crypto.getRandomValues(a);
return Array.from(a,x=>x.toString(16).padStart(2,"0")).join("");
}
const SID = newSID();
/* ---------- Passwort ---------- */
if(!sessionStorage.getItem("pwd"))
sessionStorage.setItem("pwd", prompt("Passwort?")||"");
const w = new Worker("./worker.js");
w.onmessage = e=>{
const {type,text}=e.data;
document.getElementById("log").textContent += text + "\n";
};
/* ---------- Worker starten ---------- */
w.postMessage({
type:"start",
sid: SID,
user: sessionStorage.getItem("usr"), //Place yourself
password: sessionStorage.getItem("pwd") //Place yourself
});
/* ---------- Tab-Close: Worker sauber abbrechen ---------- */
window.addEventListener("beforeunload", ()=>w.terminate());
</script>
</body>
</html>
<?php
/* IMAP-Tunnel pro Session – beendet sich nach 15 s Inaktivität */
set_time_limit(0);
ini_set('output_buffering',0);
ini_set('implicit_flush',1);
while(ob_get_level())ob_end_flush();
header("Content-Type: application/octet-stream");
$sid = preg_replace('/[^a-f0-9]/i','',$_GET['sid'] ?? '');
if($sid===''){ http_response_code(400); exit("no sid\n"); }
$queue = __DIR__."/queue_$sid.txt";
touch($queue);
$imap = stream_socket_client('tcp://<SERVER HOST HERE>:993',$e,$s,15);
if(!$imap){ echo "\nIMAP connect error $e/$s\n"; exit; }
stream_set_blocking($imap,false);
echo " "; flush(); // löst fetch() im Browser aus
$last = time(); // Aktivitäts-Timer
for(;;){
/* ----- Browser → IMAP ----- */
$fp = fopen($queue,'c+b');
flock($fp,LOCK_EX);
rewind($fp);
$buf = stream_get_contents($fp);
ftruncate($fp,0);
fflush($fp);
flock($fp,LOCK_UN);
fclose($fp);
if($buf!==''){
foreach(explode("\n",$buf) as $line){
$bin = base64_decode(trim($line),true);
if($bin!=='') fwrite($imap,$bin);
}
$last = time(); // Aktivität registriert
}
/* ----- IMAP → Browser ----- */
$in = fread($imap,8192);
if($in!==false && $in!==''){
echo $in; flush();
$last = time(); // Aktivität registriert
}
/* ----- Timeout? ------------- */
if(time() - $last > 15){ break; } // 15 s idle → Ende
usleep(100_000);
}
<?php
$sid = preg_replace('/[^a-f0-9]/i','',$_GET['sid'] ?? '');
if($sid===''){ http_response_code(400); exit('no sid'); }
$queue = __DIR__."/queue_$sid.txt";
$line = rtrim(file_get_contents('php://input'),"\r\n");
if($line===''){ http_response_code(400); exit('empty'); }
$fp = fopen($queue,'ab');
flock($fp,LOCK_EX);
fwrite($fp,$line.PHP_EOL);
flock($fp,LOCK_UN);
fclose($fp);
echo "OK";
self.window = self; // forge erwartet window
self.navigator = { userAgent:"Worker" };
importScripts("./forge.min.js");
const post = (t,x)=>self.postMessage({type:t,text:x});
/* ------------------------------------------------------------------ */
self.onmessage = ({data})=>{
if(data.type==="start") run(data.sid,data.user,data.password);
};
function run(sid,user,pwd){
const relayURL = `./relay.php?sid=${sid}`;
const sendURL = `./send.php?sid=${sid}`;
/* ----------- Stream zum Relay ---------- */
fetch(relayURL,{cache:"no-store"})
.then(r=>streamPump(r.body.getReader())) // ← richtiger Funktionsname
.catch(e=>post("log","relay fetch fail: "+e));
/* ----------- forge-TLS Engine ----------- */
const tls = forge.tls.createConnection({
server:false, verify:() => true,
connected: () => post("log","TLS ready"),
tlsDataReady:c => sendRaw(c.tlsData.getBytes()),
dataReady: c => imapClear(c.data.getBytes()),
closed: () => post("log","TLS closed"),
error: (c,e)=> post("log","TLS err: "+e)
});
tls.handshake();
/* ----------- TLS-Bytes → send.php ------- */
function sendRaw(bin){
if(!bin) return;
fetch(sendURL,{
method:"POST",
headers:{"Content-Type":"text/plain"},
body: btoa(bin)+"\n" // eine Zeile Base64
}).catch(e=>post("log","send fail: "+e));
}
/* ----------- IMAP Mini-FSM -------------- */
let phase=0,uids=[];
const send = cmd => tls.prepare(cmd+"\r\n");
function imapClear(txt){
post("log",txt.trim());
if(phase===0 && /^\* OK/.test(txt)){ send(`A1 LOGIN "${user}" "${pwd}"`); phase=1; return;}
if(phase===1 && /A1 OK/.test(txt)){ send("A2 SELECT INBOX"); phase=2; return;}
if(phase===2 && /A2 OK/.test(txt)){ send("A3 UID SEARCH ALL"); phase=3; return;}
if(phase===3 && txt.startsWith("* SEARCH")){
uids = txt.replace("* SEARCH","").trim().split(/\s+/).map(Number); return;
}
if(phase===3 && /A3 OK/.test(txt)){
send(`A4 UID FETCH ${uids.slice(-5).join(",")} (BODY.PEEK[HEADER.FIELDS (SUBJECT FROM DATE)] RFC822.SIZE)`);
phase=4; return;
}
if(phase===4 && /A4 OK/.test(txt)){ post("result",txt); send("A5 LOGOUT"); phase=5; }
}
/* ----------- Stream-Pump (debug-Filter) -- */
function streamPump(reader){
(async function(){
let started=false;
for(;;){
const {value,done}=await reader.read();
if(done) break;
if(!value||!value.length) continue;
let chunk=value;
if(!started){
const i = chunk.findIndex(b=>b===0x14||b===0x15||b===0x16||b===0x17);
if(i===-1) continue; // nur Preamble
if(i>0) chunk = chunk.slice(i);
started = true;
}
tls.process(String.fromCharCode(...chunk));
}
})();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment