package main import ( "encoding/json" "io/fs" "net/http" "os" "path/filepath" "regexp" "strings" ) func Base64Page(w http.ResponseWriter, r *http.Request) { body := `

Base64 Scanner

Scan selected HTML files for asset references that are not embedded as base64 data URIs.

Inputs

` w.Header().Set("Content-Type", "text/html; charset=utf-8") _, _ = w.Write([]byte(Page("/base64", "Base64 Scanner", body))) } func Base64PickFolder(w http.ResponseWriter, r *http.Request) { p := PickFolder("Select folder to scan", "") if p == "" { writeJSON(w, map[string]any{"paths": []string{}}) return } writeJSON(w, map[string]any{"paths": collectHTMLFiles(p)}) } func Base64PickFiles(w http.ResponseWriter, r *http.Request) { paths := PickFiles( "Select HTML files", []FileFilter{{Name: "HTML", Extensions: []string{"html", "htm"}}}, true, "", ) writeJSON(w, map[string]any{"paths": paths}) } type scanResult struct { File string `json:"file"` OK bool `json:"ok"` Assets []string `json:"assets"` } func Base64Scan(w http.ResponseWriter, r *http.Request) { var req struct { Folder string `json:"folder"` Files []string `json:"files"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSON(w, map[string]any{"results": []scanResult{}, "error": err.Error()}) return } var targets []string var baseDir string if len(req.Files) > 0 { for _, f := range req.Files { if fileExists(f) { targets = append(targets, f) } } if len(targets) > 0 { baseDir = commonBaseDir(targets) } } else if req.Folder != "" && fileExists(req.Folder) { targets = collectHTMLFiles(req.Folder) baseDir = req.Folder } else { writeJSON(w, map[string]any{"results": []scanResult{}, "error": "Pick a folder or files first"}) return } results := []scanResult{} for _, file := range targets { data, err := os.ReadFile(file) if err != nil { continue } offenders := findNonBase64Assets(string(data)) display := file if baseDir != "" { rel, err := filepath.Rel(baseDir, file) if err == nil && rel != "" { display = rel } else { display = filepath.Base(file) } } results = append(results, scanResult{ File: display, OK: len(offenders) == 0, Assets: offenders, }) } writeJSON(w, map[string]any{"results": results}) } func commonBaseDir(files []string) string { if len(files) == 0 { return "" } if len(files) == 1 { return filepath.Dir(files[0]) } split := make([][]string, len(files)) for i, f := range files { split[i] = strings.Split(filepath.Dir(f), string(filepath.Separator)) } first := split[0] i := 0 for i < len(first) { match := true for _, parts := range split { if i >= len(parts) || parts[i] != first[i] { match = false break } } if !match { break } i++ } return strings.Join(first[:i], string(filepath.Separator)) } func collectHTMLFiles(root string) []string { var out []string htmlRx := regexp.MustCompile(`(?i)\.html?$`) _ = filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { if err != nil { return nil } if d.IsDir() { name := d.Name() if name == "node_modules" || name == ".git" { return filepath.SkipDir } return nil } if htmlRx.MatchString(d.Name()) { out = append(out, path) } return nil }) return out } var ( scriptBlockRx = regexp.MustCompile(`(?is)]*>.*?`) attrRx = regexp.MustCompile(`(?i)\b(?:src|href|poster|data-src)\s*=\s*("([^"]*)"|'([^']*)')`) cssUrlRx = regexp.MustCompile(`(?i)url\(\s*(?:"([^"]*)"|'([^']*)'|([^)]*))\s*\)`) base64DataRx = regexp.MustCompile(`(?i)^data:[^;,]*;base64,`) mailTelRx = regexp.MustCompile(`(?i)^(mailto:|tel:)`) ) func findNonBase64Assets(htmlSrc string) []string { markup := scriptBlockRx.ReplaceAllString(htmlSrc, "") seen := map[string]bool{} var out []string add := func(u string) { if u == "" || seen[u] { return } seen[u] = true out = append(out, u) } for _, m := range attrRx.FindAllStringSubmatch(markup, -1) { url := m[2] if url == "" { url = m[3] } url = strings.TrimSpace(url) if url == "" { continue } if isAssetReference(url) && !base64DataRx.MatchString(url) { add(url) } } for _, m := range cssUrlRx.FindAllStringSubmatch(markup, -1) { url := m[1] if url == "" { url = m[2] } if url == "" { url = m[3] } url = strings.TrimSpace(url) if url == "" { continue } if !base64DataRx.MatchString(url) { add(url) } } if out == nil { out = []string{} } return out } func isAssetReference(url string) bool { if strings.HasPrefix(url, "#") || strings.HasPrefix(strings.ToLower(url), "javascript:") { return false } if mailTelRx.MatchString(url) { return false } return true }