Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: show match context #396 #436

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/mqueryfront/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"react": "^18.3.1",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.3.1",
"react-draggable": "^4.4.6",
"react-router-dom": "^6.26.2",
"react-select": "^5.8.1",
"replace-js-pagination": "^1.0.5",
Expand Down
26 changes: 26 additions & 0 deletions src/mqueryfront/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,29 @@
.cursor-pointer {
cursor: pointer;
}

.modal-container {
position: absolute;
offset-distance: 10px;
z-index: auto;
right: 5vw;
}

.modal-block {
position: relative;
block-size: "fit-content";
right: 5vw;
}

.modal-dialog {
margin: 0;
}

.modal-header:hover {
cursor: grab;
}

.modal-table {
overflow-y: scroll;
max-height: 50vh;
}
175 changes: 175 additions & 0 deletions src/mqueryfront/src/components/ActionShowMatchContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import React, { useState, useRef, useEffect } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faLightbulb } from "@fortawesome/free-solid-svg-icons";
import Draggable from "react-draggable";

const useClickOutside = (ref, callback) => {
const handleClick = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
// lose focus (higher z-index) only if other modal was clicked
const modals = document.querySelectorAll(".modal");
const wasClicked = (modal) => modal.contains(event.target);
if (Array.from(modals).some(wasClicked)) {
callback();
}
}
};

useEffect(() => {
document.addEventListener("click", handleClick);

return () => {
document.removeEventListener("click", handleClick);
};
});
};

function base64ToHex(str64) {
msm-cert marked this conversation as resolved.
Show resolved Hide resolved
return (
atob(str64)
.split("")
.map(function (aChar) {
return ("0" + aChar.charCodeAt(0).toString(16)).slice(-2);
})
.join(" ")
.toUpperCase() + " "
);
}

function base64ToUtf8(str64) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same goes for this function. I suggest sanitize and

sanitize(atob(str)) instead of
base64ToUtf8(str)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, I think that sanitize is too vague. If there will be another use case for such decryption in the project, it would be easier to get to know what was it's original purpose.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the other hand, base64ToUtf8 is wrong:

  • atob already returns an unicode string
  • the string has no particular encoding (the underlying interpreter must encode the string to bytes somehow, and may or may not use utf8, but for JS this is just a unicode string, without any encoding assigned)

I suggested sanitize because it's short, but it's true that it's a bit vogue . Consider:

  • safeText(text), toSafeText(text), toPrintableString(text), replaceUnprintableCharacters(text), as an alternative to sanitize
  • base64ToSafeText(text), base64ToSafeText(text), base64ToPrintableString(text), base64DecodeAndReplaceUnprintableCharacters(text) as an alternative to base64ToUtf8

(or another variation that describes "replacing unprintable characters in a string with dots" succintly)

Copy link
Contributor

@msm-code msm-code Jan 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noticed in the source that the name is already base64ToSanitizedUtf8. That's much better (though utf8 is still technically incorrect - maybe text or string?)

return (
atob(str64)
.split("")
.map(function (aChar) {
if (32 <= aChar.charCodeAt(0) && aChar.charCodeAt(0) < 127) {
return aChar;
}
return ".";
})
.join(" ") + " "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe remove the whitespace between letters? So (" "). I understand that now spacing depends on this, but this should be solved anyway.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, now there's no spaces in between bytes or chars.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This time I was probably unclear. The usual hexdump format, that it would be nice to have:

image

The important parts are:

  • (usually) spaces between bytes
  • no spaces between characters (to make plaintext easy to read)
  • 16 characters per line
  • optionally hexadecimal addresses on the left (we don't have that)

So spaces between bytes are OK (they make bytes easier to read). And the really important thing is 16 bytes per line. It may sound like I'm nitpicking, but I'm not ;)

As a POC:

image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To I think this is the required change, but please verify:

❯ git diff --unified=1
diff --git a/src/mqueryfront/src/components/ActionShowMatchContext.js b/src/mqueryfront/src/components/ActionShowMatchContext.js
index 68e8955..096d0a5 100644
--- a/src/mqueryfront/src/components/ActionShowMatchContext.js
+++ b/src/mqueryfront/src/components/ActionShowMatchContext.js
@@ -33,4 +33,4 @@ function base64ToHex(str64) {
         })
-        .join("")
-        .toUpperCase();
+        .join(" ")
+        .toUpperCase() + " ";
 }
