User Tools

Site Tools


test1:subject6

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

test1:subject6 [2026/04/12 02:45] – created admintest1:subject6 [2026/04/12 02:46] (current) admin
Line 1: Line 1:
 <code> <code>
 (async () => { (async () => {
 +  const rootHandle = await window.showDirectoryPicker();
 +
   const origin = location.origin;   const origin = location.origin;
-  const pageUrl = new URL(location.href); +  const currentUrl = new URL(location.href);
-  const decoder = new TextDecoder(); +
-  const encoder = new TextEncoder();+
  
-  const collected = new Map()+  const textDecoder = new TextDecoder();
-  const fileMap = new Map(); +
-  const failed = [];+
  
-  const normalizeUrl = (u, base = location.href) => {+  const normalizeUrl = (u) => {
     try {     try {
-      const x = new URL(u, base);+      const x = new URL(u, location.href);
       if (!/^https?:$/.test(x.protocol)) return null;       if (!/^https?:$/.test(x.protocol)) return null;
-      if (x.origin !== origin) return null;+      if (x.origin !== origin) return null; // same-origin only
       x.hash = "";       x.hash = "";
       return x;       return x;
Line 22: Line 20:
   };   };
  
-  const hasExt = (name) => /\.[a-zA-Z0-9]{1,10}$/.test(name || "");+  const toLocalPathParts = (urlObj, isHtml = false) => { 
 +    let path = decodeURIComponent(urlObj.pathname);
  
-  const toLocalPath = (urlObj, isHtml = false) => { +    if (!path || path === "/"
-    let path = decodeURIComponent(urlObj.pathname || "/");+      return ["index.html"]; 
 +    }
  
-    if (!path || path === "/"return "index.html";+    const parts = path.split("/").filter(Boolean);
  
-    const parts = path.split("/").filter(Boolean); 
     const last = parts[parts.length - 1] || "";     const last = parts[parts.length - 1] || "";
 +    const hasExt = /\.[a-zA-Z0-9]{1,10}$/.test(last);
  
     if (isHtml) {     if (isHtml) {
-      if (!hasExt(last)) parts.push("index.html"); +      if (hasExt && /\.html?$/i.test(last)) 
-      return parts.join("/");+        return parts
 +      } 
 +      if (hasExt
 +        return parts; 
 +      } 
 +      return [...parts, "index.html"];
     }     }
  
-    if (!hasExt(last)) parts.push("index.html")+    if (!hasExt) 
-    return parts.join("/"); +      return [...parts"index.html"]
-  };+    }
  
-  const dirname = (p) => { +    return parts;
-    const i = p.lastIndexOf("/"); +
-    return i >= 0 ? p.slice(0, i) : "";+
   };   };
  
-  const relativePath = (fromFiletoFile) => { +  const getOrCreateDir async (baseHandledirParts) => { 
-    const fromDir dirname(fromFile)+    let h baseHandle
-    const fromParts = fromDir ? fromDir.split("/": []; +    for (const part of dirParts{ 
-    const toParts toFile.split("/")+      await h.getDirectoryHandle(part, { create: true });
- +
-    let i = 0; +
-    while (i < fromParts.length && i < toParts.length && fromParts[i] === toParts[i]) { +
-      i++;+
     }     }
- +    return h;
-    const up = new Array(fromParts.length - i).fill(".."); +
-    const down = toParts.slice(i); +
-    const out = [...up, ...down].join("/"); +
-    return out || ".";+
   };   };
  
-  const addUrl = (utype = "asset"base = location.href) => { +  const writeFile async (baseHandlepathPartsblob) => { 
-    const nu normalizeUrl(ubase); +    const dirs pathParts.slice(0-1); 
-    if (!nu) return null+    const fileName = pathParts[pathParts.length - 1]
-    if (!collected.has(nu.href)) { +    const dirHandle = await getOrCreateDir(baseHandle, dirs); 
-      collected.set(nu.href, { urlnu, type }); +    const fileHandle = await dirHandle.getFileHandle(fileName, { createtrue }); 
-    } +    const writable = await fileHandle.createWritable(); 
-    return nu.href;+    await writable.write(blob); 
 +    await writable.close();
   };   };
  
-  const cssUrlRegex /url\((['"]?)(.*?)\1\)/g;+  const collected new Map();
  
-  const extractCssUrls = (cssTextbaseUrl) => { +  const addUrl = (utype = "asset") => { 
-    const out []+    const nu normalizeUrl(u)
-    let m+    if (!nu) return
-    while ((m = cssUrlRegex.exec(cssText))) { +    const key nu.href
-      const raw (m[2] || "").trim()+    if (!collected.has(key)) { 
-      if (!raw || raw.startsWith("data:"|| raw.startsWith("blob:"|| raw.startsWith("#")) continue; +      collected.set(key{ url: nu, type });
-      const nu = normalizeUrl(rawbaseUrl); +
-      if (nu) out.push(nu.href);+
     }     }
-    return out; 
   };   };
  
 +  // current page
   addUrl(location.href, "html");   addUrl(location.href, "html");
  
-  document.querySelectorAll("script[src]").forEach(el => addUrl(el.getAttribute("src"), "asset")); +  // DOM resources 
-  document.querySelectorAll("link[href]").forEach(el => addUrl(el.getAttribute("href"), "asset")); +  document.querySelectorAll("script[src]").forEach(el => addUrl(el.src, "asset")); 
-  document.querySelectorAll("img[src]").forEach(el => addUrl(el.getAttribute("src"), "asset"));+  document.querySelectorAll('link[href]').forEach(el => addUrl(el.href, "asset")); 
 +  document.querySelectorAll("img[src]").forEach(el => addUrl(el.src, "asset"));
   document.querySelectorAll("[style]").forEach(el => {   document.querySelectorAll("[style]").forEach(el => {
     const style = el.getAttribute("style") || "";     const style = el.getAttribute("style") || "";
 +    const re = /url\((['"]?)(.*?)\1\)/g;
     let m;     let m;
-    while ((m = cssUrlRegex.exec(style))) +    while ((m = re.exec(style))) addUrl(m[2], "asset");
-      addUrl(m[2], "asset"); +
-    }+
   });   });
  
-  performance.getEntriesByType("resource").forEach(r => addUrl(r.name, "asset")); +  // CSS rules 
- +  for (const sheet of Array.from(document.styleSheets)) {
-  const fetchBinary = async (url) => { +
-    const res = await fetch(url, { credentials: "include" }); +
-    if (!res.ok) throw new Error(`HTTP ${res.status}`); +
-    return new Uint8Array(await res.arrayBuffer()); +
-  }; +
- +
-  const fetchText = async (url) => { +
-    const res = await fetch(url, { credentials: "include" }); +
-    if (!res.ok) throw new Error(`HTTP ${res.status}`); +
-    return await res.text(); +
-  }; +
- +
-  const pageLocalPath = toLocalPath(pageUrl, true); +
- +
-  // 1) fetch all current collected files +
-  for (const { url, type } of [...collected.values()]) { +
-    const href = url.href; +
-    const localPath = toLocalPath(url, type === "html"); +
     try {     try {
-      if (type === "html&& href === pageUrl.href{ +      for (const rule of Array.from(sheet.cssRules || [])) { 
-        continue;+        const txt rule.cssText || ""
 +        const re /url\((['"]?)(.*?)\1\)/g; 
 +        let m; 
 +        while ((m = re.exec(txt))) addUrl(m[2], "asset");
       }       }
- 
-      const bytes = await fetchBinary(href); 
-      fileMap.set(href, { 
-        url: href, 
-        localPath, 
-        bytes, 
-        contentType: "", 
-        text: null 
-      }); 
-      console.log("saved:", localPath); 
     } catch (e) {     } catch (e) {
-      failed.push({ url: href, error: String(e) }); +      // cross-origin stylesheet access can fail
-      console.warn("failed:", href, e);+
     }     }
   }   }
  
-  // 2) bring CSS files as text, collect nested url(...) assets +  // Performance resources 
-  const cssCandidates = [...fileMap.values()].filter(=> /\.css(\?|$)/i.test(f.url) || /\.css$/i.test(f.localPath)); +  performance.getEntriesByType("resource").forEach(=> addUrl(r.name, "asset"));
-  for (const f of cssCandidates) { +
-    try { +
-      const txt = decoder.decode(f.bytes); +
-      f.text = txt; +
-      const nested = extractCssUrls(txt, f.url); +
-      for (const u of nested) { +
-        addUrl(u, "asset", f.url)+
-      } +
-    } catch (e) { +
-      console.warn("css decode failed:", f.url, e); +
-    } +
-  }+
  
-  // 3fetch newly found nested CSS assets +  // Save HTML of current page into path-based index.html 
-  for (const { url, type } of [...collected.values()]+  const currentHtmlParts = toLocalPathParts(currentUrl, true); 
-    const href url.href; +  const htmlBlob = new Blob([document.documentElement.outerHTML]
-    if (href === pageUrl.hrefcontinue+    type: "text/html;charset=utf-8" 
-    if (fileMap.has(href)continue;+  }); 
 +  await writeFile(rootHandle, currentHtmlParts, htmlBlob);
  
-    const localPath = toLocalPath(url, type =="html")+  let ok 0
-    try { +  let fail 0
-      const bytes await fetchBinary(href)+  const failed = [];
-      fileMap.set(href,+
-        url: href, +
-        localPath, +
-        bytes, +
-        contentType: "", +
-        text: null +
-      }); +
-      console.log("saved nested:", localPath); +
-    } catch (e) { +
-      failed.push({ url: href, error: String(e) }); +
-      console.warn("failed nested:", href, e); +
-    } +
-  }+
  
-  // 4) rewrite CSS url(...) to local relative paths +  for (const { url, type } of collected.values()) { 
-  for (const of [...fileMap.values()]) { +    if (type === "html" && url.href === currentUrl.href) continue;
-    if (!(/\.css(\?|$)/i.test(f.url) || /\.css$/i.test(f.localPath))) continue;+
  
     try {     try {
-      let txt f.text != null ? f.text : decoder.decode(f.bytes); +      const res await fetch(url.href, { credentials: "include}); 
- +      if (!res.okthrow new Error(`HTTP ${res.status}`);
-      txt = txt.replace(cssUrlRegex(full, q, raw) => { +
-        const val = (raw || "").trim(); +
-        if (!val || val.startsWith("data:") || val.startsWith("blob:") || val.startsWith("#")return full+
- +
-        const nu = normalizeUrl(val, f.url); +
-        if (!nu) return full; +
- +
-        const target = fileMap.get(nu.href)+
-        if (!target) return full; +
- +
-        const rel = relativePath(f.localPath, target.localPath); +
-        return `url("${rel}")`+
-      });+
  
-      f.bytes encoder.encode(txt); +      const blob await res.blob(); 
-      f.text txt;+      const pathParts toLocalPathParts(url, false); 
 +      await writeFile(rootHandle, pathParts, blob); 
 +      ok++; 
 +      console.log("saved:", url.href, "->", pathParts.join("/"));
     } catch (e) {     } catch (e) {
-      console.warn("css rewrite failed:", f.url, e);+      fail++; 
 +      failed.push({ url: url.href, error: String(e) }); 
 +      console.warn("failed:", url.href, e);
     }     }
   }   }
  
-  // 5) rewrite HTML to local relative paths +  // save fail report 
-  const htmlDoc document.documentElement.cloneNode(true); +  const report = {
- +
-  const rewriteAttr = (selector, attr) => { +
-    htmlDoc.querySelectorAll(selector).forEach(el => { +
-      const raw = el.getAttribute(attr); +
-      if (!raw) return; +
-      if (raw.startsWith("data:") || raw.startsWith("blob:") || raw.startsWith("#")) return; +
- +
-      const nu = normalizeUrl(raw, location.href); +
-      if (!nu) return; +
- +
-      const target = fileMap.get(nu.href); +
-      if (!target) return; +
- +
-      el.setAttribute(attr, relativePath(pageLocalPath, target.localPath)); +
-    }); +
-  }; +
- +
-  rewriteAttr("script[src]", "src"); +
-  rewriteAttr("link[href]", "href"); +
-  rewriteAttr("img[src]", "src"); +
-  rewriteAttr("source[src]", "src"); +
-  rewriteAttr("video[src]", "src"); +
-  rewriteAttr("audio[src]", "src"); +
-  rewriteAttr("iframe[src]", "src"); +
-  rewriteAttr("a[href]", "href"); +
- +
-  htmlDoc.querySelectorAll("[style]").forEach(el => { +
-    const style = el.getAttribute("style") || ""; +
-    const replaced = style.replace(cssUrlRegex, (full, q, raw) => { +
-      const val = (raw || "").trim(); +
-      if (!val || val.startsWith("data:") || val.startsWith("blob:") || val.startsWith("#")) return full; +
- +
-      const nu = normalizeUrl(val, location.href); +
-      if (!nu) return full; +
- +
-      const target = fileMap.get(nu.href); +
-      if (!target) return full; +
- +
-      return `url("${relativePath(pageLocalPath, target.localPath)}")`; +
-    }); +
-    el.setAttribute("style", replaced); +
-  }); +
- +
-  const htmlText = "<!DOCTYPE html>\n" + htmlDoc.outerHTML; +
-  fileMap.set(pageUrl.href,+
-    url: pageUrl.href, +
-    localPath: pageLocalPath, +
-    bytes: encoder.encode(htmlText), +
-    contentType: "text/html", +
-    text: htmlText +
-  }); +
- +
-  const reportText = JSON.stringify({+
     page: location.href,     page: location.href,
-    savedPagepageLocalPath+    savedHtmlAscurrentHtmlParts.join("/")
-    savedCount: fileMap.size+    savedCount: ok
-    failedCount: failed.length,+    failedCount: fail,
     failed     failed
-  }, null, 2); 
- 
-  fileMap.set("__REPORT__", { 
-    url: "__REPORT__", 
-    localPath: "_download_report.json", 
-    bytes: encoder.encode(reportText), 
-    contentType: "application/json", 
-    text: reportText 
-  }); 
- 
-  // 6) TAR builder 
-  const padOctal = (num, width) => { 
-    const s = num.toString(8); 
-    return s.padStart(width - 1, "0") + "\0"; 
   };   };
  
-  const writeString = (buf, offset, str, length) => { +  await writeFile
-    const bytes = encoder.encode(str); +    rootHandle
-    buf.set(bytes.slice(0length), offset); +    ["_download_report.json"]
-  }; +    new Blob([JSON.stringify(reportnull2)], { type: "application/json" }) 
- +  );
-  const writeOctal = (buf, offset, num, length) => { +
-    writeString(buf, offset, padOctal(num, length), length); +
-  }; +
- +
-  const tarHeader = (name, size, mtime = Math.floor(Date.now() / 1000), mode = 0o644) => { +
-    const buf = new Uint8Array(512); +
-    writeString(buf, 0, name, 100); +
-    writeOctal(buf, 100, mode, 8); +
-    writeOctal(buf, 108, 0, 8); +
-    writeOctal(buf, 116, 0, 8); +
-    writeOctal(buf, 124, size, 12); +
-    writeOctal(buf, 136, mtime, 12); +
-    for (let i = 148; i < 156; i++) buf[i] = 32; +
-    buf[156] = "0".charCodeAt(0); +
-    writeString(buf, 257, "ustar", 6); +
-    writeString(buf, 263, "00", 2); +
- +
-    let sum = 0; +
-    for (let i = 0; i < 512; i++) sum += buf[i]; +
-    const chk = sum.toString(8).padStart(6"0"); +
-    writeString(buf148, chk, 6)+
-    buf[154= 0; +
-    buf[155] = 32; +
- +
-    return buf; +
-  }; +
- +
-  const tarParts = []; +
-  let totalBytes = 0; +
- +
-  for (const f of [...fileMap.values()]) { +
-    const name = f.localPath; +
-    const body = f.bytes instanceof Uint8Array ? f.bytes : new Uint8Array(f.bytes); +
-    const header = tarHeader(name, body.length); +
- +
-    tarParts.push(header); +
-    tarParts.push(body); +
-    totalBytes += 512 + body.length; +
- +
-    const pad = (512 - (body.length % 512)) % 512; +
-    if (pad) { +
-      tarParts.push(new Uint8Array(pad)); +
-      totalBytes += pad; +
-    } +
-  } +
- +
-  tarParts.push(new Uint8Array(1024)); +
-  totalBytes += 1024; +
- +
-  const tarBlob = new Blob(tarParts, { type: "application/x-tar" })+
- +
-  const a = document.createElement("a"); +
-  a.href = URL.createObjectURL(tarBlob); +
- +
-  const safeName = (document.title || "page").replace(/[\\/:*?"<>|]+/g, "_").slice(0, 80) || "page"; +
-  a.download = `${safeName}.tar`; +
-  a.click(); +
- +
-  setTimeout(() => URL.revokeObjectURL(a.href), 5000);+
  
   console.log("DONE");   console.log("DONE");
-  console.log("page:", pageLocalPath); +  console.log("HTML:", currentHtmlParts.join("/")); 
-  console.log("saved:", fileMap.size); +  console.log("saved assets:", ok); 
-  console.log("failed:", failed.length);+  console.log("failed assets:", fail);
 })(); })();
- 
 </code> </code>
test1/subject6.1775961924.txt.gz · Last modified: by admin · Currently locked by: 127.0.0.1,192.168.0.3