Samstag, 13 September 2025

Top 5 diese Woche

Ähnliche Tutorials

DMARC XML to Human

DMARC-Berichte liefern wichtige Informationen über die Authentizität und Sicherheit von E-Mails. Das Problem: Diese Reports werden standardmäßig als komplexe XML-Dateien bereitgestellt, die für Menschen kaum lesbar sind. Genau hier setzt unser Tool an: Der DMARC XML to Human Converter übersetzt rohe DMARC-Feedback-Dateien in eine klar strukturierte, leicht verständliche Darstellung.

Funktionen:

  • Drag & Drop Upload: Einfach DMARC XML-Dateien, ZIP-Archive oder GZIP-Dateien hochladen.
  • Automatische Entpackung & Parsing: Das Tool erkennt das Dateiformat, entpackt es bei Bedarf und liest die enthaltenen XML-Daten.
  • Menschenlesbare Darstellung: Wichtige Informationen wie Absender-Domain, Richtlinien, Auswertungen und Authentifizierungsergebnisse (SPF, DKIM) werden übersichtlich angezeigt.
  • Übersicht der Quellen: Zeigt IP-Adressen, Absenderdomains und deren Bewertung nach den DMARC-Regeln.
  • Exportfunktionen: Möglichkeit, die Ergebnisse als JSON herunterzuladen oder zu kopieren.
  • Lokale Verarbeitung: Alle Daten werden ausschließlich im Browser verarbeitet – es erfolgt keine Weitergabe an Dritte.
  • Optionaler Server-Upload: Für Unternehmen besteht die Möglichkeit, Reports per AJAX automatisch an einen definierten Server zu übertragen.

Vorteile:

  • Kein mühsames Durchsuchen von XML-Dateien mehr.
  • Schnelles Erkennen von Problemen bei SPF- oder DKIM-Prüfungen.
  • Ideal für Administratoren, Sicherheitsverantwortliche und Unternehmen, die DMARC-Monitoring einsetzen.
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Dreamcodes - DMARC XML to Human Converter</title>

