diff --git a/public/electron/main.js b/public/electron/main.js
index 2456ccc..1fa43cc 100644
--- a/public/electron/main.js
+++ b/public/electron/main.js
@@ -4,6 +4,7 @@ const {
ipcMain,
shell,
session,
+ dialog,
} = require("electron");
const { getDefaultChromeDataDir } = require("./constants")
const os = require("os");
@@ -201,6 +202,22 @@ app.on("ready", async () => {
ipcMain.handle("isWindows", (_event) => constants.isWindows);
+ ipcMain.handle('selectFile', async () => {
+ const result = await dialog.showOpenDialog(mainWindow, {
+ properties: ['openFile'],
+ filters: [
+ { name: 'XML Files', extensions: ['xml'] },
+ { name: 'All Files', extensions: ['*'] }
+ ]
+ });
+
+ if (!result.canceled && result.filePaths.length > 0) {
+ return result.filePaths[0];
+ } else {
+ return null;
+ }
+ });
+
await mainReady;
mainWindow.webContents.send("appStatus", "ready");
diff --git a/public/electron/preload.js b/public/electron/preload.js
index ddb2a63..0c431e8 100644
--- a/public/electron/preload.js
+++ b/public/electron/preload.js
@@ -128,5 +128,9 @@ contextBridge.exposeInMainWorld("services", {
);
return response;
},
+ selectFile: async () => {
+ const filePath = await ipcRenderer.invoke('selectFile');
+ return filePath;
+ },
getIsWindows: async () => ipcRenderer.invoke("isWindows"),
});
diff --git a/src/MainWindow/HomePage/InitScanForm.jsx b/src/MainWindow/HomePage/InitScanForm.jsx
index 1ad9a1d..b0b8c33 100644
--- a/src/MainWindow/HomePage/InitScanForm.jsx
+++ b/src/MainWindow/HomePage/InitScanForm.jsx
@@ -19,13 +19,15 @@ const InitScanForm = ({
prevUrlErrorMessage,
scanButtonIsClicked,
setScanButtonIsClicked,
- isAbortingScan
+ isAbortingScan,
}) => {
const [openPageLimitAdjuster, setOpenPageLimitAdjuster] = useState(false);
const [pageWord, setPageWord] = useState("pages");
const pageLimitAdjuster = useRef();
const scanTypeOptions = Object.keys(scanTypes);
const fileTypesOptions = Object.keys(fileTypes);
+ const [selectedFile, setSelectedFile] = useState(null); // State for selected file
+ const [fileUrl, setFileUrl] = useState(""); // State for the file URL
if (isProxy) {
delete viewportTypes.specific;
@@ -34,20 +36,31 @@ const InitScanForm = ({
const viewportOptions = viewportTypes;
const deviceOptions = isProxy ? [] : Object.keys(devices);
- const cachedPageLimit = sessionStorage.getItem('pageLimit');
- const cachedAdvancedOptions = sessionStorage.getItem('advancedOptions');
- const cachedScanUrl = sessionStorage.getItem('scanUrl');
+ const cachedPageLimit = sessionStorage.getItem("pageLimit");
+ const cachedAdvancedOptions = sessionStorage.getItem("advancedOptions");
+ const cachedScanUrl = sessionStorage.getItem("scanUrl");
const [pageLimit, setPageLimit] = useState(() => {
- return cachedPageLimit? JSON.parse(cachedPageLimit) : "100"
+ return cachedPageLimit ? JSON.parse(cachedPageLimit) : "100";
});
const [advancedOptions, setAdvancedOptions] = useState(() => {
- return cachedAdvancedOptions? JSON.parse(cachedAdvancedOptions) : getDefaultAdvancedOptions(isProxy)
+ return cachedAdvancedOptions
+ ? JSON.parse(cachedAdvancedOptions)
+ : getDefaultAdvancedOptions(isProxy);
});
- const [scanUrl, setScanUrl] = useState(() => {
- return cachedScanUrl? JSON.parse(cachedScanUrl) : "https://"
- });
+ const [scanUrl, setScanUrl] = useState("") ;
+
+ useEffect(() => {
+ const cachedScanUrl = sessionStorage.getItem("scanUrl");
+ if (cachedScanUrl) {
+ setScanUrl(JSON.parse(cachedScanUrl));
+ } else if (advancedOptions.scanType === scanTypeOptions[3]) {
+ setScanUrl("file:///");
+ } else {
+ setScanUrl("https://");
+ }
+ }, [advancedOptions.scanType]);
useEffect(() => {
const urlBarElem = document.getElementById("url-bar");
@@ -57,7 +70,7 @@ const InitScanForm = ({
}, [scanButtonIsClicked, prevUrlErrorMessage]);
useEffect(() => {
- setPageWord(pageLimit === "1" ? 'page' : 'pages')
+ setPageWord(pageLimit === "1" ? "page" : "pages");
}, [pageLimit]);
const togglePageLimitAdjuster = (e) => {
@@ -72,19 +85,20 @@ const InitScanForm = ({
};
const handleScanButtonClicked = () => {
- // If chosen device is Mobile in proxy environment, we override the default "Mobile"
- // sent to cli.js with iPhone's width 414px
- // Prevents the user-agent from triggering in cli.js
if (isProxy && advancedOptions.viewport === viewportTypes.mobile) {
advancedOptions.viewport = viewportTypes.custom;
advancedOptions.viewportWidth = 414;
}
setScanButtonIsClicked(true);
- sessionStorage.setItem('pageLimit', JSON.stringify(pageLimit));
- sessionStorage.setItem('advancedOptions', JSON.stringify(advancedOptions));
- sessionStorage.setItem('scanUrl', JSON.stringify(scanUrl));
- startScan({ scanUrl: scanUrl.trim(), pageLimit, ...advancedOptions });
+ sessionStorage.setItem("pageLimit", JSON.stringify(pageLimit));
+ sessionStorage.setItem("advancedOptions", JSON.stringify(advancedOptions));
+ sessionStorage.setItem("scanUrl", JSON.stringify(scanUrl));
+ if (advancedOptions.scanType === scanTypeOptions[3] && selectedFile) {
+ startScan({ file: selectedFile, scanUrl, pageLimit, ...advancedOptions });
+ } else {
+ startScan({ scanUrl: scanUrl.trim(), pageLimit, ...advancedOptions });
+ }
};
// styles are in HomePage.scss
@@ -100,7 +114,33 @@ const InitScanForm = ({
type="text"
value={scanUrl}
onChange={(e) => setScanUrl(e.target.value)}
+ style={{
+ display:
+ advancedOptions.scanType === scanTypeOptions[3]
+ ? "none"
+ : "block",
+ }} // Hide URL input for file scan
/>
+ {advancedOptions.scanType === scanTypeOptions[3] && (
+
+ {
+ const file = e.target.files[0];
+ if (file) {
+ setScanUrl("file://" + file.path);
+ }
+ }}
+ />
+
+
+ )}
+
{advancedOptions.scanType !== scanTypeOptions[2] &&
advancedOptions.scanType !== scanTypeOptions[3] && (
@@ -153,7 +193,11 @@ const InitScanForm = ({
onClick={handleScanButtonClicked}
disabled={scanButtonIsClicked || isAbortingScan}
>
- {scanButtonIsClicked || isAbortingScan ? : "Scan"}
+ {scanButtonIsClicked || isAbortingScan ? (
+
+ ) : (
+ "Scan"
+ )}
{prevUrlErrorMessage && (
diff --git a/src/MainWindow/HomePage/index.jsx b/src/MainWindow/HomePage/index.jsx
index de3c7ea..977d1b5 100644
--- a/src/MainWindow/HomePage/index.jsx
+++ b/src/MainWindow/HomePage/index.jsx
@@ -8,7 +8,13 @@ import labModeOn from "../../assets/lab-icon-on.svg";
import InitScanForm from "./InitScanForm";
import "./HomePage.scss";
import services from "../../services";
-import { cliErrorCodes, cliErrorTypes, errorStates, versionComparator, urlWithoutAuth } from "../../common/constants";
+import {
+ cliErrorCodes,
+ cliErrorTypes,
+ errorStates,
+ versionComparator,
+ urlWithoutAuth,
+} from "../../common/constants";
import Modal from "../../common/components/Modal";
import { BasicAuthForm, BasicAuthFormFooter } from "./BasicAuthForm";
import EditUserDetailsModal from "./EditUserDetailsModal";
@@ -19,9 +25,9 @@ import AboutModal from "./AboutModal";
const HomePage = ({ isProxy, appVersionInfo, setCompletedScanId }) => {
const navigate = useNavigate();
- const [prevUrlErrorMessage, setPrevUrlErrorMessage] = useState('');
+ const [prevUrlErrorMessage, setPrevUrlErrorMessage] = useState("");
const [{ name, email, browser, isLabMode }, setUserData] = useState({
- name: "",
+ name: "",
email: "",
browser: null,
isLabMode: false,
@@ -31,7 +37,7 @@ const HomePage = ({ isProxy, appVersionInfo, setCompletedScanId }) => {
const [showNoChromeErrorModal, setShowNoChromeErrorModal] = useState(false);
const [showWhatsNewModal, setShowWhatsNewModal] = useState(false);
const [showAboutPhModal, setShowAboutPhModal] = useState(false);
- const [url, setUrl] = useState('');
+ const [url, setUrl] = useState("");
const [scanButtonIsClicked, setScanButtonIsClicked] = useState(false);
const [isAbortingScan, setIsAbortingScan] = useState(false);
@@ -48,29 +54,28 @@ const HomePage = ({ isProxy, appVersionInfo, setCompletedScanId }) => {
}, []);
// function that determines whether version is a prerelease/stable build
- const getVersionLabel = useCallback((version) => {
- const {
- latestVer,
- latestVerForLab,
- allReleaseTags,
- allPreReleaseTags
- } = appVersionInfo;
-
- try {
- if (latestVer === version) return 'latest stable build';
- if (latestVerForLab === version) return 'latest pre-release';
-
- if (allReleaseTags.includes(version)) {
- return 'stable build';
- } else if (allPreReleaseTags.includes(version)) {
- return 'pre-release';
- }
- } catch (error) {
+ const getVersionLabel = useCallback(
+ (version) => {
+ const { latestVer, latestVerForLab, allReleaseTags, allPreReleaseTags } =
+ appVersionInfo;
+
+ try {
+ if (latestVer === version) return "latest stable build";
+ if (latestVerForLab === version) return "latest pre-release";
+
+ if (allReleaseTags.includes(version)) {
+ return "stable build";
+ } else if (allPreReleaseTags.includes(version)) {
+ return "pre-release";
+ }
+ } catch (error) {
console.log("Unable to show version label");
- }
-
- return undefined; // if cannot be determined, this should not happen
- }, [appVersionInfo]);
+ }
+
+ return undefined; // if cannot be determined, this should not happen
+ },
+ [appVersionInfo]
+ );
const isLatest = () => {
const currVer = appVersionInfo.appVersion;
@@ -98,9 +103,9 @@ const HomePage = ({ isProxy, appVersionInfo, setCompletedScanId }) => {
useEffect(() => {
if (scanButtonIsClicked && prevUrlErrorMessage) {
- setPrevUrlErrorMessage('');
+ setPrevUrlErrorMessage("");
}
- }, [scanButtonIsClicked])
+ }, [scanButtonIsClicked]);
useEffect(() => {
if (
@@ -110,11 +115,12 @@ const HomePage = ({ isProxy, appVersionInfo, setCompletedScanId }) => {
setShowBasicAuthModal(true);
}
- if (prevUrlErrorMessage !== null &&
- prevUrlErrorMessage.includes("No chrome browser")
+ if (
+ prevUrlErrorMessage !== null &&
+ prevUrlErrorMessage.includes("No chrome browser")
) {
setShowNoChromeErrorModal(true);
- }
+ }
}, [prevUrlErrorMessage]);
useEffect(() => {
@@ -125,11 +131,11 @@ const HomePage = ({ isProxy, appVersionInfo, setCompletedScanId }) => {
const handleShowModal = () => {
setShowWhatsNewModal(!!userData["firstLaunchOnUpdate"] && isLatest());
window.services.editUserData({ firstLaunchOnUpdate: false });
- }
+ };
const whatsNewModalTimeout = setTimeout(
handleShowModal,
- !!userData["firstLaunchOnUpdate"] ? 500 : 0,
- )
+ !!userData["firstLaunchOnUpdate"] ? 500 : 0
+ );
return whatsNewModalTimeout;
};
@@ -138,20 +144,20 @@ const HomePage = ({ isProxy, appVersionInfo, setCompletedScanId }) => {
}, []);
const editUserData = (info) => {
- setUserData(initData => ({ ...initData, ...info }));
+ setUserData((initData) => ({ ...initData, ...info }));
window.services.editUserData(info);
};
useEffect(() => {
const checkChromeExists = async () => {
const chromeExists = await window.services.checkChromeExistsOnMac();
-
+
if (!chromeExists) {
setShowNoChromeErrorModal(true);
}
- }
+ };
checkChromeExists();
- }, [])
+ }, []);
const isValidHttpUrl = (input) => {
const regexForUrl = new RegExp("^(http|https):/{2}.+$", "gmi");
@@ -159,9 +165,9 @@ const HomePage = ({ isProxy, appVersionInfo, setCompletedScanId }) => {
};
const isValidFilepath = (input) => {
- const regexForFilepath = new RegExp("^(file:\/\/).+$", "gmi");
+ const regexForFilepath = new RegExp("^(file://).+$", "gmi");
return regexForFilepath.test(input);
- }
+ };
const startScan = async (scanDetails) => {
scanDetails.browser = isProxy ? "edge" : browser;
@@ -174,10 +180,19 @@ const HomePage = ({ isProxy, appVersionInfo, setCompletedScanId }) => {
}
if (scanDetails.scanType === "Sitemap crawl") {
- if (!isValidHttpUrl(scanDetails.scanUrl) && !isValidFilepath(scanDetails.scanUrl)) {
+ if (
+ !isValidHttpUrl(scanDetails.scanUrl) &&
+ !isValidFilepath(scanDetails.scanUrl)
+ ) {
setScanButtonIsClicked(false);
setPrevUrlErrorMessage("Invalid sitemap.");
- return;
+ return;
+ }
+ } else if (scanDetails.scanType === "Local file") {
+ if (!isValidFilepath(scanDetails.scanUrl)) {
+ setScanButtonIsClicked(false);
+ setPrevUrlErrorMessage("Invalid FilePath. Please type in file:/// format.");
+ return;
}
} else if (!isValidHttpUrl(scanDetails.scanUrl)) {
setScanButtonIsClicked(false);
@@ -193,38 +208,44 @@ const HomePage = ({ isProxy, appVersionInfo, setCompletedScanId }) => {
window.localStorage.setItem("scanDetails", JSON.stringify(scanDetails));
- const checkUrlResponse = await services.validateUrlConnectivity(scanDetails);
+ const checkUrlResponse = await services.validateUrlConnectivity(
+ scanDetails
+ );
if (checkUrlResponse.success) {
- navigate('/scanning', { state: { url: urlWithoutAuth(scanDetails.scanUrl).toString() } });
- const scanResponse = await services.startScan(scanDetails);
+ navigate("/scanning", {
+ state: { url: urlWithoutAuth(scanDetails.scanUrl).toString() },
+ });
+ const scanResponse = await services.startScan(scanDetails);
- if (scanResponse.cancelled){
- return;
- }
+ if (scanResponse.cancelled) {
+ return;
+ }
- if (scanResponse.failedToCreateExportDir) {
- setPrevUrlErrorMessage('Unable to create download directory');
- return;
- }
+ if (scanResponse.failedToCreateExportDir) {
+ setPrevUrlErrorMessage("Unable to create download directory");
+ return;
+ }
- if (scanResponse.success) {
- setCompletedScanId(scanResponse.scanId);
- if (scanDetails.scanType === 'Custom flow') {
- navigate('/custom_flow', { state: { scanDetails }})
- } else {
- navigate("/result");
- }
- return;
- } else {
- /* When no pages were scanned (e.g. out of domain upon redirects when valid URL was entered),
+ if (scanResponse.success) {
+ setCompletedScanId(scanResponse.scanId);
+ if (scanDetails.scanType === "Custom flow") {
+ navigate("/custom_flow", { state: { scanDetails } });
+ } else {
+ navigate("/result");
+ }
+ return;
+ } else {
+ /* When no pages were scanned (e.g. out of domain upon redirects when valid URL was entered),
redirects user to error page to going to result page with empty result */
- navigate("/error", { state: { errorState: errorStates.noPagesScannedError,timeOfScan }});
- return;
- }
+ navigate("/error", {
+ state: { errorState: errorStates.noPagesScannedError, timeOfScan },
+ });
+ return;
+ }
} else {
setScanButtonIsClicked(false);
if (checkUrlResponse.failedToCreateExportDir) {
- setPrevUrlErrorMessage('Unable to create download directory');
+ setPrevUrlErrorMessage("Unable to create download directory");
return;
}
@@ -245,11 +266,14 @@ const HomePage = ({ isProxy, appVersionInfo, setCompletedScanId }) => {
errorMessageToShow = "Invalid sitemap.";
break;
case cliErrorTypes.browserError:
- navigate('/error', { state: { errorState: errorStates.browserError,timeOfScan }});
+ navigate("/error", {
+ state: { errorState: errorStates.browserError, timeOfScan },
+ });
return;
case cliErrorTypes.systemError:
default:
- errorMessageToShow = "Something went wrong. Please try again later.";
+ errorMessageToShow =
+ "Something went wrong. Please try again later.";
}
console.log(`status error: ${checkUrlResponse.statusCode}`);
setPrevUrlErrorMessage(errorMessageToShow);
@@ -341,18 +365,21 @@ const HomePage = ({ isProxy, appVersionInfo, setCompletedScanId }) => {
/>
>
)}
- {showNoChromeErrorModal &&
-
- }
- {showWhatsNewModal && getReleaseNotesOnUpdate(appVersionInfo) &&
+ {showNoChromeErrorModal && (
+
+ )}
+ {showWhatsNewModal && getReleaseNotesOnUpdate(appVersionInfo) && (
- }
- {showAboutPhModal &&
+ )}
+ {showAboutPhModal && (
{
isLabMode={isLabMode}
setIsLabMode={(bool) => editUserData({ isLabMode: bool })}
/>
- }
+ )}