Moves API handlers from server/lambda to /api
This commit is contained in:
63
api/check-hsts.js
Normal file
63
api/check-hsts.js
Normal file
@@ -0,0 +1,63 @@
|
||||
const https = require('https');
|
||||
|
||||
exports.handler = async function(event, context) {
|
||||
const siteURL = event.queryStringParameters.url;
|
||||
|
||||
const errorResponse = (message, statusCode = 500) => {
|
||||
return {
|
||||
statusCode: statusCode,
|
||||
body: JSON.stringify({ error: message }),
|
||||
};
|
||||
};
|
||||
const hstsIncompatible = (message, statusCode = 200) => {
|
||||
return {
|
||||
statusCode: statusCode,
|
||||
body: JSON.stringify({ message, compatible: false }),
|
||||
};
|
||||
};
|
||||
|
||||
if (!siteURL) {
|
||||
return {
|
||||
statusCode: 400,
|
||||
body: JSON.stringify({ error: 'URL parameter is missing!' }),
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = https.request(siteURL, res => {
|
||||
const headers = res.headers;
|
||||
const hstsHeader = headers['strict-transport-security'];
|
||||
|
||||
if (!hstsHeader) {
|
||||
resolve(hstsIncompatible(`Site does not serve any HSTS headers.`));
|
||||
} else {
|
||||
const maxAgeMatch = hstsHeader.match(/max-age=(\d+)/);
|
||||
const includesSubDomains = hstsHeader.includes('includeSubDomains');
|
||||
const preload = hstsHeader.includes('preload');
|
||||
|
||||
if (!maxAgeMatch || parseInt(maxAgeMatch[1]) < 10886400) {
|
||||
resolve(hstsIncompatible(`HSTS max-age is less than 10886400.`));
|
||||
} else if (!includesSubDomains) {
|
||||
resolve(hstsIncompatible(`HSTS header does not include all subdomains.`));
|
||||
} else if (!preload) {
|
||||
resolve(hstsIncompatible(`HSTS header does not contain the preload directive.`));
|
||||
} else {
|
||||
resolve({
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({
|
||||
message: "Site is compatible with the HSTS preload list!",
|
||||
compatible: true,
|
||||
hstsHeader: hstsHeader,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
resolve(errorResponse(`Error making request: ${error.message}`));
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
};
|
||||
90
api/check-ports.js
Normal file
90
api/check-ports.js
Normal file
@@ -0,0 +1,90 @@
|
||||
const net = require('net');
|
||||
|
||||
// A list of commonly used ports.
|
||||
const PORTS = [
|
||||
20, 21, 22, 23, 25, 53, 80, 67, 68, 69,
|
||||
110, 119, 123, 143, 156, 161, 162, 179, 194,
|
||||
389, 443, 587, 993, 995,
|
||||
3000, 3306, 3389, 5060, 5900, 8000, 8080, 8888
|
||||
];
|
||||
|
||||
async function checkPort(port, domain) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const socket = new net.Socket();
|
||||
|
||||
socket.setTimeout(1500); // you may want to adjust the timeout
|
||||
|
||||
socket.once('connect', () => {
|
||||
socket.destroy();
|
||||
resolve(port);
|
||||
});
|
||||
|
||||
socket.once('timeout', () => {
|
||||
socket.destroy();
|
||||
reject(new Error(`Timeout at port: ${port}`));
|
||||
});
|
||||
|
||||
socket.once('error', (e) => {
|
||||
socket.destroy();
|
||||
reject(e);
|
||||
});
|
||||
|
||||
socket.connect(port, domain);
|
||||
});
|
||||
}
|
||||
|
||||
exports.handler = async (event, context) => {
|
||||
const domain = event.queryStringParameters.url;
|
||||
|
||||
if (!domain) {
|
||||
return errorResponse('Missing domain parameter.');
|
||||
}
|
||||
|
||||
const delay = ms => new Promise(res => setTimeout(res, ms));
|
||||
const timeout = delay(9000);
|
||||
|
||||
const openPorts = [];
|
||||
const failedPorts = [];
|
||||
|
||||
const promises = PORTS.map(port => checkPort(port, domain)
|
||||
.then(() => {
|
||||
openPorts.push(port);
|
||||
return { status: 'fulfilled', port };
|
||||
})
|
||||
.catch(() => {
|
||||
failedPorts.push(port);
|
||||
return { status: 'rejected', port };
|
||||
}));
|
||||
|
||||
let timeoutReached = false;
|
||||
|
||||
for (const promise of promises) {
|
||||
const result = await Promise.race([promise, timeout.then(() => ({ status: 'timeout', timeout: true }))]);
|
||||
if (result.status === 'timeout') {
|
||||
timeoutReached = true;
|
||||
if (result.timeout) {
|
||||
// Add the ports not checked yet to the failedPorts array
|
||||
const checkedPorts = [...openPorts, ...failedPorts];
|
||||
const portsNotChecked = PORTS.filter(port => !checkedPorts.includes(port));
|
||||
failedPorts.push(...portsNotChecked);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(timeoutReached){
|
||||
return errorResponse('The function timed out before completing.');
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({ openPorts, failedPorts }),
|
||||
};
|
||||
};
|
||||
|
||||
const errorResponse = (message, statusCode = 444) => {
|
||||
return {
|
||||
statusCode: statusCode,
|
||||
body: JSON.stringify({ error: message }),
|
||||
};
|
||||
};
|
||||
69
api/dns-sec.js
Normal file
69
api/dns-sec.js
Normal file
@@ -0,0 +1,69 @@
|
||||
const https = require('https');
|
||||
|
||||
exports.handler = async function(event, context) {
|
||||
let { url } = event.queryStringParameters;
|
||||
|
||||
if (!url) {
|
||||
return errorResponse('URL query parameter is required.');
|
||||
}
|
||||
|
||||
// Extract hostname from URL
|
||||
const parsedUrl = new URL(url);
|
||||
const domain = parsedUrl.hostname;
|
||||
|
||||
const dnsTypes = ['DNSKEY', 'DS', 'RRSIG'];
|
||||
const records = {};
|
||||
|
||||
for (const type of dnsTypes) {
|
||||
const options = {
|
||||
hostname: 'dns.google',
|
||||
path: `/resolve?name=${encodeURIComponent(domain)}&type=${type}`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/dns-json'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const dnsResponse = await new Promise((resolve, reject) => {
|
||||
const req = https.request(options, res => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
resolve(JSON.parse(data));
|
||||
});
|
||||
|
||||
res.on('error', error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
|
||||
if (dnsResponse.Answer) {
|
||||
records[type] = { isFound: true, answer: dnsResponse.Answer, response: dnsResponse.Answer };
|
||||
} else {
|
||||
records[type] = { isFound: false, answer: null, response: dnsResponse};
|
||||
}
|
||||
} catch (error) {
|
||||
return errorResponse(`Error fetching ${type} record: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(records),
|
||||
};
|
||||
};
|
||||
|
||||
const errorResponse = (message, statusCode = 444) => {
|
||||
return {
|
||||
statusCode: statusCode,
|
||||
body: JSON.stringify({ error: message }),
|
||||
};
|
||||
};
|
||||
48
api/dns-server.js
Normal file
48
api/dns-server.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const dns = require('dns');
|
||||
const dnsPromises = dns.promises;
|
||||
// const https = require('https');
|
||||
const axios = require('axios');
|
||||
|
||||
exports.handler = async (event) => {
|
||||
const domain = event.queryStringParameters.url.replace(/^(?:https?:\/\/)?/i, "");
|
||||
try {
|
||||
const addresses = await dnsPromises.resolve4(domain);
|
||||
const results = await Promise.all(addresses.map(async (address) => {
|
||||
const hostname = await dnsPromises.reverse(address).catch(() => null);
|
||||
let dohDirectSupports = false;
|
||||
try {
|
||||
await axios.get(`https://${address}/dns-query`);
|
||||
dohDirectSupports = true;
|
||||
} catch (error) {
|
||||
dohDirectSupports = false;
|
||||
}
|
||||
return {
|
||||
address,
|
||||
hostname,
|
||||
dohDirectSupports,
|
||||
};
|
||||
}));
|
||||
// let dohMozillaSupport = false;
|
||||
// try {
|
||||
// const mozillaList = await axios.get('https://firefox.settings.services.mozilla.com/v1/buckets/security-state/collections/onecrl/records');
|
||||
// dohMozillaSupport = results.some(({ hostname }) => mozillaList.data.data.some(({ id }) => id.includes(hostname)));
|
||||
// } catch (error) {
|
||||
// console.error(error);
|
||||
// }
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({
|
||||
domain,
|
||||
dns: results,
|
||||
// dohMozillaSupport,
|
||||
}),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
statusCode: 500,
|
||||
body: JSON.stringify({
|
||||
error: `An error occurred while resolving DNS. ${error.message}`,
|
||||
}),
|
||||
};
|
||||
}
|
||||
};
|
||||
33
api/find-url-ip.js
Normal file
33
api/find-url-ip.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const dns = require('dns');
|
||||
|
||||
/* Lambda function to fetch the IP address of a given URL */
|
||||
exports.handler = function (event, context, callback) {
|
||||
const addressParam = event.queryStringParameters.url;
|
||||
|
||||
if (!addressParam) {
|
||||
callback(null, errorResponse('Address parameter is missing.'));
|
||||
return;
|
||||
}
|
||||
|
||||
const address = decodeURIComponent(addressParam)
|
||||
.replaceAll('https://', '')
|
||||
.replaceAll('http://', '');
|
||||
|
||||
dns.lookup(address, (err, ip, family) => {
|
||||
if (err) {
|
||||
callback(null, errorResponse(err.message));
|
||||
} else {
|
||||
callback(null, {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({ ip, family }),
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const errorResponse = (message, statusCode = 444) => {
|
||||
return {
|
||||
statusCode: statusCode,
|
||||
body: JSON.stringify({ error: message }),
|
||||
};
|
||||
};
|
||||
35
api/follow-redirects.js
Normal file
35
api/follow-redirects.js
Normal file
@@ -0,0 +1,35 @@
|
||||
exports.handler = async (event) => {
|
||||
const { url } = event.queryStringParameters;
|
||||
const redirects = [url];
|
||||
|
||||
try {
|
||||
const got = await import('got');
|
||||
await got.default(url, {
|
||||
followRedirect: true,
|
||||
maxRedirects: 12,
|
||||
hooks: {
|
||||
beforeRedirect: [
|
||||
(options, response) => {
|
||||
redirects.push(response.headers.location);
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({
|
||||
redirects: redirects,
|
||||
}),
|
||||
};
|
||||
} catch (error) {
|
||||
return errorResponse(`Error: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
const errorResponse = (message, statusCode = 444) => {
|
||||
return {
|
||||
statusCode: statusCode,
|
||||
body: JSON.stringify({ error: message }),
|
||||
};
|
||||
};
|
||||
55
api/get-carbon.js
Normal file
55
api/get-carbon.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const https = require('https');
|
||||
|
||||
exports.handler = async (event, context) => {
|
||||
const { url } = event.queryStringParameters;
|
||||
|
||||
if (!url) {
|
||||
return {
|
||||
statusCode: 400,
|
||||
body: JSON.stringify({ error: 'url query parameter is required' }),
|
||||
};
|
||||
}
|
||||
|
||||
// First, get the size of the website's HTML
|
||||
const getHtmlSize = (url) => new Promise((resolve, reject) => {
|
||||
https.get(url, res => {
|
||||
let data = '';
|
||||
res.on('data', chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
res.on('end', () => {
|
||||
const sizeInBytes = Buffer.byteLength(data, 'utf8');
|
||||
resolve(sizeInBytes);
|
||||
});
|
||||
}).on('error', reject);
|
||||
});
|
||||
|
||||
try {
|
||||
const sizeInBytes = await getHtmlSize(url);
|
||||
const apiUrl = `https://api.websitecarbon.com/data?bytes=${sizeInBytes}&green=0`;
|
||||
|
||||
// Then use that size to get the carbon data
|
||||
const carbonData = await new Promise((resolve, reject) => {
|
||||
https.get(apiUrl, res => {
|
||||
let data = '';
|
||||
res.on('data', chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
res.on('end', () => {
|
||||
resolve(JSON.parse(data));
|
||||
});
|
||||
}).on('error', reject);
|
||||
});
|
||||
|
||||
carbonData.scanUrl = url;
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(carbonData),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
statusCode: 500,
|
||||
body: JSON.stringify({ error: `Error: ${error.message}` }),
|
||||
};
|
||||
}
|
||||
};
|
||||
26
api/get-cookies.js
Normal file
26
api/get-cookies.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const axios = require('axios');
|
||||
|
||||
exports.handler = async function(event, context) {
|
||||
const { url } = event.queryStringParameters;
|
||||
|
||||
if (!url) {
|
||||
return {
|
||||
statusCode: 400,
|
||||
body: JSON.stringify({ message: 'url query string parameter is required' }),
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get(url, {withCredentials: true});
|
||||
const cookies = response.headers['set-cookie'];
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({ cookies }),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
statusCode: 500,
|
||||
body: JSON.stringify({ error: error.message }),
|
||||
};
|
||||
}
|
||||
};
|
||||
59
api/get-dns.js
Normal file
59
api/get-dns.js
Normal file
@@ -0,0 +1,59 @@
|
||||
const dns = require('dns');
|
||||
const util = require('util');
|
||||
|
||||
exports.handler = async function(event, context) {
|
||||
let hostname = event.queryStringParameters.url;
|
||||
|
||||
// Handle URLs by extracting hostname
|
||||
if (hostname.startsWith('http://') || hostname.startsWith('https://')) {
|
||||
hostname = new URL(hostname).hostname;
|
||||
}
|
||||
|
||||
try {
|
||||
const lookupPromise = util.promisify(dns.lookup);
|
||||
const resolve4Promise = util.promisify(dns.resolve4);
|
||||
const resolve6Promise = util.promisify(dns.resolve6);
|
||||
const resolveMxPromise = util.promisify(dns.resolveMx);
|
||||
const resolveTxtPromise = util.promisify(dns.resolveTxt);
|
||||
const resolveNsPromise = util.promisify(dns.resolveNs);
|
||||
const resolveCnamePromise = util.promisify(dns.resolveCname);
|
||||
const resolveSoaPromise = util.promisify(dns.resolveSoa);
|
||||
const resolveSrvPromise = util.promisify(dns.resolveSrv);
|
||||
const resolvePtrPromise = util.promisify(dns.resolvePtr);
|
||||
|
||||
const [a, aaaa, mx, txt, ns, cname, soa, srv, ptr] = await Promise.all([
|
||||
lookupPromise(hostname),
|
||||
resolve4Promise(hostname).catch(() => []), // A record
|
||||
resolve6Promise(hostname).catch(() => []), // AAAA record
|
||||
resolveMxPromise(hostname).catch(() => []), // MX record
|
||||
resolveTxtPromise(hostname).catch(() => []), // TXT record
|
||||
resolveNsPromise(hostname).catch(() => []), // NS record
|
||||
resolveCnamePromise(hostname).catch(() => []), // CNAME record
|
||||
resolveSoaPromise(hostname).catch(() => []), // SOA record
|
||||
resolveSrvPromise(hostname).catch(() => []), // SRV record
|
||||
resolvePtrPromise(hostname).catch(() => []) // PTR record
|
||||
]);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({
|
||||
A: a,
|
||||
AAAA: aaaa,
|
||||
MX: mx,
|
||||
TXT: txt,
|
||||
NS: ns,
|
||||
CNAME: cname,
|
||||
SOA: soa,
|
||||
SRV: srv,
|
||||
PTR: ptr
|
||||
})
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
statusCode: 500,
|
||||
body: JSON.stringify({
|
||||
error: error.message
|
||||
})
|
||||
};
|
||||
}
|
||||
};
|
||||
31
api/get-headers.js
Normal file
31
api/get-headers.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const axios = require('axios');
|
||||
|
||||
exports.handler = async function(event, context) {
|
||||
const { url } = event.queryStringParameters;
|
||||
|
||||
if (!url) {
|
||||
return {
|
||||
statusCode: 400,
|
||||
body: JSON.stringify({ error: 'url query string parameter is required' }),
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get(url, {
|
||||
validateStatus: function (status) {
|
||||
return status >= 200 && status < 600; // Resolve only if the status code is less than 600
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(response.headers),
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return {
|
||||
statusCode: 500,
|
||||
body: JSON.stringify({ error: error.message }),
|
||||
};
|
||||
}
|
||||
};
|
||||
30
api/get-txt.js
Normal file
30
api/get-txt.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const dns = require('dns').promises;
|
||||
|
||||
exports.handler = async (event) => {
|
||||
const url = new URL(event.queryStringParameters.url);
|
||||
try {
|
||||
const txtRecords = await dns.resolveTxt(url.hostname);
|
||||
|
||||
// Parsing and formatting TXT records into a single object
|
||||
const readableTxtRecords = txtRecords.reduce((acc, recordArray) => {
|
||||
const recordObject = recordArray.reduce((recordAcc, recordString) => {
|
||||
const splitRecord = recordString.split('=');
|
||||
const key = splitRecord[0];
|
||||
const value = splitRecord.slice(1).join('=');
|
||||
return { ...recordAcc, [key]: value };
|
||||
}, {});
|
||||
return { ...acc, ...recordObject };
|
||||
}, {});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(readableTxtRecords),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
return {
|
||||
statusCode: 500,
|
||||
body: JSON.stringify({ error: error.message }),
|
||||
};
|
||||
}
|
||||
};
|
||||
32
api/lighthouse-report.js
Normal file
32
api/lighthouse-report.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const axios = require('axios');
|
||||
|
||||
exports.handler = function(event, context, callback) {
|
||||
const { url } = event.queryStringParameters;
|
||||
|
||||
if (!url) {
|
||||
callback(null, {
|
||||
statusCode: 400,
|
||||
body: JSON.stringify({ error: 'URL param is required'}),
|
||||
});
|
||||
}
|
||||
|
||||
const apiKey = process.env.GOOGLE_CLOUD_API_KEY;
|
||||
const endpoint = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=${encodeURIComponent(url)}&category=PERFORMANCE&category=ACCESSIBILITY&category=BEST_PRACTICES&category=SEO&category=PWA&strategy=mobile&key=${apiKey}`;
|
||||
|
||||
axios.get(endpoint)
|
||||
.then(
|
||||
(response) => {
|
||||
callback(null, {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(response.data),
|
||||
});
|
||||
}
|
||||
).catch(
|
||||
() => {
|
||||
callback(null, {
|
||||
statusCode: 500,
|
||||
body: JSON.stringify({ error: 'Error running Lighthouse'}),
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
45
api/read-robots-txt.js
Normal file
45
api/read-robots-txt.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const axios = require('axios');
|
||||
|
||||
exports.handler = async function(event, context) {
|
||||
const siteURL = event.queryStringParameters.url;
|
||||
|
||||
if (!siteURL) {
|
||||
return {
|
||||
statusCode: 400,
|
||||
body: JSON.stringify({ error: 'Missing url query parameter' }),
|
||||
};
|
||||
}
|
||||
|
||||
let parsedURL;
|
||||
try {
|
||||
parsedURL = new URL(siteURL);
|
||||
} catch (error) {
|
||||
return {
|
||||
statusCode: 400,
|
||||
body: JSON.stringify({ error: 'Invalid url query parameter' }),
|
||||
};
|
||||
}
|
||||
|
||||
const robotsURL = `${parsedURL.protocol}//${parsedURL.hostname}/robots.txt`;
|
||||
|
||||
try {
|
||||
const response = await axios.get(robotsURL);
|
||||
|
||||
if (response.status === 200) {
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: response.data,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
statusCode: response.status,
|
||||
body: JSON.stringify({ error: 'Failed to fetch robots.txt', statusCode: response.status }),
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
statusCode: 500,
|
||||
body: JSON.stringify({ error: `Error fetching robots.txt: ${error.message}` }),
|
||||
};
|
||||
}
|
||||
};
|
||||
80
api/screenshot.js
Normal file
80
api/screenshot.js
Normal file
@@ -0,0 +1,80 @@
|
||||
const puppeteer = require('puppeteer-core');
|
||||
const chromium = require('chrome-aws-lambda');
|
||||
|
||||
exports.handler = async (event, context, callback) => {
|
||||
let browser = null;
|
||||
let targetUrl = event.queryStringParameters.url;
|
||||
|
||||
if (!targetUrl) {
|
||||
callback(null, {
|
||||
statusCode: 400,
|
||||
body: JSON.stringify({ error: 'URL is missing from queryStringParameters' }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!targetUrl.startsWith('http://') && !targetUrl.startsWith('https://')) {
|
||||
targetUrl = 'http://' + targetUrl;
|
||||
}
|
||||
|
||||
try {
|
||||
new URL(targetUrl);
|
||||
} catch (error) {
|
||||
callback(null, {
|
||||
statusCode: 400,
|
||||
body: JSON.stringify({ error: 'URL provided is invalid' }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
browser = await puppeteer.launch({
|
||||
args: chromium.args,
|
||||
defaultViewport: { width: 800, height: 600 },
|
||||
executablePath: process.env.CHROME_PATH || await chromium.executablePath,
|
||||
headless: chromium.headless,
|
||||
ignoreHTTPSErrors: true,
|
||||
ignoreDefaultArgs: ['--disable-extensions'],
|
||||
});
|
||||
|
||||
let page = await browser.newPage();
|
||||
|
||||
await page.emulateMediaFeatures([{ name: 'prefers-color-scheme', value: 'dark' }]);
|
||||
|
||||
page.setDefaultNavigationTimeout(8000);
|
||||
|
||||
await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
|
||||
|
||||
await page.evaluate(() => {
|
||||
const selector = 'body';
|
||||
return new Promise((resolve, reject) => {
|
||||
const element = document.querySelector(selector);
|
||||
if (!element) {
|
||||
reject(new Error(`Error: No element found with selector: ${selector}`));
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
const screenshotBuffer = await page.screenshot();
|
||||
|
||||
const base64Screenshot = screenshotBuffer.toString('base64');
|
||||
|
||||
const response = {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({ image: base64Screenshot }),
|
||||
};
|
||||
|
||||
callback(null, response);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
callback(null, {
|
||||
statusCode: 500,
|
||||
body: JSON.stringify({ error: `An error occurred: ${error.message}` }),
|
||||
});
|
||||
} finally {
|
||||
if (browser !== null) {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
69
api/server-status.js
Normal file
69
api/server-status.js
Normal file
@@ -0,0 +1,69 @@
|
||||
const https = require('https');
|
||||
const { performance, PerformanceObserver } = require('perf_hooks');
|
||||
|
||||
exports.handler = async function(event, context) {
|
||||
const { url } = event.queryStringParameters;
|
||||
|
||||
if (!url) {
|
||||
return {
|
||||
statusCode: 400,
|
||||
body: JSON.stringify({ error: 'You must provide a URL query parameter!' }),
|
||||
};
|
||||
}
|
||||
|
||||
let dnsLookupTime;
|
||||
let responseCode;
|
||||
let startTime;
|
||||
|
||||
const obs = new PerformanceObserver((items) => {
|
||||
dnsLookupTime = items.getEntries()[0].duration;
|
||||
performance.clearMarks();
|
||||
});
|
||||
|
||||
obs.observe({ entryTypes: ['measure'] });
|
||||
|
||||
performance.mark('A');
|
||||
|
||||
try {
|
||||
startTime = performance.now();
|
||||
const response = await new Promise((resolve, reject) => {
|
||||
const req = https.get(url, res => {
|
||||
let data = '';
|
||||
responseCode = res.statusCode;
|
||||
res.on('data', chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
res.on('end', () => {
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', reject);
|
||||
req.end();
|
||||
});
|
||||
|
||||
if (responseCode < 200 || responseCode >= 400) {
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({ error: `Received non-success response code: ${responseCode}` }),
|
||||
};
|
||||
}
|
||||
|
||||
performance.mark('B');
|
||||
performance.measure('A to B', 'A', 'B');
|
||||
let responseTime = performance.now() - startTime;
|
||||
obs.disconnect();
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({ isUp: true, dnsLookupTime, responseTime, responseCode }),
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
obs.disconnect();
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({ error: `Error during operation: ${error.message}` }),
|
||||
};
|
||||
}
|
||||
};
|
||||
56
api/site-features.js
Normal file
56
api/site-features.js
Normal file
@@ -0,0 +1,56 @@
|
||||
const https = require('https');
|
||||
|
||||
exports.handler = async function (event, context) {
|
||||
const { url } = event.queryStringParameters;
|
||||
const apiKey = process.env.BUILT_WITH_API_KEY;
|
||||
|
||||
const errorResponse = (message, statusCode = 500) => {
|
||||
return {
|
||||
statusCode: statusCode,
|
||||
body: JSON.stringify({ error: message }),
|
||||
};
|
||||
};
|
||||
|
||||
if (!url) {
|
||||
return errorResponse('URL query parameter is required', 400);
|
||||
}
|
||||
|
||||
if (!apiKey) {
|
||||
return errorResponse('Missing BuiltWith API key in environment variables', 500);
|
||||
}
|
||||
|
||||
const apiUrl = `https://api.builtwith.com/free1/api.json?KEY=${apiKey}&LOOKUP=${encodeURIComponent(url)}`;
|
||||
|
||||
try {
|
||||
const response = await new Promise((resolve, reject) => {
|
||||
const req = https.get(apiUrl, res => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
if (res.statusCode >= 200 && res.statusCode <= 299) {
|
||||
resolve(data);
|
||||
} else {
|
||||
reject(new Error(`Request failed with status code: ${res.statusCode}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', error => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: response,
|
||||
};
|
||||
} catch (error) {
|
||||
return errorResponse(`Error making request: ${error.message}`);
|
||||
}
|
||||
};
|
||||
41
api/sitemap.js
Normal file
41
api/sitemap.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const axios = require('axios');
|
||||
const xml2js = require('xml2js');
|
||||
|
||||
exports.handler = async (event) => {
|
||||
const baseUrl = event.queryStringParameters.url.replace(/^(?:https?:\/\/)?/i, "");
|
||||
const url = baseUrl.startsWith('http') ? baseUrl : `http://${baseUrl}`;
|
||||
let sitemapUrl;
|
||||
|
||||
try {
|
||||
// Fetch robots.txt
|
||||
const robotsRes = await axios.get(`${url}/robots.txt`);
|
||||
const robotsTxt = robotsRes.data.split('\n');
|
||||
|
||||
for (let line of robotsTxt) {
|
||||
if (line.startsWith('Sitemap:')) {
|
||||
sitemapUrl = line.split(' ')[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (!sitemapUrl) {
|
||||
return {
|
||||
statusCode: 404,
|
||||
body: JSON.stringify({ error: 'Sitemap not found in robots.txt' }),
|
||||
};
|
||||
}
|
||||
|
||||
// Fetch sitemap
|
||||
const sitemapRes = await axios.get(sitemapUrl);
|
||||
const sitemap = await xml2js.parseStringPromise(sitemapRes.data);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(sitemap),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
statusCode: 500,
|
||||
body: JSON.stringify({ error: error.message }),
|
||||
};
|
||||
}
|
||||
};
|
||||
50
api/ssl-check.js
Normal file
50
api/ssl-check.js
Normal file
@@ -0,0 +1,50 @@
|
||||
const https = require('https');
|
||||
|
||||
exports.handler = async function (event, context) {
|
||||
const { url } = event.queryStringParameters;
|
||||
|
||||
const errorResponse = (message, statusCode = 500) => {
|
||||
return {
|
||||
statusCode: statusCode,
|
||||
body: JSON.stringify({ error: message }),
|
||||
};
|
||||
};
|
||||
|
||||
if (!url) {
|
||||
return errorResponse('URL query parameter is required', 400);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await new Promise((resolve, reject) => {
|
||||
const req = https.request(url, res => {
|
||||
|
||||
// Check if the SSL handshake was authorized
|
||||
if (!res.socket.authorized) {
|
||||
resolve(errorResponse(`SSL handshake not authorized. Reason: ${res.socket.authorizationError}`));
|
||||
} else {
|
||||
let cert = res.socket.getPeerCertificate(true);
|
||||
if (!cert || Object.keys(cert).length === 0) {
|
||||
resolve(errorResponse("No certificate presented by the server."));
|
||||
} else {
|
||||
// omit the raw and issuerCertificate fields
|
||||
const { raw, issuerCertificate, ...certWithoutRaw } = cert;
|
||||
resolve({
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(certWithoutRaw),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
req.on('error', error => {
|
||||
resolve(errorResponse(`Error fetching site certificate: ${error.message}`));
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
return errorResponse(`Unexpected error occurred: ${error.message}`);
|
||||
}
|
||||
};
|
||||
69
api/tech-stack.js
Normal file
69
api/tech-stack.js
Normal file
@@ -0,0 +1,69 @@
|
||||
const Wappalyzer = require('wappalyzer');
|
||||
|
||||
const analyze = async (url) => {
|
||||
|
||||
const options = {};
|
||||
|
||||
const wappalyzer = new Wappalyzer(options);
|
||||
return (async function() {
|
||||
try {
|
||||
await wappalyzer.init()
|
||||
const headers = {}
|
||||
const storage = {
|
||||
local: {},
|
||||
session: {},
|
||||
}
|
||||
const site = await wappalyzer.open(url, headers, storage)
|
||||
const results = await site.analyze()
|
||||
return results;
|
||||
} catch (error) {
|
||||
return error;
|
||||
} finally {
|
||||
await wappalyzer.destroy()
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
exports.handler = async (event, context, callback) => {
|
||||
// Validate URL parameter
|
||||
if (!event.queryStringParameters || !event.queryStringParameters.url) {
|
||||
return {
|
||||
statusCode: 400,
|
||||
body: JSON.stringify({ error: 'Missing url parameter' }),
|
||||
};
|
||||
}
|
||||
|
||||
// Get URL from param
|
||||
let url = event.queryStringParameters.url;
|
||||
if (!/^https?:\/\//i.test(url)) {
|
||||
url = 'http://' + url;
|
||||
}
|
||||
|
||||
try {
|
||||
return analyze(url).then(
|
||||
(results) => {
|
||||
if (!results.technologies || results.technologies.length === 0) {
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({ error: 'Unable to find any technologies for site' }),
|
||||
};
|
||||
}
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(results),
|
||||
}
|
||||
}
|
||||
)
|
||||
.catch((error) => {
|
||||
return {
|
||||
statusCode: 500,
|
||||
body: JSON.stringify({ error: error.message }),
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
return {
|
||||
statusCode: 500,
|
||||
body: JSON.stringify({ error: error.message }),
|
||||
};
|
||||
}
|
||||
};
|
||||
55
api/trace-route.js
Normal file
55
api/trace-route.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const traceroute = require('traceroute');
|
||||
const url = require('url');
|
||||
|
||||
exports.handler = async function(event, context) {
|
||||
const urlString = event.queryStringParameters.url;
|
||||
|
||||
try {
|
||||
if (!urlString) {
|
||||
throw new Error('URL parameter is missing!');
|
||||
}
|
||||
|
||||
// Parse the URL and get the hostname
|
||||
const urlObject = url.parse(urlString);
|
||||
const host = urlObject.hostname;
|
||||
|
||||
if (!host) {
|
||||
throw new Error('Invalid URL provided');
|
||||
}
|
||||
|
||||
// Traceroute with callback
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
traceroute.trace(host, (err, hops) => {
|
||||
if (err || !hops) {
|
||||
reject(err || new Error('No hops found'));
|
||||
} else {
|
||||
resolve(hops);
|
||||
}
|
||||
});
|
||||
|
||||
// Check if remaining time is less than 8.8 seconds, then reject promise
|
||||
if (context.getRemainingTimeInMillis() < 8800) {
|
||||
reject(new Error('Lambda is about to timeout'));
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({
|
||||
message: "Traceroute completed!",
|
||||
result,
|
||||
}),
|
||||
};
|
||||
} catch (err) {
|
||||
const message = err.code === 'ENOENT'
|
||||
? 'Traceroute command is not installed on the host.'
|
||||
: err.message;
|
||||
|
||||
return {
|
||||
statusCode: 500,
|
||||
body: JSON.stringify({
|
||||
error: message,
|
||||
}),
|
||||
};
|
||||
}
|
||||
};
|
||||
102
api/whois-lookup.js
Normal file
102
api/whois-lookup.js
Normal file
@@ -0,0 +1,102 @@
|
||||
const net = require('net');
|
||||
const psl = require('psl');
|
||||
// const { URL } = require('url');
|
||||
|
||||
const errorResponse = (message, statusCode = 444) => {
|
||||
return {
|
||||
statusCode: statusCode,
|
||||
body: JSON.stringify({ error: message }),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
const getBaseDomain = (url) => {
|
||||
let protocol = '';
|
||||
if (url.startsWith('http://')) {
|
||||
protocol = 'http://';
|
||||
} else if (url.startsWith('https://')) {
|
||||
protocol = 'https://';
|
||||
}
|
||||
let noProtocolUrl = url.replace(protocol, '');
|
||||
const parsed = psl.parse(noProtocolUrl);
|
||||
return protocol + parsed.domain;
|
||||
};
|
||||
|
||||
|
||||
exports.handler = async function(event, context) {
|
||||
let url = event.queryStringParameters.url;
|
||||
|
||||
if (!url) {
|
||||
return errorResponse('URL query parameter is required.', 400);
|
||||
}
|
||||
|
||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||
url = 'http://' + url;
|
||||
}
|
||||
|
||||
let hostname;
|
||||
try {
|
||||
hostname = getBaseDomain(new URL(url).hostname);
|
||||
} catch (error) {
|
||||
return errorResponse(`Unable to parse URL: ${error}`, 400);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const client = net.createConnection({ port: 43, host: 'whois.internic.net' }, () => {
|
||||
client.write(hostname + '\r\n');
|
||||
});
|
||||
|
||||
let data = '';
|
||||
client.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
client.on('end', () => {
|
||||
try {
|
||||
const parsedData = parseWhoisData(data);
|
||||
resolve({
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(parsedData),
|
||||
});
|
||||
} catch (error) {
|
||||
resolve(errorResponse(error.message));
|
||||
}
|
||||
});
|
||||
|
||||
client.on('error', (err) => {
|
||||
resolve(errorResponse(err.message, 500));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const parseWhoisData = (data) => {
|
||||
|
||||
if (data.includes('No match for')) {
|
||||
return { error: 'No matches found for domain in internic database'};
|
||||
}
|
||||
|
||||
const lines = data.split('\r\n');
|
||||
const parsedData = {};
|
||||
|
||||
let lastKey = '';
|
||||
|
||||
for (const line of lines) {
|
||||
const index = line.indexOf(':');
|
||||
if (index === -1) {
|
||||
if (lastKey !== '') {
|
||||
parsedData[lastKey] += ' ' + line.trim();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let key = line.slice(0, index).trim();
|
||||
const value = line.slice(index + 1).trim();
|
||||
if (value.length === 0) continue;
|
||||
key = key.replace(/\W+/g, '_');
|
||||
lastKey = key;
|
||||
|
||||
parsedData[key] = value;
|
||||
}
|
||||
|
||||
return parsedData;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user