debian-mirror-gitlab/app/assets/javascripts/lib/utils/ref_validator.js

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

146 lines
4.6 KiB
JavaScript
Raw Normal View History

2023-05-27 22:25:52 +05:30
import { __, sprintf } from '~/locale';
// this service validates tagName agains git ref format.
// the git spec can be found here: https://git-scm.com/docs/git-check-ref-format#_description
// the ruby counterpart of the validator is here:
// lib/gitlab/git_ref_validator.rb
const EXPANDED_PREFIXES = ['refs/heads/', 'refs/remotes/', 'refs/tags'];
const DISALLOWED_PREFIXES = ['-', '/'];
const DISALLOWED_POSTFIXES = ['/'];
const DISALLOWED_NAMES = ['HEAD', '@'];
const DISALLOWED_SUBSTRINGS = [' ', '\\', '~', ':', '..', '^', '?', '*', '[', '@{'];
const DISALLOWED_SEQUENCE_POSTFIXES = ['.lock', '.'];
const DISALLOWED_SEQUENCE_PREFIXES = ['.'];
// eslint-disable-next-line no-control-regex
const CONTROL_CHARACTERS_REGEX = /[\x00-\x19\x7f]/;
const toReadableString = (array) => array.map((item) => `"${item}"`).join(', ');
const DisallowedPrefixesValidationMessage = sprintf(
__('Tag name should not start with %{prefixes}'),
{
prefixes: toReadableString([...EXPANDED_PREFIXES, ...DISALLOWED_PREFIXES]),
},
false,
);
const DisallowedPostfixesValidationMessage = sprintf(
__('Tag name should not end with %{postfixes}'),
{ postfixes: toReadableString(DISALLOWED_POSTFIXES) },
false,
);
const DisallowedNameValidationMessage = sprintf(
__('Tag name cannot be one of the following: %{names}'),
{ names: toReadableString(DISALLOWED_NAMES) },
false,
);
const EmptyNameValidationMessage = __('Tag name should not be empty');
const DisallowedSubstringsValidationMessage = sprintf(
__('Tag name should not contain any of the following: %{substrings}'),
{ substrings: toReadableString(DISALLOWED_SUBSTRINGS) },
false,
);
const DisallowedSequenceEmptyValidationMessage = __(
`No slash-separated tag name component can be empty`,
);
const DisallowedSequencePrefixesValidationMessage = sprintf(
__('No slash-separated component can begin with %{sequencePrefixes}'),
{ sequencePrefixes: toReadableString(DISALLOWED_SEQUENCE_PREFIXES) },
false,
);
const DisallowedSequencePostfixesValidationMessage = sprintf(
__('No slash-separated component can end with %{sequencePostfixes}'),
{ sequencePostfixes: toReadableString(DISALLOWED_SEQUENCE_POSTFIXES) },
false,
);
const ControlCharactersValidationMessage = __('Tag name should not contain any control characters');
export const validationMessages = {
EmptyNameValidationMessage,
DisallowedPrefixesValidationMessage,
DisallowedPostfixesValidationMessage,
DisallowedNameValidationMessage,
DisallowedSubstringsValidationMessage,
DisallowedSequenceEmptyValidationMessage,
DisallowedSequencePrefixesValidationMessage,
DisallowedSequencePostfixesValidationMessage,
ControlCharactersValidationMessage,
};
export class ValidationResult {
isValid = true;
validationErrors = [];
addValidationError = (errorMessage) => {
this.isValid = false;
this.validationErrors.push(errorMessage);
};
}
export const validateTag = (refName) => {
if (typeof refName !== 'string') {
throw new Error('refName argument must be a string');
}
const validationResult = new ValidationResult();
if (!refName || refName.trim() === '') {
validationResult.addValidationError(EmptyNameValidationMessage);
return validationResult;
}
if (CONTROL_CHARACTERS_REGEX.test(refName)) {
validationResult.addValidationError(ControlCharactersValidationMessage);
}
if (DISALLOWED_NAMES.some((name) => name === refName)) {
validationResult.addValidationError(DisallowedNameValidationMessage);
}
if ([...EXPANDED_PREFIXES, ...DISALLOWED_PREFIXES].some((prefix) => refName.startsWith(prefix))) {
validationResult.addValidationError(DisallowedPrefixesValidationMessage);
}
if (DISALLOWED_POSTFIXES.some((postfix) => refName.endsWith(postfix))) {
validationResult.addValidationError(DisallowedPostfixesValidationMessage);
}
if (DISALLOWED_SUBSTRINGS.some((substring) => refName.includes(substring))) {
validationResult.addValidationError(DisallowedSubstringsValidationMessage);
}
const refNameParts = refName.split('/');
if (refNameParts.some((part) => part === '')) {
validationResult.addValidationError(DisallowedSequenceEmptyValidationMessage);
}
if (
refNameParts.some((part) =>
DISALLOWED_SEQUENCE_PREFIXES.some((prefix) => part.startsWith(prefix)),
)
) {
validationResult.addValidationError(DisallowedSequencePrefixesValidationMessage);
}
if (
refNameParts.some((part) =>
DISALLOWED_SEQUENCE_POSTFIXES.some((postfix) => part.endsWith(postfix)),
)
) {
validationResult.addValidationError(DisallowedSequencePostfixesValidationMessage);
}
return validationResult;
};