@@ -103,3 +103,3 @@ const ActionShowMatchContext = (props) => {
                     <>
-                        <td scope="row" style={{ width: "10%" }}>
+                        <td scope="row">
                             <span className="badge rounded-pill bg-info ms-1 mt-1">
@@ -110,3 +110,3 @@ const ActionShowMatchContext = (props) => {
                             {ReactHtmlParser(
-                                cellHTML(foundSample, 20, base64ToHex)
+                                cellHTML(foundSample, 48, base64ToHex)
                             )}
@@ -115,3 +115,3 @@ const ActionShowMatchContext = (props) => {
                             {ReactHtmlParser(
-                                cellHTML(foundSample, 10, base64ToSanitizedUtf8)
+                                cellHTML(foundSample, 16, base64ToSanitizedUtf8)
                             )}
@@ -129,3 +129,2 @@ const ActionShowMatchContext = (props) => {
                         rowSpan={Object.keys(props.context[rulename]).length}
-                        style={{ width: "15%" }}
                     >
@@ -178,3 +177,3 @@ const ActionShowMatchContext = (props) => {
                         >
-                            <div className="modal-dialog modal-lg">
+                            <div className="modal-dialog modal-xl">
                                 <div className="modal-content">

);
}

const ActionShowMatchContext = (props) => {
const ref = useRef(null);
const [showModal, setShowModal] = useState(false);
const [focus, setFocus] = useState(true);
useClickOutside(ref, () => setFocus(false));
msm-cert marked this conversation as resolved.
Show resolved Hide resolved

const modalHeader = (
<div className="modal-header d-flex justify-content-between">
<h6 className="modal-title">{`Match context for ${props.filename}`}</h6>
<button
type="button"
className="close "
onClick={() => setShowModal(false)}
>
<span aria-hidden="true">&times;</span>
</button>
</div>
);

const tableRows = Object.keys(props.context).map((rulename, index) => {
const rulenameRows = Object.keys(props.context[rulename]).map(
(identifier) => {
const foundSample = props.context[rulename][identifier];
return (
<>
<td scope="row" style={{ width: "10%" }}>
<span className="badge rounded-pill bg-info ms-1 mt-1">
{identifier}
</span>
</td>
<td
scope="row"
className="text-monospace text-break"
style={{ width: "45%" }}
>
{base64ToHex(foundSample["before"])}
{<b>{base64ToHex(foundSample["matching"])}</b>}
{base64ToHex(foundSample["after"])}
</td>
<td
scope="row"
className="text-monospace text-break"
style={{ width: "30%" }}
>
{base64ToUtf8(foundSample["before"])}
{<b>{base64ToUtf8(foundSample["matching"])}</b>}
{base64ToUtf8(foundSample["after"])}
</td>
</>
);
}
);

return (
<>
<tr key={index}>
<td
scope="row fit-content"
rowSpan={Object.keys(props.context[rulename]).length}
style={{ width: "15%" }}
>
<span className="badge rounded-pill bg-primary ms-1 mt-1">
{rulename}
</span>
</td>
{rulenameRows[0]}
</tr>
{rulenameRows.slice(1).map((row) => (
<tr>{row}</tr>
))}
</>
);
});

const modalBody = (
<div className="modal-body modal-table">
{!Object.keys(props.context).length ? (
"No context available"
) : (
<table className="table table-bordered">
<tbody>{tableRows}</tbody>
</table>
)}
</div>
);

return (
<div className="d-flex flex-row">
<button
title="Show match context"
className="text-secondary"
style={{ border: 0, background: 0 }}
onClick={() => setShowModal(!showModal)}
>
<FontAwesomeIcon icon={faLightbulb} size="sm" />
</button>
{showModal && (
<Draggable handle=".modal-header">
<div
className="modal-container"
style={{ zIndex: focus ? 100 : 10 }}
ref={ref}
onClick={() => setFocus(true)}
>
<div
className="modal modal-block"
style={{ display: showModal ? "block" : "none" }}
>
<div className="modal-dialog modal-lg">
<div className="modal-content">
{modalHeader}
{modalBody}
</div>
</div>
</div>
</div>
</Draggable>
)}
</div>
);
};

export default ActionShowMatchContext;
9 changes: 8 additions & 1 deletion src/mqueryfront/src/query/QueryMatchesItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import React from "react";
import path from "path-browserify";
import ActionDownload from "../components/ActionDownload";
import ActionCopyToClipboard from "../components/ActionCopyToClipboard";
import ActionShowMatchContext from "../components/ActionShowMatchContext";

const QueryMatchesItem = (props) => {
const { match, download_url } = props;
const { matches, meta, file } = match;
const { matches, meta, file, context } = match;

const fileBasename = path.basename(file);

Expand Down Expand Up @@ -68,6 +69,12 @@ const QueryMatchesItem = (props) => {
tooltipMessage="Copy file name to clipboard"
/>
</small>
<small className="text-secondary ms-2 me-1 mt-1">
<ActionShowMatchContext
filename={fileBasename}
context={context}
/>
</small>
{matchBadges}
{metadataBadges}
</div>
Expand Down
13 changes: 13 additions & 0 deletions src/mqueryfront/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,11 @@ classnames@^2.2.5:
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==

clsx@^1.1.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==

color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
Expand Down Expand Up @@ -2119,6 +2124,14 @@ react-dom@^18.3.1:
loose-envify "^1.1.0"
scheduler "^0.23.2"

react-draggable@^4.4.6:
version "4.4.6"
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.6.tgz#63343ee945770881ca1256a5b6fa5c9f5983fe1e"
integrity sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==
dependencies:
clsx "^1.1.1"
prop-types "^15.8.1"

react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
Expand Down
Loading