1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
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;
};
|