Created
July 3, 2025 08:03
-
-
Save LoneDev6/d0b96d5c512081b0066df1e2351aa6ab to your computer and use it in GitHub Desktop.
Download all files in an apache listing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
const https = require("https"); | |
const http = require("http"); | |
const fs = require("fs"); | |
const path = require("path"); | |
const { JSDOM } = require("jsdom"); | |
if (process.argv.length < 3) { | |
console.error("Usage: node download-apache-listing.js <url>"); | |
process.exit(1); | |
} | |
const rootUrl = process.argv[2].endsWith("/") | |
? process.argv[2] | |
: process.argv[2] + "/"; | |
const urlObj = new URL(rootUrl); | |
const protocol = urlObj.protocol === "https:" ? https : http; | |
const visited = new Set(); | |
function fetchListing(url) { | |
return new Promise((resolve, reject) => { | |
protocol | |
.get( | |
url, | |
{ rejectUnauthorized: false }, // ignore SSL | |
(res) => { | |
if (res.statusCode !== 200) { | |
reject( | |
new Error(`Request Failed: ${res.statusCode} ${res.statusMessage}`) | |
); | |
return; | |
} | |
let raw = ""; | |
res.on("data", (chunk) => (raw += chunk)); | |
res.on("end", () => resolve(raw)); | |
} | |
) | |
.on("error", reject); | |
}); | |
} | |
function downloadFile(url, localPath) { | |
return new Promise((resolve, reject) => { | |
const file = fs.createWriteStream(localPath); | |
protocol | |
.get( | |
url, | |
{ rejectUnauthorized: false }, // ignore SSL | |
(res) => { | |
if (res.statusCode !== 200) { | |
reject( | |
new Error(`Download failed: ${res.statusCode} ${res.statusMessage}`) | |
); | |
return; | |
} | |
res.pipe(file); | |
file.on("finish", () => file.close(resolve)); | |
} | |
) | |
.on("error", (err) => { | |
fs.unlink(localPath, () => reject(err)); | |
}); | |
}); | |
} | |
async function crawl(url, baseDir) { | |
if (visited.has(url)) return; | |
visited.add(url); | |
console.log(`Listing: ${url}`); | |
const html = await fetchListing(url); | |
const dom = new JSDOM(html); | |
const links = [...dom.window.document.querySelectorAll("a")]; | |
for (const link of links) { | |
const href = link.getAttribute("href"); | |
if ( | |
!href || | |
href === "../" || | |
href.startsWith("?") || | |
href.startsWith("#") | |
) { | |
continue; | |
} | |
const newUrl = new URL(href, url).toString(); | |
if (href.endsWith("/")) { | |
// Directory | |
const subDir = path.join(baseDir, href); | |
await crawl(newUrl, subDir); | |
} else { | |
// File | |
const localPath = path.join(baseDir, href); | |
const dirName = path.dirname(localPath); | |
if (!fs.existsSync(dirName)) { | |
fs.mkdirSync(dirName, { recursive: true }); | |
} | |
console.log(`Downloading: ${newUrl}`); | |
await downloadFile(newUrl, localPath); | |
} | |
} | |
} | |
(async () => { | |
const outDir = path.basename(urlObj.pathname) || "download"; | |
if (!fs.existsSync(outDir)) { | |
fs.mkdirSync(outDir); | |
} | |
try { | |
await crawl(rootUrl, outDir); | |
console.log("✅ Done."); | |
} catch (err) { | |
console.error("❌ Error:", err); | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment