Skip to content

Commit

Permalink
webui: Unquote clp queries before submitting them to the backend (fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
kirkrodrigues authored Apr 8, 2024
1 parent 55f2a17 commit c158852
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,10 @@ def start_webui(instance_id: str, clp_config: CLPConfig, mounts: CLPDockerMounts
"SqlDbSearchJobsTableName": SEARCH_JOBS_TABLE_NAME,
"SqlDbClpArchivesTableName": f"{CLP_METADATA_TABLE_PREFIX}archives",
"SqlDbClpFilesTableName": f"{CLP_METADATA_TABLE_PREFIX}files",
}
},
"public": {
"ClpStorageEngine": clp_config.package.storage_engine,
},
}
update_meteor_settings("", meteor_settings, meteor_settings_updates)

Expand Down
11 changes: 11 additions & 0 deletions components/webui/imports/api/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Enum for the CLP package's possible storage engines. These must match the values in
* clp_py_utils.clp_config.StorageEngine.
* @enum {string}
*/
const CLP_STORAGE_ENGINES = Object.freeze({
CLP: "clp",
CLP_S: "clp-s"
});

export {CLP_STORAGE_ENGINES};
17 changes: 16 additions & 1 deletion components/webui/imports/ui/SearchView/SearchView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {useTracker} from "meteor/react-meteor-data";
import React, {useEffect, useRef, useState} from "react";
import {ProgressBar} from "react-bootstrap";

import {CLP_STORAGE_ENGINES} from "../../api/constants";
import {SearchResultsMetadataCollection} from "../../api/search/collections";
import {
INVALID_JOB_ID,
Expand All @@ -15,6 +16,7 @@ import {
SEARCH_SIGNAL,
} from "../../api/search/constants";
import SearchJobCollectionsManager from "../../api/search/SearchJobCollectionsManager";
import {unquoteString} from "../../utils/misc";
import {LOCAL_STORAGE_KEYS} from "../constants";

import {
Expand Down Expand Up @@ -199,6 +201,19 @@ const SearchView = () => {
handleClearResults();
}

let processedQueryString = queryString;
if (CLP_STORAGE_ENGINES.CLP === Meteor.settings.public.ClpStorageEngine) {
try {
processedQueryString = unquoteString(queryString, '"', '\\');
if ("" === processedQueryString) {
throw new Error("Cannot be empty.");
}
} catch (e) {
setOperationErrorMsg(`Invalid query: ${e.message}`);
return;
}
}

setOperationErrorMsg("");
setLocalLastSearchSignal(SEARCH_SIGNAL.REQ_QUERYING);
setVisibleSearchResultsLimit(VISIBLE_RESULTS_LIMIT_INITIAL);
Expand All @@ -225,7 +240,7 @@ const SearchView = () => {

const args = {
ignoreCase: ignoreCase,
queryString: queryString,
queryString: processedQueryString,
timeRangeBucketSizeMillis: newTimelineConfig.bucketDuration.asMilliseconds(),
timestampBegin: timestampBeginUnixMillis.valueOf(),
timestampEnd: timestampEndUnixMillis.valueOf(),
Expand Down
65 changes: 64 additions & 1 deletion components/webui/imports/utils/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,67 @@ const computeHumanSize = (num) => {
return `${Math.round(num)} B`;
};

export {computeHumanSize, sleep};
/**
* Removes wrapping quotes from the given string, if it's quoted, and unescapes quotes from within
* the quoted string.
* NOTE: This method does *not* unescape non-quote characters, unlike most methods which handle
* unescaping quotes.
* @param str
* @param quoteChar
* @param escapeChar
* @return The processed string
* @throws Error if the quoted string has a quote within it (rather than at its ends) or it's
* missing one of it's begin/end quotes.
*/
const unquoteString = (str, quoteChar, escapeChar) => {
if (0 === str.length) {
return str;
}

// Determine the position of every character that we should remove from the processed string
const positionOfCharsToRemove = [];
const chars = Array.from(str);
let isEscaped = false;
for (let i = 0; i < chars.length; ++i) {
const c = chars[i];
if (isEscaped) {
isEscaped = false;
if (c === quoteChar) {
// We only remove the escape characters that escape quotes
positionOfCharsToRemove.push(i - 1);
}
} else if (c === escapeChar) {
isEscaped = true;
} else if (c === quoteChar) {
positionOfCharsToRemove.push(i);
}
}

if (positionOfCharsToRemove.length === 0) {
return str;
}

// Ensure any unescaped quotes are only at the beginning and end of the string
let foundBeginQuote = false;
let foundEndQuote = false;
positionOfCharsToRemove.forEach((pos) => {
const char = chars[pos];
if (quoteChar === char) {
if (0 === pos) {
foundBeginQuote = true;
} else if (chars.length - 1 === pos) {
foundEndQuote = true;
} else {
throw new Error(`Found unescaped quote character (${quoteChar}) within.`);
}
}
});
if (foundBeginQuote ^ foundEndQuote) {
throw new Error("Begin/end quote is missing.");
}

const processedChars = chars.filter((c, i) => false === positionOfCharsToRemove.includes(i));
return processedChars.join("");
}

export {computeHumanSize, sleep, unquoteString};
1 change: 1 addition & 0 deletions components/webui/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
},
"public": {
"AggregationResultsCollectionName": "aggregation-results",
"ClpStorageEngine": "clp",
"SearchResultsCollectionName": "search-results",
"SearchResultsMetadataCollectionName": "results-metadata",
"StatsCollectionName": "stats"
Expand Down
2 changes: 2 additions & 0 deletions components/webui/tests/main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import assert from "assert";

import "./misc.js";

describe("webui", function () {
it("package.json has correct name", async function () {
const { name } = await import("../package.json");
Expand Down
36 changes: 36 additions & 0 deletions components/webui/tests/misc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import assert from "assert";

import {unquoteString} from "/imports/utils/misc";

describe("misc", function () {
it("unquoteString", function () {
// Empty string
assert.strictEqual(unquoteString("", '"', '\\'), "");
// Unquoted string
assert.strictEqual(unquoteString("abc", '"', '\\'), "abc");
// Double-quoted string
assert.strictEqual(unquoteString("\"abc\"", '"', '\\'), "abc");
// Single-quoted string
assert.strictEqual(unquoteString("'abc'", "'", '\\'), "abc");
// With escaped characters
assert.strictEqual(unquoteString("a\\\\b\\\"c\\*\\?", '"', '\\'), "a\\\\b\"c\\*\\?");
// Double-quoted with escaped characters
assert.strictEqual(unquoteString("\"a\\\\b\\\"c\\*\\?\"", '"', '\\'), "a\\\\b\"c\\*\\?");

// With one of the quotes missing
assert.throws(() => {
unquoteString("\"abc", '"', '\\');
}, Error);
assert.throws(() => {
unquoteString("abc\"", '"', '\\');
}, Error);

// With an unescaped quote in the middle
assert.throws(() => {
unquoteString("ab\"c", '"', '\\');
}, Error);
assert.throws(() => {
unquoteString("\"ab\"c\"", '"', '\\');
}, Error);
});
});

0 comments on commit c158852

Please sign in to comment.