Skip to content

Commit 841b45c

Browse files
committed
CVE identifier length check
1 parent d70eb7d commit 841b45c

1 file changed

Lines changed: 75 additions & 21 deletions

File tree

src/components/cveRecordSearchModule.vue

Lines changed: 75 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,17 @@
1919
<div class="field has-addons mt-1">
2020
<p class="control">
2121
<span v-if="websiteEnv !== 'prd'" class="select cve-search-selector">
22-
<select v-model="searchType">
22+
<select v-model="searchType" @input="searchTypeSwap">
2323
<option>Search CVE List</option>
2424
<option>Find a Test CVE Record/ID (Legacy)</option>
2525
</select>
2626
</span>
2727
</p>
2828
<p class="control is-expanded">
29-
<input v-if="searchTypeBoolean" v-model.trim="queryString" @keyup="onKeyUp"
30-
@keyup.enter="validate" type="text" class="input cve-id-input is-expanded"
29+
<input v-if="searchTypeBoolean" v-model.trim="queryString"
30+
@input="onInputChange" @keyup.enter="validate" type="text" class="input cve-id-input is-expanded"
3131
placeholder="Enter keywords (e.g.: CVE ID, sql injection, etc.)" />
32-
<input v-else v-model.trim="cveId" @keyup="onKeyUp" @keyup.enter="validate"
32+
<input v-else v-model.trim="cveId" @input="onInputChange" @keyup.enter="validate"
3333
type="text" class="input cve-id-input" placeholder="Enter CVE ID (CVE-YYYY-NNNN)" />
3434
</p>
3535
<p class="control">
@@ -65,7 +65,11 @@ import { useGenericGlobalsStore } from '@/stores/genericGlobals';
6565
const cveIdRegex = /^CVE\p{Pd}(?<year>\d{4})\p{Pd}(?<id>\d{4,})$/iu;
6666
const wordRegex = /^[a-z0-9 ]+$/i;
6767
68-
const cveStartYear = 1999;
68+
// This is the current maximum supported length of the "suffix" portion of
69+
// the CVE ID. The schema defines a suffix length up to 19, but code in
70+
// other modules only supports what's defined here.
71+
72+
const maxCveIdSuffix = 7;
6973
7074
let cveListSearchStore = useCveListSearchStore();
7175
const route = useRoute();
@@ -80,8 +84,9 @@ let cveRecordStore = usecveRecordStore();
8084
let searchType = ref('Search CVE List');
8185
let cveId = cveRecordStore.cveId;
8286
83-
// this seems redundant, but it fixes an edge case.
84-
// if a user searches for a particular field, then on the results page flips the toggle, THEN refreshes without searching, this will keep the correct helper text showing.
87+
// this seems redundant, but it fixes an edge case. if a user searches for a
88+
// particular field, then on the results page flips the toggle, THEN refreshes
89+
// without searching, this will keep the correct helper text showing.
8590
let searchTypeBoolean = computed(() => {
8691
return searchType.value == 'Search CVE List' ? true : false;
8792
});
@@ -120,7 +125,8 @@ function showHelpMessage(helpText) {
120125
}
121126
122127
function startSearch() {
123-
// We only want to flip the search item _When we actually do a search_ otherwise we should default back to what we were on a page refresh
128+
// We only want to flip the search item _When we actually do a search_
129+
// otherwise we should default back to what we were on a page refresh
124130
if (searchTypeBoolean.value) {
125131
cveGenericGlobalsStore.setUseSearch(true);
126132
cveGenericGlobalsStore.setCurrentServicesUrl(`https://${import.meta.env.VITE_CVE_SERVICES_BASE_URL}`)
@@ -132,8 +138,8 @@ function startSearch() {
132138
if (cveGenericGlobalsStore.useSearch) {
133139
cveListSearchStore.$reset();
134140
cveListSearchStore.query = queryString.value;
135-
if (route.name != 'SearchResults' || !route.query?.query
136-
|| (route.query.query != cveListSearchStore.query)) {
141+
if (route?.name !== 'SearchResults' || !route.query?.query
142+
|| (route.query.query !== cveListSearchStore.query)) {
137143
138144
router.push({name: 'SearchResults',
139145
query: {query: cveListSearchStore.query}});
@@ -148,32 +154,39 @@ function startSearch() {
148154
149155
function validateQueryString() {
150156
151-
const contentMessage = 'Enter words of alphanumeric characters OR a single CVD ID (CVE-YYYY-NNNN).';
157+
const contentMessage = 'Enter words of alphanumeric characters OR a single CVE ID (CVE-YYYY-NNNN).';
152158
const isSearch = searchTypeBoolean.value;
153159
const searchValue = isSearch ? queryString.value : cveId;
154160
161+
// The previous search value is only used in determining whether to enable
162+
// the search (see its usage in onInputChange() below).
163+
155164
prevSearchValue.value = searchValue;
156165
cveListSearchStore.isSearchButtonDisabled = true;
157166
showHelpMessage('');
158167
159168
if (!searchValue)
160169
return !cveListSearchStore.isSearchButtonDisabled;
161170
162-
const cveIdMatch = cveIdRegex.exec(searchValue)
171+
const cveIdMatch = cveIdRegex.exec(searchValue);
172+
let cveIdValid = false;
163173
164174
if (cveIdMatch) {
165-
const year = parseInt(cveIdMatch.groups.year);
175+
// The search string is a CVE ID. Re-assemble the components, making sure
176+
// "normal" dashes are used to separate the components.
177+
178+
const year = cveIdMatch.groups.year;
166179
const id = cveIdMatch.groups.id;
167-
const currentYear = new Date().getFullYear();
180+
const normalizedCveId = `CVE-${year}-${id}`;
168181
169-
if (cveStartYear <= year && year <= currentYear) {
170-
const normalizedCveId = `CVE-${year}-${id}`;
182+
if (id.length <= maxCveIdSuffix) {
183+
cveIdValid = true;
171184
if (isSearch)
172185
queryString.value = normalizedCveId;
173186
else
174187
cveId = normalizedCveId;
175188
} else {
176-
showHelpMessage(`CVE ID year must be within ${cveStartYear}-${currentYear}`);
189+
showHelpMessage(`Invalid CVE ID "${normalizedCveId}" - identifier out of range`);
177190
}
178191
}
179192
@@ -189,7 +202,7 @@ function validateQueryString() {
189202
// The provided search string is good.
190203
cveListSearchStore.isSearchButtonDisabled = false;
191204
}
192-
} else if (cveIdMatch) {
205+
} else if (cveIdValid) {
193206
194207
// Legacy Find by CVE ID and it's in the correct format.
195208
cveListSearchStore.isSearchButtonDisabled = false;
@@ -203,17 +216,38 @@ function validateQueryString() {
203216
return !cveListSearchStore.isSearchButtonDisabled;
204217
}
205218
206-
function onKeyUp() {
207-
if (cveListSearchStore.isSearchButtonDisabled) {
219+
function onInputChange() {
220+
// This function is called when the search string changes. The only purpose
221+
// is to clear the way for the search if there's a value (it's not empty)
222+
// and it's not the same as a "bad" value used in the previous search.
223+
// As long as this is the case, we enable the search, which will then check
224+
// the validity of the search string when initiated.
225+
208226
const isSearch = searchTypeBoolean.value;
209227
const searchValue = isSearch ? queryString.value : cveId;
210228
229+
if (cveListSearchStore.isSearchButtonDisabled) {
211230
if (prevSearchValue.value !== searchValue)
212231
cveListSearchStore.isSearchButtonDisabled = false;
232+
} else if (!searchValue.length) {
233+
// This handles the case when the user clears the input field following
234+
// a successful search, possibly by using the delete button or selecting
235+
// the input and cutting via the keyboard. It makes the state consistent
236+
// with when the page starts with nothing in the input field.
237+
238+
cveListSearchStore.isSearchButtonDisabled = true;
213239
}
214240
}
215241
216242
function validate() {
243+
// This function is called when initiating a search. It's invoked as a result
244+
// of one of these user actions:
245+
//
246+
// - pressing enter in the input field.
247+
// - clicking the Search/Find button.
248+
// - entering the search URL.
249+
//
250+
// Provided the search string is valid, the search is started.
217251
218252
if (validateQueryString()) {
219253
try {
@@ -222,11 +256,31 @@ function validate() {
222256
} finally {
223257
cveListSearchStore.isSearchButtonDisabled = false;
224258
}
225-
} else if (route.name != 'home' || route?.query) {
259+
} else if (route?.name != 'home' || route?.query) {
260+
// The query string is not valid and the route indicates that the URL
261+
// is most likely associated with a previous valid search. The user
262+
// has initiated another search that is unrelated to the URL being
263+
// displayed (e.g., cve.org/CVERecord/SearchResults?query=CVE-2020-0001).
264+
// Rather than display a URL that is not associated with the
265+
// current search, reset it to the home route.
266+
226267
router.push({name: 'home', query: {}});
227268
}
228269
}
229270
271+
function searchTypeSwap() {
272+
273+
// This function is used only when there's an option menu present that
274+
// allows for using the "legacy" find, which does NOT apply to the
275+
// production website. When a switch is made in the type of search, this
276+
// function resets the previous search value and removes the URL that's
277+
// applicable to the other search type.
278+
279+
prevSearchValue.value = '';
280+
281+
router.push({name: 'home', query: {}});
282+
}
283+
230284
const websiteEnv = computed(() => {
231285
return import.meta.env.VITE_WEBSITE_ENVIRONMENT;
232286
});

0 commit comments

Comments
 (0)