Bash Shell İle Firefox Kayıtlı Şifrelerini .cvs'den DinamikHTML Dosyasına Çevirme (GNU/Linux'un Gücü)

Programlama ve Script dilleri konusunda bilgi paylaşım alanıdır.
Cevapla
Kullanıcı avatarı
TRWE_2012
Zettabyte1
Zettabyte1
Mesajlar: 15601
Kayıt: 25 Eyl 2013, 13:38
cinsiyet: Erkek
Teşekkür etti: 2708 kez
Teşekkür edildi: 5623 kez

Bash Shell İle Firefox Kayıtlı Şifrelerini .cvs'den DinamikHTML Dosyasına Çevirme (GNU/Linux'un Gücü)

Mesaj gönderen TRWE_2012 »

Merhabalar

Bugün , acilen bir neden dolayı Firefox'a kayıt şifrelerden bazıları lazım oldu.Bende bir bash shell betiği tasarladım.(şeytanın bile aklına gelmez birşey)

Bu bash shell betiği, firefox 'dan export edilen .cvs dosyasını;

1.Okuyor
2.Komut Çıktısını terminale basıyor.
3.Ardından Dinamik HTML Dosyasına dönüştürüyor.

Böylece eliniz de HTML sayfası şeklinde tüm şifrelerinizin bir yedek dökümü oluyor.


Önce Kod İçeriğini Verelim :

Kod: Tümünü seç

#!/usr/bin/env bash
# =============================================================================
# firefox_csv_export.sh
# Description : Reads a Firefox exported CSV file, displays it in the terminal
#               and generates a styled HTML report in the same directory.
# Usage       : bash firefox_csv_export.sh
# Compatible  : Bash 4+, Linux Mint 22.2 / Ubuntu 24.x
# =============================================================================

set -euo pipefail

# -----------------------------------------------------------------------------
# ANSI Color Codes
# -----------------------------------------------------------------------------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BLUE='\033[0;34m'
BOLD='\033[1m'
DIM='\033[2m'
RESET='\033[0m'
LINE='\033[0;34m'"$(printf '%0.s-' {1..70})"'\033[0m'

# -----------------------------------------------------------------------------
# Banner
# -----------------------------------------------------------------------------
print_banner() {
    echo -e "${CYAN}"
    echo "  +---------------------------------------------------------+"
    echo "  |         Firefox Password CSV Viewer & Exporter          |"
    echo "  |                  HTML Report Generator                  |"
    echo "  +---------------------------------------------------------+"
    echo -e "${RESET}"
}

# -----------------------------------------------------------------------------
# Input: Get and validate CSV file path
# -----------------------------------------------------------------------------
get_csv_path() {
    while true; do
        echo -e "${BOLD}Enter the full path to your Firefox CSV file:${RESET}"
        echo -e "${DIM}Example: /home/linuxmaster/Desktop/firefox_logins.csv${RESET}"
        echo -ne "${YELLOW}Path: ${RESET}"
        read -r CSV_PATH

        # Expand tilde if present
        CSV_PATH="${CSV_PATH/#\~/$HOME}"

        if [[ -z "$CSV_PATH" ]]; then
            echo -e "${RED}[ERROR] Path cannot be empty. Please try again.${RESET}\n"
            continue
        fi

        if [[ ! -f "$CSV_PATH" ]]; then
            echo -e "${RED}[ERROR] File not found: ${CSV_PATH}${RESET}\n"
            continue
        fi

        if [[ "${CSV_PATH##*.}" != "csv" ]]; then
            echo -e "${YELLOW}[WARNING] File does not have .csv extension. Continue? (y/n): ${RESET}"
            read -r CONFIRM
            [[ "$CONFIRM" != "y" && "$CONFIRM" != "Y" ]] && continue
        fi

        break
    done

    CSV_DIR="$(dirname "$CSV_PATH")"
    CSV_BASENAME="$(basename "$CSV_PATH" .csv)"
    HTML_OUTPUT="${CSV_DIR}/${CSV_BASENAME}_report.html"
}

