-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #25 from AlecBlance/feat/referrer
- Loading branch information
Showing
10 changed files
with
181 additions
and
169 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
"s3bucketlist": minor | ||
--- | ||
|
||
- Add origin of s3 bucket request | ||
- Fixes on duplicate bucket checks | ||
- Style adjustment on scroll display |
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,178 +1,47 @@ | ||
import * as cheerio from "cheerio"; | ||
import { IBucketType } from "./types"; | ||
import { getStorageKeyValue } from "@/utils/helper.utils"; | ||
import { recordBuckets } from "./lib/recorder"; | ||
|
||
const requests = chrome.webRequest.onHeadersReceived; | ||
const storage = chrome.storage.local; | ||
|
||
/** | ||
* Gets the buckets stored in the session storage | ||
*/ | ||
const getBuckets = async () => { | ||
const { buckets } = await storage.get("buckets"); | ||
return buckets as IBucketType; | ||
// To make async function work, and also the ability to remove listener | ||
const dummyFunc = (detail: chrome.webRequest.WebResponseHeadersDetails) => { | ||
recordBuckets(detail); | ||
}; | ||
|
||
/** | ||
* Changes the badge text to the number of buckets | ||
* | ||
* @param buckets list of buckets from the storage | ||
*/ | ||
const addNumber = async (buckets: IBucketType) => { | ||
const { lastSeen } = await storage.get("lastSeen"); | ||
const bucketsListed = | ||
buckets.good.filter((bucket) => bucket.date > lastSeen.good).length + 1; | ||
chrome.action.setBadgeText({ | ||
text: bucketsListed.toString(), | ||
}); | ||
chrome.action.setBadgeBackgroundColor({ | ||
color: "green", | ||
}); | ||
}; | ||
|
||
/** | ||
* Gets the permissions from the offscreen page | ||
* | ||
* @param $ the cheerio object | ||
*/ | ||
const getPerms = ($: cheerio.CheerioAPI, hostname: string) => { | ||
const permissions = {} as Record<string, string[]>; | ||
const hasUri = $("URI"); | ||
const hasCode = $("Code"); | ||
const hasListBucket = $("ListBucketResult"); | ||
let type = ""; | ||
const date = new Date().getTime(); | ||
try { | ||
if (!hasUri.length && !hasCode.length && hasListBucket) | ||
throw new Error("No permissions"); | ||
if (hasUri.length) { | ||
hasUri.toArray().map((elem) => { | ||
const title = $(elem).text().split("/").pop()!; | ||
const perm = $(elem).parent().next().text(); | ||
permissions[title] = [...(permissions[title] || []), perm]; | ||
}); | ||
type = "good"; | ||
} | ||
if (hasListBucket.length) { | ||
permissions["ListBucket"] = ["True"]; | ||
type = "good"; | ||
} else if (hasCode.length) { | ||
const elem = hasCode[0]; | ||
const title = $(elem).text(); | ||
const perm = $(elem).next().text(); | ||
type = "bad"; | ||
permissions[title] = [...(permissions[title] || []), perm]; | ||
} | ||
} catch (e) { | ||
type = "error"; | ||
console.log(e); | ||
} | ||
return { type, info: { permissions, date, hostname } }; | ||
const listener = async (toRecord: boolean) => { | ||
if (toRecord) | ||
requests.addListener( | ||
dummyFunc, | ||
{ | ||
urls: ["<all_urls>"], | ||
}, | ||
["responseHeaders"], | ||
); | ||
else requests.removeListener(dummyFunc); | ||
}; | ||
|
||
/** | ||
* Gets the bucket info from the offscreen page | ||
* | ||
* @param hostname the hostname of the bucket | ||
*/ | ||
const getBucketInfo = async (hostname: string, buckets: IBucketType) => { | ||
try { | ||
const text = await Promise.all([ | ||
fetch("http://" + hostname + "/?acl").then((res) => res.text()), | ||
fetch("http://" + hostname + "/").then((res) => res.text()), | ||
]).then(([aclText, listBucketText]) => aclText + listBucketText); | ||
const permissions = getPerms(cheerio.load(text), hostname); | ||
(async () => { | ||
const [{ buckets }, { record }] = (await getStorageKeyValue([ | ||
"buckets", | ||
"record", | ||
])) as Record<string, any>[]; | ||
if (!buckets || !Object.keys(buckets).length) { | ||
storage.set({ | ||
buckets: { | ||
...buckets, | ||
[permissions.type]: [permissions.info, ...buckets[permissions.type]], | ||
}, | ||
buckets: { good: [], bad: [], error: [] }, | ||
record: true, | ||
lastSeen: { good: 0, bad: 0, error: 0 }, | ||
}); | ||
return permissions; | ||
} catch (e) { | ||
console.log(e); | ||
return false; | ||
} | ||
}; | ||
listener(record ?? true); | ||
})(); | ||
|
||
/** | ||
* Records the buckets from the network requests | ||
* | ||
* @param response the response from the network request | ||
*/ | ||
const recordBuckets = ( | ||
response: chrome.webRequest.WebResponseHeadersDetails, | ||
): void => { | ||
const s3 = response.responseHeaders?.some( | ||
(header) => header.name == "x-amz-request-id", | ||
); | ||
if (!s3) return; | ||
getBuckets().then((buckets) => { | ||
const url = new URL(response.url); | ||
let hostname = url.hostname; | ||
const pathname = url.pathname; | ||
if (hostname === "s3.amazonaws.com") { | ||
hostname += "/" + pathname.split("/")[1]; | ||
} | ||
const isPresent = Object.values(buckets) | ||
.flat() | ||
.some((bucket) => bucket.hostname === hostname); | ||
const noFavicon = !hostname.includes("favicon.ico"); | ||
if (!isPresent && noFavicon) { | ||
getBucketInfo(hostname, buckets).then((permissions) => { | ||
if (permissions && permissions.type === "good") addNumber(buckets); | ||
}); | ||
} | ||
chrome.runtime.onMessage.addListener((toRecord: boolean) => { | ||
listener(toRecord); | ||
chrome.action.setBadgeText({ | ||
text: toRecord ? "" : "!", | ||
}); | ||
}; | ||
|
||
/** | ||
* Listens for the network requests | ||
*/ | ||
const listener = async () => { | ||
requests.addListener( | ||
recordBuckets, | ||
{ | ||
urls: ["<all_urls>"], | ||
}, | ||
["responseHeaders"], | ||
); | ||
}; | ||
|
||
/** | ||
* Handles messages from the popup | ||
* | ||
* @param toRecord whether to record the buckets | ||
* @param _sender the sender of the message | ||
* @param sendResponse the response to send back | ||
*/ | ||
const fromPopup = (toRecord: boolean) => { | ||
if (toRecord) { | ||
listener(); | ||
chrome.action.setBadgeText({ | ||
text: "", | ||
}); | ||
} else { | ||
requests.removeListener(recordBuckets); | ||
chrome.action.setBadgeText({ | ||
text: "!", | ||
}); | ||
chrome.action.setBadgeBackgroundColor({ | ||
color: "orange", | ||
}); | ||
} | ||
}; | ||
|
||
listener(); | ||
|
||
getStorageKeyValue("buckets").then((response: Record<string, any>) => { | ||
if (Object.keys(response).length && Object.keys(response.buckets).length) | ||
return; | ||
storage.set({ | ||
buckets: { good: [], bad: [], error: [] }, | ||
record: true, | ||
lastSeen: { good: 0, bad: 0, error: 0 }, | ||
chrome.action.setBadgeBackgroundColor({ | ||
color: toRecord ? "green" : "orange", | ||
}); | ||
}); | ||
|
||
chrome.runtime.onMessage.addListener(fromPopup); |
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import * as cheerio from "cheerio"; | ||
import { addNumber } from "@/utils/helper.utils"; | ||
import { IBucketType } from "@/types"; | ||
|
||
const storage = chrome.storage.local; | ||
const preRecord = new Set(); | ||
|
||
/** | ||
* Gets the permissions from the offscreen page | ||
* | ||
* @param $ the cheerio object | ||
*/ | ||
const getPerms = ($: cheerio.CheerioAPI, hostname: string) => { | ||
const permissions = {} as Record<string, string[]>; | ||
const hasUri = $("URI"); | ||
const hasCode = $("Code"); | ||
const hasListBucket = $("ListBucketResult"); | ||
let type = ""; | ||
const date = new Date().getTime(); | ||
try { | ||
if (!hasUri.length && !hasCode.length && hasListBucket) | ||
throw new Error("No permissions"); | ||
if (hasUri.length) { | ||
hasUri.toArray().map((elem) => { | ||
const title = $(elem).text().split("/").pop()!; | ||
const perm = $(elem).parent().next().text(); | ||
permissions[title] = [...(permissions[title] || []), perm]; | ||
}); | ||
type = "good"; | ||
} | ||
if (hasListBucket.length) { | ||
permissions["ListBucket"] = ["True"]; | ||
type = "good"; | ||
} else if (hasCode.length) { | ||
const elem = hasCode[0]; | ||
const title = $(elem).text(); | ||
const perm = $(elem).next().text(); | ||
type = "bad"; | ||
permissions[title] = [...(permissions[title] || []), perm]; | ||
} | ||
} catch (e) { | ||
type = "error"; | ||
console.log("Error in getPerms", e); | ||
} | ||
return { type, info: { permissions, date, hostname } }; | ||
}; | ||
|
||
/** | ||
* Gets the bucket info from the offscreen page | ||
* | ||
* @param hostname the hostname of the bucket | ||
*/ | ||
const getBucketInfo = async (hostname: string) => { | ||
try { | ||
// Combining results to have a single text to search | ||
const text = await Promise.all([ | ||
fetch("http://" + hostname + "/?acl").then((res) => res.text()), | ||
fetch("http://" + hostname + "/").then((res) => res.text()), | ||
]).then(([aclText, listBucketText]) => aclText + listBucketText); | ||
return getPerms(cheerio.load(text), hostname); | ||
} catch (e) { | ||
console.log(e); | ||
return false; | ||
} | ||
}; | ||
|
||
/** | ||
* Records the buckets from the network requests | ||
* | ||
* @param response the response from the network request | ||
*/ | ||
export const recordBuckets = async ( | ||
response: chrome.webRequest.WebResponseHeadersDetails, | ||
) => { | ||
const s3 = response.responseHeaders?.some( | ||
(header) => header.name == "x-amz-request-id", | ||
); | ||
if (!s3 || response.frameId < 0) return; | ||
const { hostname, pathname } = new URL(response.url); | ||
// There are instances that the bucket name is a path | ||
const bucketHostname = | ||
hostname === "s3.amazonaws.com" | ||
? `${hostname}/${pathname.split("/")[1]}` | ||
: hostname; | ||
const hasFavicon = bucketHostname.includes("favicon.ico"); | ||
if (hasFavicon) return; | ||
if (!preRecord.add(bucketHostname)) return; | ||
const { buckets }: { buckets: IBucketType } = await storage.get("buckets"); | ||
const isPresent = Object.values(buckets) | ||
.flat() | ||
.some((bucket) => bucket.hostname === bucketHostname); | ||
// Double requests that might be recorded that has favicon.ico | ||
if (isPresent) return; | ||
const bucketInfo = await getBucketInfo(bucketHostname); | ||
if (!bucketInfo) return; | ||
storage.set({ | ||
buckets: { | ||
...buckets, | ||
[bucketInfo.type]: [ | ||
{ ...bucketInfo.info, origin: response.initiator }, | ||
...buckets[bucketInfo.type], | ||
], | ||
}, | ||
}); | ||
if (bucketInfo.type === "good") addNumber(buckets); | ||
}; |
This file contains 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
Oops, something went wrong.