<!-- Externe Bibliotheken per CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js" integrity="sha512-+sX8QfZyZQ7Q7r2b/3t1p3kJ6s6pJ3h8h2eQqGv5k7b8XbFf0oZkY9y0L6n5f2qHqv8h8+Yq5sHkq8Y1p6mMw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pako/2.1.0/pako.min.js" integrity="sha512-4lFj5qON7bXn14G1+Zl0E0wP9Q+o5j2b6g2Q3qZ2p7sY8c3uK6q9r1e7o4p8u9v1w2Y3z5m7n8p9q2z1o7k3g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<style>
  :root{
    --bg:#f6f7fb;
    --card:#ffffff;
    --accent:#1766a6;
    --muted:#6b7280;
    --success:#0f9d58;
  }
  body{
    margin:0;
    font-family:Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
    background:var(--bg);
    color:#0f172a;
    -webkit-font-smoothing:antialiased;
  }
  header{
    background:linear-gradient(90deg,#0e5fa4, #0b72b9);
    color:white;
    padding:28px 20px;
    display:flex;
    align-items:center;
    gap:16px;
  }
  header h1{ margin:0; font-size:20px; font-weight:600; }
  header p{ margin:0; opacity:0.9; font-size:13px; }
  .container{ max-width:1100px; margin:28px auto; padding:0 20px; }
  .drop{
    background:linear-gradient(180deg, rgba(255,255,255,0.8), rgba(255,255,255,0.6));
    border:2px dashed rgba(23,102,166,0.15);
    border-radius:10px;
    padding:28px;
    display:flex;
    gap:18px;
    align-items:center;
    justify-content:space-between;
    box-shadow: 0 6px 18px rgba(16,24,40,0.04);
  }
  .drop-left{ display:flex; gap:18px; align-items:center; }
  .drop .info{ max-width:720px; }
  .drop h2{ margin:0; font-size:18px; color:var(--accent); }
  .drop p{ margin:6px 0 0; color:var(--muted); font-size:13px; line-height:1.4; }
  .actions{ display:flex; gap:10px; align-items:center; }
  .btn{
    background:var(--accent);
    color:white;
    border:none;
    padding:10px 14px;
    border-radius:8px;
    cursor:pointer;
    font-weight:600;
  }
  .btn.secondary{
    background:#fff;
    color:var(--accent);
    border:1px solid rgba(23,102,166,0.12);
  }
  input[type=file]{ display:none; }
  .list{ margin-top:18px; display:grid; gap:12px; }
  .card{
    background:var(--card);
    border-radius:10px;
    padding:16px;
    box-shadow: 0 6px 18px rgba(16,24,40,0.04);
  }
  .meta{ display:flex; gap:12px; flex-wrap:wrap; font-size:13px; color:var(--muted); }
  .key{ color:var(--accent); font-weight:700; margin-right:6px; }
  .section-title{ font-size:15px; margin:12px 0 8px; color:#0f172a; }
  table{ width:100%; border-collapse:collapse; font-size:13px; }
  th, td{ text-align:left; padding:8px; border-bottom:1px solid #f1f5f9; }
  th{ color:var(--muted); font-weight:600; font-size:12px; }
  pre{ background:#0b1720; color:#e6eef6; padding:12px; border-radius:8px; overflow:auto; font-size:13px; }
  .small{ font-size:12px; color:var(--muted); }
  .controls{ display:flex; gap:10px; margin-top:12px; flex-wrap:wrap; }
  .badge{ background:#eef6fb; color:var(--accent); padding:6px 8px; border-radius:999px; font-weight:700; font-size:12px; }
  .footer-note{ font-size:13px; color:var(--muted); margin-top:12px; }
  .row{ display:flex; gap:12px; align-items:center; flex-wrap:wrap; }
  .export-btn{ background:var(--success); border:none; color:white; padding:8px 12px; border-radius:8px; cursor:pointer; }
  .danger{ background:#ef4444; color:white; padding:8px 12px; border-radius:8px; border:none; cursor:pointer; }
  .toggle{ display:inline-flex; gap:6px; align-items:center; background:#fff; padding:6px 8px; border-radius:8px; border:1px solid #e6eef6; }
  .empty{ color:var(--muted); padding:24px; text-align:center; border-radius:10px; background:linear-gradient(180deg, #fff, #fbfdff); border:1px dashed #edf2f7; }
</style>
</head>
<body>
<header>
  <div style="display:flex;flex-direction:column;">
    <h1>Dreamcodes - DMARC XML to Human Converter</h1>
    <p>DMARC Reports lesbar machen, Fehler verstehen und Maßnahmen ableiten</p>
  </div>
</header>

<div class="container">
  <div class="drop" id="dropzone">
    <div class="drop-left">
      <div style="width:64px;height:64px;border-radius:12px;background:#eaf6ff;display:flex;align-items:center;justify-content:center">
        <svg width="34" height="34" viewBox="0 0 24 24" fill="none" aria-hidden><path d="M12 2v13" stroke="#0369a1" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/><path d="M5 10l7-7 7 7" stroke="#0369a1" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
      </div>
      <div class="info">
        <h2>Dreamcodes - Drag und Drop DMARC XML Dateien hierher</h2>
        <p>Unterstützte Formate, unkomprimiertes XML, gzip oder zip. Du kannst beliebig viele Dateien hochladen. Die Dateien werden lokal im Browser verarbeitet.</p>
      </div>
    </div>

    <div class="actions">
      <label for="fileInput" class="btn" title="Dateien wählen">Dateien wählen</label>
      <input id="fileInput" type="file" multiple />
      <button id="clearAll" class="btn secondary">Alles entfernen</button>
      <div style="font-size:12px;color:#fff;background:rgba(255,255,255,0.06);padding:6px 8px;border-radius:8px" class="small">Lokal im Browser</div>
    </div>
  </div>

  <div id="output" class="list" style="margin-top:16px;">
    <div class="card empty" id="emptyState">
      Ziehe DMARC XML Dateien hierher oder wähle Dateien aus, um sie lesbar aufzubereiten
    </div>
  </div>

  <div style="margin-top:18px;display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
    <div class="toggle" title="Optionen">
      <input type="checkbox" id="showRaw" /> <label for="showRaw" style="font-size:13px;color:#0f172a">Rohdaten anzeigen</label>
    </div>
    <button id="exportAll" class="export-btn">Alle als JSON exportieren</button>
    <button id="uploadServer" class="btn secondary">Optionalen Upload per AJAX</button>
  </div>
  <p class="footer-note">
  Hinweis: Die Verarbeitung geschieht ausschließlich im Browser. 
  Die Dateien werden nicht ohne dein Zutun an einen Server gesendet. 
  Mehr Infos findest du bei 
  <a href="http://www.dreamcodes.net" target="_blank" rel="noopener noreferrer">Dreamcodes</a>.
</p>
</div>
<script>
const $ = id => document.getElementById(id);
const fileInput = $('fileInput');
const dropzone = $('dropzone');
const output = $('output');
const empty = $('emptyState');
const clearAllBtn = $('clearAll');
const exportAllBtn = $('exportAll');
const uploadServerBtn = $('uploadServer');
const showRaw = $('showRaw');
let processedReports = []; // Array mit Objekten { filename, xmlString, parsed }
function setEmptyState(show){
  empty.style.display = show ? 'block' : 'none';
}
function createCard(fileName, parsed, rawXml){
  const card = document.createElement('div');
  card.className='card';
  const header = document.createElement('div');
  header.className='row';
  const title = document.createElement('div');
  title.innerHTML = `<div style="font-weight:700">${escapeHtml(fileName)}</div><div class="small">DMARC Report Übersicht</div>`;
  header.appendChild(title);
  const controls = document.createElement('div');
  controls.style.marginLeft='auto';
  controls.className='row';
  const exportBtn = document.createElement('button');
  exportBtn.className='export-btn';
  exportBtn.textContent='JSON herunterladen';
  exportBtn.onclick = () => downloadJSON(parsed, fileName + '.json');
  const copyBtn = document.createElement('button');
  copyBtn.className='btn secondary';
  copyBtn.textContent='In Zwischenablage kopieren';
  copyBtn.onclick = async () => {
    await navigator.clipboard.writeText(JSON.stringify(parsed, null, 2));
    alert('JSON in die Zwischenablage kopiert');
  };
  const toggleRawBtn = document.createElement('button');
  toggleRawBtn.className='btn secondary';
  toggleRawBtn.textContent='Roh anzeigen';
  toggleRawBtn.onclick = () => {
    const pre = card.querySelector('pre');
    pre.style.display = pre.style.display === 'none' ? 'block' : 'none';
  };
  controls.appendChild(exportBtn);
  controls.appendChild(copyBtn);
  controls.appendChild(toggleRawBtn);
  header.appendChild(controls);
  card.appendChild(header);
  const meta = document.createElement('div');
  meta.className='meta';
  const m = parsed.report_metadata || {};
  meta.innerHTML = `
    <div><span class="key">Organisation</span> ${escapeHtml(m.org_name || 'unbekannt')}</div>
    <div><span class="key">Report ID</span> ${escapeHtml(m.report_id || 'n/a')}</div>
    <div><span class="key">Datum von</span> ${formatEpoch(m.begin)} bis ${formatEpoch(m.end)}</div>
  `;
  card.appendChild(meta);
  if(parsed.policy_published){
    const s = document.createElement('div');
    s.innerHTML = `<div class="section-title">Richtlinie</div>
      <div class="small">Domain: <strong>${escapeHtml(parsed.policy_published.domain || '')}</strong>, Policy: <strong>${escapeHtml(parsed.policy_published.p || '')}</strong>, Subdomain Policy: <strong>${escapeHtml(parsed.policy_published.sp||'n/a')}</strong>, Tengrade: <strong>${escapeHtml(parsed.policy_published.pct||'100')}</strong></div>`;
    card.appendChild(s);
  }
  if(parsed.records && parsed.records.length){
    const tableTitle = document.createElement('div');
    tableTitle.className='section-title';
    tableTitle.textContent = `Beobachtete Quellen ${parsed.records.length} Einträge`;
    card.appendChild(tableTitle);
    const table = document.createElement('table');
    const thead = document.createElement('thead');
    thead.innerHTML = '<tr><th>IP</th><th>Count</th><th>Resultat</th><th>Header From</th><th>SPF</th><th>DKIM</th></tr>';
    table.appendChild(thead);
    const tbody = document.createElement('tbody');
    parsed.records.forEach(r => {
      const tr = document.createElement('tr');
      const ip = r.row && r.row.source_ip ? r.row.source_ip : '';
      const count = r.row && r.row.count ? r.row.count : '';
      const res = r.row && r.row.policy_evaluated ? `${r.row.policy_evaluated.disposition || ''} / dkim:${r.row.policy_evaluated.dkim || ''} / spf:${r.row.policy_evaluated.spf || ''}` : '';
      const headerFrom = r.identifiers && r.identifiers.header_from ? r.identifiers.header_from : '';
      const spf = r.auth_results && r.auth_results.spf ? summarizeAuth(r.auth_results.spf) : '';
      const dkim = r.auth_results && r.auth_results.dkim ? summarizeAuth(r.auth_results.dkim) : '';
      tr.innerHTML = `<td>${escapeHtml(ip)}</td><td>${escapeHtml(String(count))}</td><td>${escapeHtml(res)}</td><td>${escapeHtml(headerFrom)}</td><td>${escapeHtml(spf)}</td><td>${escapeHtml(dkim)}</td>`;
      tbody.appendChild(tr);
    });
    table.appendChild(tbody);
    card.appendChild(table);
  } else {
    const noRec = document.createElement('div');
    noRec.className='small';
    noRec.textContent = 'Keine Einträge im Report gefunden';
    card.appendChild(noRec);
  }
  const pre = document.createElement('pre');
  pre.style.display = showRaw && showRaw.checked ? 'block' : 'none';
  pre.textContent = rawXml || '';
  card.appendChild(pre);

  return card;
}
function escapeHtml(input){
  if(!input && input !== 0) return '';
  return String(input).replaceAll('&','&amp;').replaceAll('<','&lt;').replaceAll('>','&gt;').replaceAll('"','&quot;');
}
function formatEpoch(epoch){
  if(!epoch) return 'n/a';
  try {
    const ms = Number(epoch) * 1000;
    if(isNaN(ms)) return 'n/a';
    const d = new Date(ms);
    return d.toISOString().replace('T',' ').replace('.000Z',' UTC');
  } catch(e){ return 'n/a'; }
}
function summarizeAuth(arr){
  if(!arr) return '';
  if(Array.isArray(arr)){
    return arr.map(a => `${a.domain||''}:${a.result||''}`).join(', ');
  } else if(typeof arr === 'object'){
    return `${arr.domain||''}:${arr.result||''}`;
  }
  return String(arr);
}
function downloadJSON(obj, filename){
  const blob = new Blob([JSON.stringify(obj, null, 2)], {type:'application/json'});
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href=url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  a.remove();
  URL.revokeObjectURL(url);
}
function parseDmarcXml(xmlString){
  const parser = new DOMParser();
  const doc = parser.parseFromString(xmlString, 'application/xml');
  const parseError = doc.querySelector('parsererror');
  if(parseError) {
    // Falls kein gültiges XML
    throw new Error('Ungültiges XML');
  }

  const getText = (parent, tag) => {
    const el = parent.querySelector(tag);
    return el ? el.textContent.trim() : null;
  };

  const root = doc.documentElement;
  // Report metadata
  const report_metadata = {};
  const rm = root.querySelector('report_metadata');
  if(rm){
    report_metadata.org_name = getText(rm, 'org_name');
    report_metadata.report_id = getText(rm, 'report_id');
    const dateRange = rm.querySelector('date_range');
    if(dateRange){
      report_metadata.begin = getText(dateRange, 'begin');
      report_metadata.end = getText(dateRange, 'end');
    }
  }

  // Policy published
  const policy_published = {};
  const pp = root.querySelector('policy_published');
  if(pp){
    policy_published.domain = getText(pp, 'domain');
    policy_published.adkim = getText(pp, 'adkim');
    policy_published.aspf = getText(pp, 'aspf');
    policy_published.p = getText(pp, 'p');
    policy_published.sp = getText(pp, 'sp');
    policy_published.pct = getText(pp, 'pct');
  }

  // Records
  const records = [];
  const recEls = root.querySelectorAll('record');
  recEls.forEach(rEl => {
    const row = {};
    const rowEl = rEl.querySelector('row');
    if(rowEl){
      row.source_ip = getText(rowEl, 'source_ip');
      row.count = getText(rowEl, 'count');
      const pol = rowEl.querySelector('policy_evaluated');
      if(pol){
        row.policy_evaluated = {
          disposition: getText(pol, 'disposition'),
          dkim: getText(pol, 'dkim'),
          spf: getText(pol, 'spf')
        };
      }
    }
    const identifiers = {};
    const idEl = rEl.querySelector('identifiers');
    if(idEl){
      identifiers.header_from = getText(idEl, 'header_from');
      identifiers.envelope_to = getText(idEl, 'envelope_to');
      identifiers.envelope_from = getText(idEl, 'envelope_from');
    }
    const auth_results = {};
    const arEl = rEl.querySelector('auth_results');
    if(arEl){
      // dkim may be multiple
      const dkimEls = arEl.querySelectorAll('dkim');
      if(dkimEls.length){
        auth_results.dkim = Array.from(dkimEls).map(dk=>({
          domain: getText(dk, 'domain'),
          selector: getText(dk, 'selector'),
          result: getText(dk, 'result')
        }));
      }
      const spfEls = arEl.querySelectorAll('spf');
      if(spfEls.length){
        auth_results.spf = Array.from(spfEls).map(spf=>({
          domain: getText(spf, 'domain'),
          scope: getText(spf, 'scope'),
          result: getText(spf, 'result')
        }));
      }
    }

    records.push({row, identifiers, auth_results});
  });

  return {
    report_metadata,
    policy_published,
    records
  };
}
async function handleFile(file){
  const name = file.name || 'unknown';
  const arrBuf = await file.arrayBuffer();
  const bytes = new Uint8Array(arrBuf);
  const isGzip = bytes[0] === 0x1f && bytes[1] === 0x8b;
  const isZip = bytes[0] === 0x50 && bytes[1] === 0x4b && bytes[2] === 0x03 && bytes[3] === 0x04;
  if(isGzip){
    try {
      const decompressed = pako.ungzip(bytes, { to: 'string' });
      processXmlString(name.replace(/\.gz$/i, ''), decompressed);
    } catch(e){
      console.error('GZIP Fehler', e);
      alert('Fehler beim Entpacken von gzip Datei ' + name);
    }
  } else if(isZip){
    try {
      const jszip = await JSZip.loadAsync(bytes);
      let found = 0;
      await Promise.all(
        Object.keys(jszip.files).map(async key => {
          const f = jszip.files[key];
          if(!f.dir && /(\.xml)$/i.test(f.name)){
            found++;
            const content = await f.async('string');
            processXmlString(f.name, content);
          } else if(!f.dir && /\.(xml|xml\.txt)$/i.test(f.name)){
            found++;
            const content = await f.async('string');
            processXmlString(f.name, content);
          } else if(!f.dir && /\.(gz)$/i.test(f.name)){
            // handle nested gz in zip
            const data = await f.async('uint8array');
            try {
              const dec = pako.ungzip(data, { to: 'string' });
              processXmlString(f.name.replace(/\.gz$/i,''), dec);
              found++;
            } catch(e){}
          }
        })
      );
      if(found === 0){
        alert('Kein XML in der ZIP Datei gefunden: ' + name);
      }
    } catch(e){
      console.error('ZIP Fehler', e);
      alert('Fehler beim Auslesen der ZIP Datei ' + name);
    }
  } else {
    // Assume plain XML text
    const text = new TextDecoder().decode(bytes);
    // If file extension .xml or content starts with <?xml or <feedback
    const trimmed = text.trim();
    if(trimmed.startsWith('<') && trimmed.length > 20){
      processXmlString(name, text);
    } else {
      alert('Unbekanntes Format: ' + name);
    }
  }
}
function processXmlString(filename, xmlString){
  try {
    const parsed = parseDmarcXml(xmlString);
    processedReports.push({ filename, xmlString, parsed });
    renderAll();
  } catch(e){
    console.error('Parse Fehler', e);
    alert('Fehler beim Parsen der Datei ' + filename + '. Datei ist möglicherweise kein gültiger DMARC XML Bericht.');
  }
}
function renderAll(){
  // clear
  output.innerHTML = '';
  if(processedReports.length === 0){
    setEmptyState(true);
    output.appendChild(empty);
    return;
  }
  setEmptyState(false);
  processedReports.forEach(r => {
    const card = createCard(r.filename, r.parsed, r.xmlString);
    output.appendChild(card);
  });
}
// Drag and drop events
['dragenter','dragover'].forEach(evt => {
  dropzone.addEventListener(evt, e => {
    e.preventDefault();
    dropzone.style.borderColor = 'rgba(23,102,166,0.4)';
  });
});
['dragleave','drop'].forEach(evt => {
  dropzone.addEventListener(evt, e => {
    e.preventDefault();
    dropzone.style.borderColor = 'rgba(23,102,166,0.15)';
  });
});
dropzone.addEventListener('drop', async e => {
  const dt = e.dataTransfer;
  const files = Array.from(dt.files || []);
  if(files.length === 0) return;
  for(const f of files) await handleFile(f);
});

// File input
fileInput.addEventListener('change', async e => {
  const files = Array.from(e.target.files || []);
  for(const f of files) await handleFile(f);
  fileInput.value = '';
});

// Clear all
clearAllBtn.addEventListener('click', () => {
  if(!confirm('Alle verarbeiteten Reports entfernen?')) return;
  processedReports = [];
  renderAll();
});

// Export all JSON
exportAllBtn.addEventListener('click', () => {
  if(processedReports.length === 0){ alert('Keine Reports zum Exportieren'); return; }
  const payload = processedReports.map(p => ({ filename: p.filename, parsed: p.parsed }));
  downloadJSON(payload, 'dmarc_reports.json');
});

// Optionaler AJAX Upload
uploadServerBtn.addEventListener('click', async () => {
  if(processedReports.length === 0){ alert('Keine Reports vorhanden'); return; }
  const url = prompt('Ziel URL für Upload eingeben, z. B. https://example.com/api/dmarc', '');
  if(!url) return;
  try {
    // Beispiel: POST JSON
    const payload = processedReports.map(p => ({ filename: p.filename, parsed: p.parsed }));
    const resp = await fetch(url, {
      method:'POST',
      headers:{ 'Content-Type':'application/json' },
      body: JSON.stringify({ reports: payload })
    });
    if(!resp.ok) throw new Error('Upload fehlgeschlagen ' + resp.status);
    alert('Upload erfolgreich');
  } catch(err){
    console.error(err);
    alert('Fehler beim Upload: ' + err.message);
  }
});

// init
setEmptyState(true);
renderAll();

</script>
</body>
</html>
Vorheriges Tutorial

Hier etwas für dich dabei?