1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.linecorp.centraldogma.internal;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static java.util.Objects.requireNonNull;
21
22 import java.io.BufferedReader;
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.StringReader;
26 import java.nio.file.Files;
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31
32 import com.jayway.jsonpath.JsonPath;
33
34
35
36
37
38 public final class Util {
39
40 private static final Pattern FILE_NAME_PATTERN = Pattern.compile(
41 "^(?:[-_0-9a-zA-Z](?:[-_.0-9a-zA-Z]*[-_0-9a-zA-Z])?)+$");
42 private static final Pattern FILE_PATH_PATTERN = Pattern.compile(
43 "^(?:/[-_0-9a-zA-Z](?:[-_.0-9a-zA-Z]*[-_0-9a-zA-Z])?)+$");
44 private static final Pattern JSON_FILE_PATH_PATTERN = Pattern.compile(
45 "^(?:/[-_0-9a-zA-Z](?:[-_.0-9a-zA-Z]*[-_0-9a-zA-Z])?)+\\.(?i)json$");
46 private static final Pattern DIR_PATH_PATTERN = Pattern.compile(
47 "^(?:/[-_0-9a-zA-Z](?:[-_.0-9a-zA-Z]*[-_0-9a-zA-Z])?)*/?$");
48 private static final Pattern PATH_PATTERN_PATTERN = Pattern.compile("^[- /*_.,0-9a-zA-Z]+$");
49 private static final Pattern EMAIL_PATTERN = Pattern.compile(
50 "^[_A-Za-z0-9-+]+(?:\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(?:\\.[A-Za-z0-9]+)*(?:\\.[A-Za-z]{2,})$");
51 private static final Pattern GENERAL_EMAIL_PATTERN = Pattern.compile(
52 "^[_A-Za-z0-9-+]+(?:\\.[_A-Za-z0-9-]+)*@(.+)$");
53
54
55
56
57
58
59
60 public static final Pattern USER_INPUT_PROJECT_AND_REPO_NAME_PATTERN =
61 Pattern.compile("^(?!.*\\.git$)[0-9A-Za-z](?:[-+_0-9A-Za-z.]*[0-9A-Za-z])?$");
62
63 public static final String INTERNAL_PROJECT_PREFIX = "@";
64
65
66
67
68
69 public static final Pattern PROJECT_AND_REPO_NAME_PATTERN =
70 Pattern.compile("^(?!.*\\.git$)(" + INTERNAL_PROJECT_PREFIX +
71 "|[0-9A-Za-z])(?:[-+_0-9A-Za-z.]*[0-9A-Za-z])?$");
72
73 public static String validateFileName(String name, String paramName) {
74 requireNonNull(name, paramName);
75 checkArgument(isValidFileName(name),
76 "%s: %s (expected: %s)", paramName, name, FILE_NAME_PATTERN);
77 return name;
78 }
79
80 public static boolean isValidFileName(String name) {
81 requireNonNull(name, "name");
82 return !name.isEmpty() && FILE_NAME_PATTERN.matcher(name).matches();
83 }
84
85 public static String validateFilePath(String path, String paramName) {
86 requireNonNull(path, paramName);
87 checkArgument(isValidFilePath(path),
88 "%s: %s (expected: %s)", paramName, path, FILE_PATH_PATTERN);
89 return path;
90 }
91
92 public static boolean isValidFilePath(String path) {
93 requireNonNull(path, "path");
94 return !path.isEmpty() && path.charAt(0) == '/' &&
95 FILE_PATH_PATTERN.matcher(path).matches();
96 }
97
98 public static String validateJsonFilePath(String path, String paramName) {
99 requireNonNull(path, paramName);
100 checkArgument(isValidJsonFilePath(path),
101 "%s: %s (expected: %s)", paramName, path, JSON_FILE_PATH_PATTERN);
102 return path;
103 }
104
105 public static boolean isValidJsonFilePath(String path) {
106 requireNonNull(path, "path");
107 return !path.isEmpty() && path.charAt(0) == '/' &&
108 JSON_FILE_PATH_PATTERN.matcher(path).matches();
109 }
110
111 public static String validateJsonPath(String jsonPath, String paramName) {
112 requireNonNull(jsonPath, paramName);
113 checkArgument(isValidJsonPath(jsonPath),
114 "%s: %s (expected: a valid JSON path)", paramName, jsonPath);
115 return jsonPath;
116 }
117
118 public static boolean isValidJsonPath(String jsonPath) {
119 try {
120 JsonPath.compile(jsonPath);
121 return true;
122 } catch (Exception e) {
123 return false;
124 }
125 }
126
127 public static String validateDirPath(String path, String paramName) {
128 requireNonNull(path, paramName);
129 checkArgument(isValidDirPath(path),
130 "%s: %s (expected: %s)", paramName, path, DIR_PATH_PATTERN);
131 return path;
132 }
133
134 public static boolean isValidDirPath(String path) {
135 return isValidDirPath(path, false);
136 }
137
138 public static boolean isValidDirPath(String path, boolean mustEndWithSlash) {
139 requireNonNull(path);
140 if (mustEndWithSlash && !path.endsWith("/")) {
141 return false;
142 }
143 return !path.isEmpty() && path.charAt(0) == '/' &&
144 DIR_PATH_PATTERN.matcher(path).matches();
145 }
146
147 public static String validatePathPattern(String pathPattern, String paramName) {
148 requireNonNull(pathPattern, paramName);
149 checkArgument(isValidPathPattern(pathPattern),
150 "%s: %s (expected: %s)", paramName, pathPattern, PATH_PATTERN_PATTERN);
151 return pathPattern;
152 }
153
154 public static boolean isValidPathPattern(String pathPattern) {
155 requireNonNull(pathPattern, "pathPattern");
156 return PATH_PATTERN_PATTERN.matcher(pathPattern).matches();
157 }
158
159 public static String validateProjectName(String projectName, String paramName, boolean allowInternal) {
160 requireNonNull(projectName, paramName);
161 if (allowInternal) {
162 checkArgument(isValidProjectName(projectName, true),
163 "%s: %s (expected: %s)", paramName, projectName,
164 PROJECT_AND_REPO_NAME_PATTERN);
165 } else {
166 checkArgument(isValidProjectName(projectName, false),
167 "%s: %s (expected: %s)", paramName, projectName,
168 USER_INPUT_PROJECT_AND_REPO_NAME_PATTERN);
169 }
170 return projectName;
171 }
172
173 public static boolean isValidProjectName(String projectName, boolean allowInternal) {
174 requireNonNull(projectName, "projectName");
175 if (allowInternal) {
176 return PROJECT_AND_REPO_NAME_PATTERN.matcher(projectName).matches();
177 } else {
178 return USER_INPUT_PROJECT_AND_REPO_NAME_PATTERN.matcher(projectName).matches();
179 }
180 }
181
182 public static boolean isValidProjectName(String projectName) {
183 requireNonNull(projectName, "projectName");
184 return USER_INPUT_PROJECT_AND_REPO_NAME_PATTERN.matcher(projectName).matches();
185 }
186
187 public static String validateRepositoryName(String repoName, String paramName) {
188 requireNonNull(repoName, paramName);
189 checkArgument(isValidRepositoryName(repoName),
190 "%s: %s (expected: %s)", paramName, repoName, USER_INPUT_PROJECT_AND_REPO_NAME_PATTERN);
191 return repoName;
192 }
193
194 public static boolean isValidRepositoryName(String repoName) {
195 requireNonNull(repoName, "repoName");
196 return USER_INPUT_PROJECT_AND_REPO_NAME_PATTERN.matcher(repoName).matches();
197 }
198
199 public static String validateEmailAddress(String emailAddr, String paramName) {
200 requireNonNull(emailAddr, paramName);
201 checkArgument(isValidEmailAddress(emailAddr),
202 "%s: %s (expected: a valid e-mail address)", paramName, emailAddr);
203 return emailAddr;
204 }
205
206 public static boolean isValidEmailAddress(String emailAddr) {
207 requireNonNull(emailAddr, "emailAddr");
208 if (EMAIL_PATTERN.matcher(emailAddr).matches()) {
209 return true;
210 }
211
212 final Matcher m = GENERAL_EMAIL_PATTERN.matcher(emailAddr);
213 if (m.matches()) {
214 final String domainPart = m.group(1);
215 return isValidIpV4Address(domainPart) ||
216 isValidIpV6Address(domainPart);
217 }
218 return false;
219 }
220
221 public static String toEmailAddress(String emailAddr, String paramName) {
222 requireNonNull(emailAddr, paramName);
223 if (isValidEmailAddress(emailAddr)) {
224 return emailAddr;
225 }
226 return emailAddr + "@localhost.localdomain";
227 }
228
229 public static String emailToUsername(String emailAddr, String paramName) {
230 validateEmailAddress(emailAddr, paramName);
231 return emailAddr.substring(0, emailAddr.indexOf('@'));
232 }
233
234 public static List<String> stringToLines(String str) {
235 final BufferedReader reader = new BufferedReader(new StringReader(str));
236 final List<String> lines = new ArrayList<>(128);
237 try {
238 String line;
239 while ((line = reader.readLine()) != null) {
240 lines.add(line);
241 }
242 } catch (IOException ignored) {
243
244 }
245 return lines;
246 }
247
248
249
250
251 public static String simpleTypeName(Object obj) {
252 if (obj == null) {
253 return "null";
254 }
255
256 return simpleTypeName(obj.getClass(), false);
257 }
258
259
260
261
262 public static String simpleTypeName(Class<?> clazz) {
263 return simpleTypeName(clazz, false);
264 }
265
266
267
268
269 public static String simpleTypeName(Class<?> clazz, boolean decapitalize) {
270 if (clazz == null) {
271 return "null";
272 }
273
274 String className = clazz.getName();
275 final int lastDotIdx = className.lastIndexOf('.');
276 if (lastDotIdx >= 0) {
277 className = className.substring(lastDotIdx + 1);
278 }
279
280 if (!decapitalize) {
281 return className;
282 }
283
284 final StringBuilder buf = new StringBuilder(className.length());
285 boolean lowercase = true;
286 for (int i = 0; i < className.length(); i++) {
287 final char c1 = className.charAt(i);
288 final char c2;
289 if (lowercase) {
290 c2 = Character.toLowerCase(c1);
291 if (c1 == c2) {
292 lowercase = false;
293 }
294 } else {
295 c2 = c1;
296 }
297 buf.append(c2);
298 }
299
300 return buf.toString();
301 }
302
303
304
305
306 @SuppressWarnings("unchecked")
307 public static <T> T unsafeCast(Object o) {
308 return (T) o;
309 }
310
311
312
313
314 public static <T> Iterable<T> requireNonNullElements(Iterable<T> values, String name) {
315 requireNonNull(values, name);
316
317 int i = 0;
318 for (T v : values) {
319 if (v == null) {
320 throw new NullPointerException(name + '[' + i + ']');
321 }
322 i++;
323 }
324
325 return values;
326 }
327
328 private static boolean isValidIpV4Address(String ip) {
329 return isValidIpV4Address(ip, 0, ip.length());
330 }
331
332 @SuppressWarnings("DuplicateBooleanBranch")
333 private static boolean isValidIpV4Address(String ip, int from, int toExcluded) {
334 final int len = toExcluded - from;
335 int i;
336 return len <= 15 && len >= 7 &&
337 (i = ip.indexOf('.', from + 1)) > 0 && isValidIpV4Word(ip, from, i) &&
338 (i = ip.indexOf('.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) &&
339 (i = ip.indexOf('.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) &&
340 isValidIpV4Word(ip, i + 1, toExcluded);
341 }
342
343 private static boolean isValidIpV4Word(CharSequence word, int from, int toExclusive) {
344 final int len = toExclusive - from;
345 final char c0;
346 final char c1;
347 final char c2;
348 if (len < 1 || len > 3 || (c0 = word.charAt(from)) < '0') {
349 return false;
350 }
351 if (len == 3) {
352 return (c1 = word.charAt(from + 1)) >= '0' &&
353 (c2 = word.charAt(from + 2)) >= '0' &&
354 (c0 <= '1' && c1 <= '9' && c2 <= '9' ||
355 c0 == '2' && c1 <= '5' && (c2 <= '5' || c1 < '5' && c2 <= '9'));
356 }
357 return c0 <= '9' && (len == 1 || isValidNumericChar(word.charAt(from + 1)));
358 }
359
360 private static boolean isValidIpV6Address(String ip) {
361 int end = ip.length();
362 if (end < 2) {
363 return false;
364 }
365
366
367 int start;
368 char c = ip.charAt(0);
369 if (c == '[') {
370 end--;
371 if (ip.charAt(end) != ']') {
372
373 return false;
374 }
375 start = 1;
376 c = ip.charAt(1);
377 } else {
378 start = 0;
379 }
380
381 int colons;
382 int compressBegin;
383 if (c == ':') {
384
385 if (ip.charAt(start + 1) != ':') {
386 return false;
387 }
388 colons = 2;
389 compressBegin = start;
390 start += 2;
391 } else {
392 colons = 0;
393 compressBegin = -1;
394 }
395
396 int wordLen = 0;
397 loop:
398 for (int i = start; i < end; i++) {
399 c = ip.charAt(i);
400 if (isValidHexChar(c)) {
401 if (wordLen < 4) {
402 wordLen++;
403 continue;
404 }
405 return false;
406 }
407
408 switch (c) {
409 case ':':
410 if (colons > 7) {
411 return false;
412 }
413 if (ip.charAt(i - 1) == ':') {
414 if (compressBegin >= 0) {
415 return false;
416 }
417 compressBegin = i - 1;
418 } else {
419 wordLen = 0;
420 }
421 colons++;
422 break;
423 case '.':
424
425
426
427 if (compressBegin < 0 && colons != 6 ||
428
429
430 colons == 7 && compressBegin >= start || colons > 7) {
431 return false;
432 }
433
434
435
436
437 final int ipv4Start = i - wordLen;
438 int j = ipv4Start - 2;
439 if (isValidIPv4MappedChar(ip.charAt(j))) {
440 if (!isValidIPv4MappedChar(ip.charAt(j - 1)) ||
441 !isValidIPv4MappedChar(ip.charAt(j - 2)) ||
442 !isValidIPv4MappedChar(ip.charAt(j - 3))) {
443 return false;
444 }
445 j -= 5;
446 }
447
448 for (; j >= start; --j) {
449 final char tmpChar = ip.charAt(j);
450 if (tmpChar != '0' && tmpChar != ':') {
451 return false;
452 }
453 }
454
455
456 int ipv4End = ip.indexOf('%', ipv4Start + 7);
457 if (ipv4End < 0) {
458 ipv4End = end;
459 }
460 return isValidIpV4Address(ip, ipv4Start, ipv4End);
461 case '%':
462
463 end = i;
464 break loop;
465 default:
466 return false;
467 }
468 }
469
470
471 if (compressBegin < 0) {
472 return colons == 7 && wordLen > 0;
473 }
474
475 return compressBegin + 2 == end ||
476
477 wordLen > 0 && (colons < 8 || compressBegin <= start);
478 }
479
480 private static boolean isValidNumericChar(char c) {
481 return c >= '0' && c <= '9';
482 }
483
484 private static boolean isValidIPv4MappedChar(char c) {
485 return c == 'f' || c == 'F';
486 }
487
488 private static boolean isValidHexChar(char c) {
489 return c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f';
490 }
491
492
493
494
495 public static void deleteFileTree(File directory) throws IOException {
496 if (directory.exists()) {
497 Files.walkFileTree(directory.toPath(), DeletingFileVisitor.INSTANCE);
498 }
499 }
500
501 private Util() {}
502 }