# -----------------------------------------------------------------------------
# Parse CSV: skip header, store fields in arrays
# -----------------------------------------------------------------------------
parse_csv() {
    echo -e "\n${CYAN}[INFO] Parsing CSV file...${RESET}"

    declare -ga URLS=()
    declare -ga USERS=()
    declare -ga PASSES=()

    local line_num=0
    while IFS=',' read -r url username password rest; do
        ((line_num++)) || true
        # Skip header row
        [[ $line_num -eq 1 ]] && continue
        # Skip empty lines
        [[ -z "$url" && -z "$username" ]] && continue

        # Strip surrounding quotes if present
        url="${url//\"/}"
        username="${username//\"/}"
        password="${password//\"/}"

        URLS+=("$url")
        USERS+=("$username")
        PASSES+=("$password")
    done < "$CSV_PATH"

    TOTAL_RECORDS=${#URLS[@]}

    if [[ $TOTAL_RECORDS -eq 0 ]]; then
        echo -e "${RED}[ERROR] No records found in CSV. Check file format.${RESET}"
        exit 1
    fi

    echo -e "${GREEN}[OK] Found ${BOLD}${TOTAL_RECORDS}${RESET}${GREEN} records.${RESET}"
}

# -----------------------------------------------------------------------------
# Terminal Display
# -----------------------------------------------------------------------------
display_terminal() {
    echo -e "\n${LINE}"
    printf "${BOLD}%-4s  %-35s  %-25s  %-20s${RESET}\n" "No." "URL" "Username" "Password"
    echo -e "${LINE}"

    for i in "${!URLS[@]}"; do
        local num=$((i + 1))
        local url="${URLS[$i]}"
        local user="${USERS[$i]}"
        local pass="${PASSES[$i]}"

        # Truncate long values for terminal display
        [[ ${#url}  -gt 35 ]] && url="${url:0:32}..."
        [[ ${#user} -gt 25 ]] && user="${user:0:22}..."
        [[ ${#pass} -gt 20 ]] && pass="${pass:0:17}..."

        # Alternate row color for readability
        if (( num % 2 == 0 )); then
            printf "${DIM}%-4s  %-35s  %-25s  %-20s${RESET}\n" "$num" "$url" "$user" "$pass"
        else
            printf "%-4s  %-35s  %-25s  %-20s\n" "$num" "$url" "$user" "$pass"
        fi
    done

    echo -e "${LINE}"
    echo -e "${GREEN}Total: ${BOLD}${TOTAL_RECORDS}${RESET}${GREEN} entries displayed.${RESET}\n"
}

# -----------------------------------------------------------------------------
# HTML Escaping helper
# -----------------------------------------------------------------------------
html_escape() {
    local s="$1"
    s="${s//&/&amp;}"
    s="${s//</&lt;}"
    s="${s//>/&gt;}"
    s="${s//\"/&quot;}"
    s="${s//\'/&#39;}"
    echo "$s"
}

# -----------------------------------------------------------------------------
# Generate HTML Report
# -----------------------------------------------------------------------------
generate_html() {
    echo -e "${CYAN}[INFO] Generating HTML report...${RESET}"

    local GEN_DATE
    GEN_DATE="$(date '+%d %B %Y - %H:%M:%S')"

    # Build table rows
    local TABLE_ROWS=""
    for i in "${!URLS[@]}"; do
        local num=$((i + 1))
        local url
        local user
        local pass
        url="$(html_escape "${URLS[$i]}")"
        user="$(html_escape "${USERS[$i]}")"
        pass="$(html_escape "${PASSES[$i]}")"

        # Extract domain for favicon
        local domain=""
        if [[ "$url" =~ ^https?://([^/]+) ]]; then
            domain="${BASH_REMATCH[1]}"
        fi

        TABLE_ROWS+="
        <tr>
            <td class=\"num\">${num}</td>
            <td class=\"url-cell\">
                $([ -n "$domain" ] && echo "<img class=\"favicon\" src=\"https://www.google.com/s2/favicons?domain=${domain}&sz=16\" alt=\"\" onerror=\"this.style.display='none'\">")
                <a href=\"${url}\" target=\"_blank\" rel=\"noopener\">${url}</a>
            </td>
            <td class=\"user-cell\">
                <span class=\"user-badge\">${user}</span>
            </td>
            <td class=\"pass-cell\">
                <span class=\"pass-hidden\" data-pass=\"${pass}\">&#9679;&#9679;&#9679;&#9679;&#9679;&#9679;&#9679;&#9679;</span>
                <button class=\"toggle-btn\" onclick=\"togglePass(this)\">Show</button>
            </td>
        </tr>"
    done

    # Write HTML file
    cat > "$HTML_OUTPUT" << HTMLEOF
<!DOCTYPE html>
<html lang="tr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Firefox Password Report</title>
    <style>
        :root {
            --bg:       #0f1117;
            --surface:  #1a1d27;
            --border:   #2a2d3a;
            --accent:   #e8632a;
            --accent2:  #f0a500;
            --text:     #d4d8e8;
            --text-dim: #6b7280;
            --green:    #22c55e;
            --blue:     #3b82f6;
            --row-alt:  #161926;
        }
        * { box-sizing: border-box; margin: 0; padding: 0; }
        body {
            background: var(--bg);
            color: var(--text);
            font-family: 'Courier New', monospace;
            min-height: 100vh;
            padding: 2rem 1rem;
        }
        header {
            max-width: 1100px;
            margin: 0 auto 2rem;
            border-left: 4px solid var(--accent);
            padding-left: 1.2rem;
        }
        header h1 {
            font-size: 1.6rem;
            color: #fff;
            letter-spacing: 0.05em;
        }
        header h1 span { color: var(--accent); }
        .meta {
            margin-top: 0.4rem;
            font-size: 0.78rem;
            color: var(--text-dim);
        }
        .meta strong { color: var(--accent2); }
        .stats {
            max-width: 1100px;
            margin: 0 auto 1.5rem;
            display: flex;
            gap: 1rem;
            flex-wrap: wrap;
        }
        .stat-box {
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: 6px;
            padding: 0.7rem 1.4rem;
            font-size: 0.82rem;
        }
        .stat-box strong {
            display: block;
            font-size: 1.4rem;
            color: var(--accent);
        }
        .search-bar {
            max-width: 1100px;
            margin: 0 auto 1.2rem;
        }
        .search-bar input {
            width: 100%;
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: 6px;
            color: var(--text);
            padding: 0.6rem 1rem;
            font-family: inherit;
            font-size: 0.9rem;
            outline: none;
            transition: border-color 0.2s;
        }
        .search-bar input:focus { border-color: var(--accent); }
        .table-wrap {
            max-width: 1100px;
            margin: 0 auto;
            overflow-x: auto;
            border: 1px solid var(--border);
            border-radius: 8px;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            font-size: 0.85rem;
        }
        thead tr {
            background: var(--surface);
            border-bottom: 2px solid var(--accent);
        }
        thead th {
            padding: 0.8rem 1rem;
            text-align: left;
            color: var(--accent);
            font-size: 0.75rem;
            letter-spacing: 0.1em;
            text-transform: uppercase;
            white-space: nowrap;
        }
        tbody tr {
            border-bottom: 1px solid var(--border);
            transition: background 0.15s;
        }
        tbody tr:nth-child(even) { background: var(--row-alt); }
        tbody tr:hover { background: #1f2335; }
        td { padding: 0.65rem 1rem; vertical-align: middle; }
        .num {
            color: var(--text-dim);
            font-size: 0.75rem;
            width: 40px;
            text-align: center;
        }
        .url-cell {
            max-width: 380px;
            overflow: hidden;
        }
        .url-cell a {
            color: var(--blue);
            text-decoration: none;
            word-break: break-all;
            font-size: 0.82rem;
        }
        .url-cell a:hover { text-decoration: underline; }
        .favicon {
            width: 14px;
            height: 14px;
            margin-right: 6px;
            vertical-align: middle;
            border-radius: 2px;
        }
        .user-badge {
            background: #1e2a1e;
            color: var(--green);
            border: 1px solid #2d4a2d;
            border-radius: 4px;
            padding: 0.15rem 0.5rem;
            font-size: 0.8rem;
            word-break: break-all;
        }
        .pass-cell { white-space: nowrap; }
        .pass-hidden {
            font-size: 0.7rem;
            color: var(--text-dim);
            letter-spacing: 0.2em;
            margin-right: 0.5rem;
            font-family: inherit;
        }
        .toggle-btn {
            background: var(--surface);
            border: 1px solid var(--border);
            color: var(--text-dim);
            border-radius: 4px;
            padding: 0.15rem 0.5rem;
            font-size: 0.72rem;
            cursor: pointer;
            font-family: inherit;
            transition: all 0.15s;
        }
        .toggle-btn:hover {
            border-color: var(--accent);
            color: var(--accent);
        }
        footer {
            max-width: 1100px;
            margin: 2rem auto 0;
            text-align: center;
            font-size: 0.72rem;
            color: var(--text-dim);
            border-top: 1px solid var(--border);
            padding-top: 1rem;
        }
        .hidden-row { display: none; }
    </style>
</head>
<body>
    <header>
        <h1>Firefox <span>Password</span> Report</h1>
        <div class="meta">
            Generated: <strong>${GEN_DATE}</strong> &nbsp;|&nbsp;
            Source: <strong>$(html_escape "$CSV_PATH")</strong>
        </div>
    </header>

    <div class="stats">
        <div class="stat-box">
            <strong>${TOTAL_RECORDS}</strong>
            Total Entries
        </div>
    </div>

    <div class="search-bar">
        <input type="text" id="searchInput" placeholder="Search by URL or username..." oninput="filterTable()">
    </div>

    <div class="table-wrap">
        <table id="passTable">
            <thead>
                <tr>
                    <th>#</th>
                    <th>URL</th>
                    <th>Username</th>
                    <th>Password</th>
                </tr>
            </thead>
            <tbody>
                ${TABLE_ROWS}
            </tbody>
        </table>
    </div>

    <footer>
        Firefox Password Report &mdash; Generated by firefox_csv_export.sh
        &nbsp;|&nbsp; Total: ${TOTAL_RECORDS} entries
    </footer>

    <script>
        function togglePass(btn) {
            var span = btn.previousElementSibling;
            var stored = span.getAttribute('data-pass');
            if (btn.textContent === 'Show') {
                span.textContent = stored;
                span.style.color = '#e8632a';
                span.style.letterSpacing = 'normal';
                span.style.fontSize = '0.82rem';
                btn.textContent = 'Hide';
            } else {
                span.innerHTML = '&#9679;&#9679;&#9679;&#9679;&#9679;&#9679;&#9679;&#9679;';
                span.style.color = '';
                span.style.letterSpacing = '';
                span.style.fontSize = '';
                btn.textContent = 'Show';
            }
        }

        function filterTable() {
            var q = document.getElementById('searchInput').value.toLowerCase();
            var rows = document.querySelectorAll('#passTable tbody tr');
            rows.forEach(function(row) {
                var text = row.textContent.toLowerCase();
                row.classList.toggle('hidden-row', q.length > 0 && text.indexOf(q) === -1);
            });
        }
    </script>
</body>
</html>
HTMLEOF

    echo -e "${GREEN}[OK] HTML report saved: ${BOLD}${HTML_OUTPUT}${RESET}"
}

# -----------------------------------------------------------------------------
# Main
# -----------------------------------------------------------------------------
main() {
    print_banner
    get_csv_path
    parse_csv
    display_terminal
    generate_html

    echo -e "\n${BLUE}Done. Open your report in a browser:${RESET}"
    echo -e "  ${BOLD}xdg-open \"${HTML_OUTPUT}\"${RESET}\n"
}

main "$@"
Şimdi ekran görüntülerini verelim :
Resim
Resim
Resim
SONUÇ :
Resim
Kullanıcı avatarı
TRWE_2012
Zettabyte1
Zettabyte1
Mesajlar: 15601
Kayıt: 25 Eyl 2013, 13:38
cinsiyet: Erkek
Teşekkür etti: 2708 kez
Teşekkür edildi: 5623 kez

PowerSHELL Versiyonu

Mesaj gönderen TRWE_2012 »

Bunu da Windows11 Yapı 24H2+ üstü sürümler için hazırladığım/tasarladığım,462 satır ve 14 fonksiyondan oluşan bir betik otomasyonu...Türünün tek örneği

Çalıştırma

PowerShell'i normal (yönetici gerekmez) açın:

Kod: Tümünü seç

powershell -ExecutionPolicy Bypass -File firefox_csv_export.ps1
Ardından CSV dosyasının TAM yolunu yazın, örneğin:

Kod: Tümünü seç

C:\Users\TRWE_2012\Desktop\firefox_logins.csv
HTML çıktısı birebir aynıdır; koyu tema, favicon, gizli şifre, interaktif (canlı) arama.

firefox_csv_export.ps1

Kod: Tümünü seç

# =============================================================================
# firefox_csv_export.ps1
# Description : Reads a Firefox exported CSV file, displays it in the terminal
#               and generates a styled HTML report in the same directory.
# Usage       : powershell -ExecutionPolicy Bypass -File firefox_csv_export.ps1
# Compatible  : PowerShell 5.1 / 7.x | Windows 11
# =============================================================================

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

# -----------------------------------------------------------------------------
# ANSI Color Helpers (works on Windows Terminal / PS 7.x / PS 5.1 with VT)
# -----------------------------------------------------------------------------
$ESC = [char]27
function Red    ($t) { "${ESC}[0;31m${t}${ESC}[0m" }
function Green  ($t) { "${ESC}[0;32m${t}${ESC}[0m" }
function Yellow ($t) { "${ESC}[1;33m${t}${ESC}[0m" }
function Cyan   ($t) { "${ESC}[0;36m${t}${ESC}[0m" }
function Blue   ($t) { "${ESC}[0;34m${t}${ESC}[0m" }
function Bold   ($t) { "${ESC}[1m${t}${ESC}[0m" }
function Dim    ($t) { "${ESC}[2m${t}${ESC}[0m" }

$LINE = "${ESC}[0;34m$("─" * 70)${ESC}[0m"

# Enable VT processing on older PS 5.1 / legacy console
$null = [System.Console]::OutputEncoding = [System.Text.Encoding]::UTF8
try {
    $kernel32 = Add-Type -MemberDefinition @"
        [DllImport("kernel32.dll", SetLastError=true)]
        public static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
        [DllImport("kernel32.dll", SetLastError=true)]
        public static extern IntPtr GetStdHandle(int nStdHandle);
        [DllImport("kernel32.dll", SetLastError=true)]
        public static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
"@ -Name "Kernel32VT" -Namespace "" -PassThru
    $handle = $kernel32::GetStdHandle(-11)
    $mode   = 0
    $null   = $kernel32::GetConsoleMode($handle, [ref]$mode)
    $null   = $kernel32::SetConsoleMode($handle, $mode -bor 0x0004)
} catch { <# Silently continue if VT already enabled #> }

# -----------------------------------------------------------------------------
# Banner
# -----------------------------------------------------------------------------
function Print-Banner {
    Write-Host ""
    Write-Host (Cyan "  +---------------------------------------------------------+")
    Write-Host (Cyan "  |         Firefox Password CSV Viewer & Exporter          |")
    Write-Host (Cyan "  |                  HTML Report Generator                  |")
    Write-Host (Cyan "  +---------------------------------------------------------+")
    Write-Host ""
}

# -----------------------------------------------------------------------------
# Input: Get and validate CSV file path
# -----------------------------------------------------------------------------
function Get-CsvPath {
    while ($true) {
        Write-Host (Bold "Enter the full path to your Firefox CSV file:")
        Write-Host (Dim  "Example: C:\Users\TRWE_2012\Desktop\firefox_logins.csv")
        Write-Host -NoNewline (Yellow "Path: ")
        $path = Read-Host

        if ([string]::IsNullOrWhiteSpace($path)) {
            Write-Host (Red "[ERROR] Path cannot be empty. Please try again.`n")
            continue
        }

        # Strip surrounding quotes if drag-dropped
        $path = $path.Trim('"').Trim("'")

        if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
            Write-Host (Red "[ERROR] File not found: $path`n")
            continue
        }

        if ([System.IO.Path]::GetExtension($path) -ne ".csv") {
            Write-Host -NoNewline (Yellow "[WARNING] File does not have .csv extension. Continue? (y/n): ")
            $confirm = Read-Host
            if ($confirm -notin @("y","Y")) { continue }
        }

        return $path
    }
}

# -----------------------------------------------------------------------------
# HTML Escaping helper
# -----------------------------------------------------------------------------
function Escape-Html ([string]$s) {
    $s = $s -replace "&",  "&amp;"
    $s = $s -replace "<",  "&lt;"
    $s = $s -replace ">",  "&gt;"
    $s = $s -replace '"',  "&quot;"
    $s = $s -replace "'",  "&#39;"
    return $s
}

# -----------------------------------------------------------------------------
# Parse CSV
# -----------------------------------------------------------------------------
function Parse-Csv ([string]$CsvPath) {
    Write-Host ""
    Write-Host (Cyan "[INFO] Parsing CSV file...")

    $records = @()

    try {
        $rows = Import-Csv -LiteralPath $CsvPath -Encoding UTF8
    } catch {
        Write-Host (Red "[ERROR] Could not read CSV: $_")
        exit 1
    }

    foreach ($row in $rows) {
        # Firefox exports: url, username, password
        $url  = if ($row.PSObject.Properties.Name -contains "url")      { $row.url }      else { "" }
        $user = if ($row.PSObject.Properties.Name -contains "username") { $row.username } else { "" }
        $pass = if ($row.PSObject.Properties.Name -contains "password") { $row.password } else { "" }

        if ([string]::IsNullOrWhiteSpace($url) -and [string]::IsNullOrWhiteSpace($user)) { continue }

        $records += [PSCustomObject]@{
            Url      = $url
            Username = $user
            Password = $pass
        }
    }

    if ($records.Count -eq 0) {
        Write-Host (Red "[ERROR] No records found. Check file format.")
        exit 1
    }

    Write-Host (Green "[OK] Found $(Bold $records.Count) records.")
    return $records
}

# -----------------------------------------------------------------------------
# Terminal Display
# -----------------------------------------------------------------------------
function Display-Terminal ([array]$Records) {
    Write-Host ""
    Write-Host $LINE
    Write-Host (Bold ("{0,-4}  {1,-35}  {2,-25}  {3,-20}" -f "No.", "URL", "Username", "Password"))
    Write-Host $LINE

    for ($i = 0; $i -lt $Records.Count; $i++) {
        $num  = $i + 1
        $url  = $Records[$i].Url
        $user = $Records[$i].Username
        $pass = $Records[$i].Password

        # Truncate for terminal readability
        if ($url.Length  -gt 35) { $url  = $url.Substring(0,32)  + "..." }
        if ($user.Length -gt 25) { $user = $user.Substring(0,22) + "..." }
        if ($pass.Length -gt 20) { $pass = $pass.Substring(0,17) + "..." }

        $line = "{0,-4}  {1,-35}  {2,-25}  {3,-20}" -f $num, $url, $user, $pass

        if ($num % 2 -eq 0) {
            Write-Host (Dim $line)
        } else {
            Write-Host $line
        }
    }

    Write-Host $LINE
    Write-Host (Green "Total: $(Bold $Records.Count) entries displayed.")
    Write-Host ""
}

# -----------------------------------------------------------------------------
# Generate HTML Report
# -----------------------------------------------------------------------------
function Generate-Html ([array]$Records, [string]$CsvPath, [string]$HtmlOutput) {
    Write-Host (Cyan "[INFO] Generating HTML report...")

    $genDate    = Get-Date -Format "dd MMMM yyyy - HH:mm:ss"
    $csvEscaped = Escape-Html $CsvPath
    $total      = $Records.Count

    # Build table rows
    $tableRows = ""
    for ($i = 0; $i -lt $Records.Count; $i++) {
        $num    = $i + 1
        $url    = Escape-Html $Records[$i].Url
        $user   = Escape-Html $Records[$i].Username
        $pass   = Escape-Html $Records[$i].Password

        # Extract domain for favicon
        $domain = ""
        if ($Records[$i].Url -match "^https?://([^/]+)") {
            $domain = $matches[1]
        }

        $faviconTag = ""
        if ($domain -ne "") {
            $faviconTag = "<img class=`"favicon`" src=`"https://www.google.com/s2/favicons?domain=$domain&sz=16`" alt=`"`" onerror=`"this.style.display='none'`">"
        }

        $tableRows += @"

        <tr>
            <td class="num">$num</td>
            <td class="url-cell">
                $faviconTag
                <a href="$url" target="_blank" rel="noopener">$url</a>
            </td>
            <td class="user-cell">
                <span class="user-badge">$user</span>
            </td>
            <td class="pass-cell">
                <span class="pass-hidden" data-pass="$pass">&#9679;&#9679;&#9679;&#9679;&#9679;&#9679;&#9679;&#9679;</span>
                <button class="toggle-btn" onclick="togglePass(this)">Show</button>
            </td>
        </tr>
"@
    }

    $html = @"
<!DOCTYPE html>
<html lang="tr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Firefox Password Report</title>
    <style>
        :root {
            --bg:       #0f1117;
            --surface:  #1a1d27;
            --border:   #2a2d3a;
            --accent:   #e8632a;
            --accent2:  #f0a500;
            --text:     #d4d8e8;
            --text-dim: #6b7280;
            --green:    #22c55e;
            --blue:     #3b82f6;
            --row-alt:  #161926;
        }
        * { box-sizing: border-box; margin: 0; padding: 0; }
        body {
            background: var(--bg);
            color: var(--text);
            font-family: 'Courier New', monospace;
            min-height: 100vh;
            padding: 2rem 1rem;
        }
        header {
            max-width: 1100px;
            margin: 0 auto 2rem;
            border-left: 4px solid var(--accent);
            padding-left: 1.2rem;
        }
        header h1 { font-size: 1.6rem; color: #fff; letter-spacing: 0.05em; }
        header h1 span { color: var(--accent); }
        .meta { margin-top: 0.4rem; font-size: 0.78rem; color: var(--text-dim); }
        .meta strong { color: var(--accent2); }
        .stats {
            max-width: 1100px;
            margin: 0 auto 1.5rem;
            display: flex;
            gap: 1rem;
            flex-wrap: wrap;
        }
        .stat-box {
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: 6px;
            padding: 0.7rem 1.4rem;
            font-size: 0.82rem;
        }
        .stat-box strong { display: block; font-size: 1.4rem; color: var(--accent); }
        .search-bar { max-width: 1100px; margin: 0 auto 1.2rem; }
        .search-bar input {
            width: 100%;
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: 6px;
            color: var(--text);
            padding: 0.6rem 1rem;
            font-family: inherit;
            font-size: 0.9rem;
            outline: none;
            transition: border-color 0.2s;
        }
        .search-bar input:focus { border-color: var(--accent); }
        .table-wrap {
            max-width: 1100px;
            margin: 0 auto;
            overflow-x: auto;
            border: 1px solid var(--border);
            border-radius: 8px;
        }
        table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
        thead tr { background: var(--surface); border-bottom: 2px solid var(--accent); }
        thead th {
            padding: 0.8rem 1rem;
            text-align: left;
            color: var(--accent);
            font-size: 0.75rem;
            letter-spacing: 0.1em;
            text-transform: uppercase;
            white-space: nowrap;
        }
        tbody tr { border-bottom: 1px solid var(--border); transition: background 0.15s; }
        tbody tr:nth-child(even) { background: var(--row-alt); }
        tbody tr:hover { background: #1f2335; }
        td { padding: 0.65rem 1rem; vertical-align: middle; }
        .num { color: var(--text-dim); font-size: 0.75rem; width: 40px; text-align: center; }
        .url-cell { max-width: 380px; overflow: hidden; }
        .url-cell a { color: var(--blue); text-decoration: none; word-break: break-all; font-size: 0.82rem; }
        .url-cell a:hover { text-decoration: underline; }
        .favicon { width: 14px; height: 14px; margin-right: 6px; vertical-align: middle; border-radius: 2px; }
        .user-badge {
            background: #1e2a1e;
            color: var(--green);
            border: 1px solid #2d4a2d;
            border-radius: 4px;
            padding: 0.15rem 0.5rem;
            font-size: 0.8rem;
            word-break: break-all;
        }
        .pass-cell { white-space: nowrap; }
        .pass-hidden {
            font-size: 0.7rem;
            color: var(--text-dim);
            letter-spacing: 0.2em;
            margin-right: 0.5rem;
        }
        .toggle-btn {
            background: var(--surface);
            border: 1px solid var(--border);
            color: var(--text-dim);
            border-radius: 4px;
            padding: 0.15rem 0.5rem;
            font-size: 0.72rem;
            cursor: pointer;
            font-family: inherit;
            transition: all 0.15s;
        }
        .toggle-btn:hover { border-color: var(--accent); color: var(--accent); }
        footer {
            max-width: 1100px;
            margin: 2rem auto 0;
            text-align: center;
            font-size: 0.72rem;
            color: var(--text-dim);
            border-top: 1px solid var(--border);
            padding-top: 1rem;
        }
        .hidden-row { display: none; }
    </style>
</head>
<body>
    <header>
        <h1>Firefox <span>Password</span> Report</h1>
        <div class="meta">
            Generated: <strong>$genDate</strong> &nbsp;|&nbsp;
            Source: <strong>$csvEscaped</strong>
        </div>
    </header>

    <div class="stats">
        <div class="stat-box">
            <strong>$total</strong>
            Total Entries
        </div>
    </div>

    <div class="search-bar">
        <input type="text" id="searchInput" placeholder="Search by URL or username..." oninput="filterTable()">
    </div>

    <div class="table-wrap">
        <table id="passTable">
            <thead>
                <tr>
                    <th>#</th>
                    <th>URL</th>
                    <th>Username</th>
                    <th>Password</th>
                </tr>
            </thead>
            <tbody>
                $tableRows
            </tbody>
        </table>
    </div>

    <footer>
        Firefox Password Report &mdash; Generated by firefox_csv_export.ps1
        &nbsp;|&nbsp; Total: $total entries
    </footer>

    <script>
        function togglePass(btn) {
            var span = btn.previousElementSibling;
            var stored = span.getAttribute('data-pass');
            if (btn.textContent === 'Show') {
                span.textContent = stored;
                span.style.color = '#e8632a';
                span.style.letterSpacing = 'normal';
                span.style.fontSize = '0.82rem';
                btn.textContent = 'Hide';
            } else {
                span.innerHTML = '&#9679;&#9679;&#9679;&#9679;&#9679;&#9679;&#9679;&#9679;';
                span.style.color = '';
                span.style.letterSpacing = '';
                span.style.fontSize = '';
                btn.textContent = 'Show';
            }
        }

        function filterTable() {
            var q = document.getElementById('searchInput').value.toLowerCase();
            var rows = document.querySelectorAll('#passTable tbody tr');
            rows.forEach(function(row) {
                var text = row.textContent.toLowerCase();
                row.classList.toggle('hidden-row', q.length > 0 && text.indexOf(q) === -1);
            });
        }
    </script>
</body>
</html>
"@

    # Write with UTF-8 BOM for proper Turkish character support in browser
    [System.IO.File]::WriteAllText($HtmlOutput, $html, [System.Text.UTF8Encoding]::new($true))
    Write-Host (Green "[OK] HTML report saved: $(Bold $HtmlOutput)")
}

# -----------------------------------------------------------------------------
# Main
# -----------------------------------------------------------------------------
function Main {
    Print-Banner

    $csvPath    = Get-CsvPath
    $csvDir     = [System.IO.Path]::GetDirectoryName($csvPath)
    $csvBase    = [System.IO.Path]::GetFileNameWithoutExtension($csvPath)
    $htmlOutput = [System.IO.Path]::Combine($csvDir, "${csvBase}_report.html")

    $records = Parse-Csv -CsvPath $csvPath
    Display-Terminal -Records $records
    Generate-Html -Records $records -CsvPath $csvPath -HtmlOutput $htmlOutput

    Write-Host ""
    Write-Host (Blue "Done. Open your report in browser:")
    Write-Host (Bold "  Start-Process `"$htmlOutput`"")
    Write-Host ""

    # Auto-open in default browser
    Write-Host -NoNewline (Yellow "Open HTML report in browser now? (y/n): ")
    $open = Read-Host
    if ($open -in @("y","Y")) {
        Start-Process $htmlOutput
    }
}

Main
Güle güle kullanın...
Cevapla

“Programlama ve Script dilleri” sayfasına dön