View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.util;
20  
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.Map;
24  
25  import org.eclipse.aether.artifact.Artifact;
26  import org.eclipse.aether.metadata.Metadata;
27  
28  import static java.util.Objects.requireNonNull;
29  
30  /**
31   * A reusable utility class for file paths.
32   *
33   * @since 2.0.13
34   */
35  public final class PathUtils {
36      private PathUtils() {
37          // hide constructor
38      }
39  
40      private static final Map<String, String> ILLEGAL_PATH_SEGMENT_REPLACEMENTS;
41  
42      static {
43          HashMap<String, String> illegalPathSegmentReplacements = new HashMap<>();
44          illegalPathSegmentReplacements.put("\\", "-BACKSLASH-");
45          illegalPathSegmentReplacements.put("/", "-SLASH-");
46          illegalPathSegmentReplacements.put(":", "-COLON-");
47          illegalPathSegmentReplacements.put("\"", "-QUOTE-");
48          illegalPathSegmentReplacements.put("<", "-LT-");
49          illegalPathSegmentReplacements.put(">", "-GT-");
50          illegalPathSegmentReplacements.put("|", "-PIPE-");
51          illegalPathSegmentReplacements.put("?", "-QMARK-");
52          illegalPathSegmentReplacements.put("*", "-ASTERISK-");
53          ILLEGAL_PATH_SEGMENT_REPLACEMENTS = Collections.unmodifiableMap(illegalPathSegmentReplacements);
54      }
55  
56      /**
57       * Method that makes sure that passed in string is valid "path segment" string. It achieves it by potentially
58       * changing it, replacing illegal characters in it with legal ones.
59       * <p>
60       * Note: this method considers empty string as "valid path segment", it is caller duty to ensure empty string
61       * is not used as path segment alone.
62       * <p>
63       * This method is simplistic on purpose, and if frequently used, best if results are cached (per session)
64       */
65      public static String stringToPathSegment(String string) {
66          requireNonNull(string);
67          StringBuilder result = new StringBuilder(string);
68          for (Map.Entry<String, String> entry : ILLEGAL_PATH_SEGMENT_REPLACEMENTS.entrySet()) {
69              String illegal = entry.getKey();
70              int pos = result.indexOf(illegal);
71              while (pos >= 0) {
72                  result.replace(pos, pos + illegal.length(), entry.getValue());
73                  pos = result.indexOf(illegal);
74              }
75          }
76          // Strings consisting solely of dots contain no illegal character, yet "." and ".." carry path
77          // meaning when used as a path segment. Map them to explicit tokens, mirroring the character
78          // replacements above.
79          String segment = result.toString();
80          if (segment.equals(".")) {
81              return "-DOT-";
82          } else if (segment.equals("..")) {
83              return "-DOTDOT-";
84          }
85          return segment;
86      }
87  
88      /**
89       * Validates that a coordinate component does not contain path traversal sequences
90       * or path separator characters that could cause the composed path to escape
91       * the local repository directory.
92       *
93       * @since 2.0.21
94       */
95      public static void validatePathComponent(String value, String label) {
96          if (value != null && !value.isEmpty()) {
97              // Important: "equals .." and not "contains ..", as if escape attempted, it will contain path separators
98              // OTOH: version "1.." is valid version string!
99              // Colon is not a valid character in a coordinate component.
100             if (value.equals("..") || value.contains("/") || value.contains("\\") || value.contains(":")) {
101                 throw new IllegalArgumentException(
102                         "Invalid " + label + ": must not contain '..', '/', '\\' or ':': " + value);
103             }
104         }
105     }
106 
107     /**
108      * Validates a coordinate component that is expanded into multiple path segments by replacing each dot with a
109      * path separator, like the group ID is. Beside the checks done by
110      * {@link #validatePathComponent(String, String)}, it rejects values containing empty dot-separated segments
111      * (leading, trailing or consecutive dots), as the expansion of such values does not compose a valid
112      * relative path.
113      *
114      * @since 2.0.23
115      */
116     public static void validateDotSeparatedPathComponent(String value, String label) {
117         validatePathComponent(value, label);
118         if (value != null && !value.isEmpty()) {
119             for (String segment : value.split("\\.", -1)) {
120                 if (segment.isEmpty()) {
121                     throw new IllegalArgumentException("Invalid " + label
122                             + ": must not contain empty segments (leading, trailing or consecutive dots): " + value);
123                 }
124             }
125         }
126     }
127 
128     /**
129      * Validates all coordinate components of an {@link Artifact}.
130      *
131      * @see #validatePathComponent(String, String)
132      * @see #validateDotSeparatedPathComponent(String, String)
133      * @since 2.0.21
134      */
135     public static void validateArtifactComponents(Artifact artifact) {
136         validateDotSeparatedPathComponent(artifact.getGroupId(), "groupId");
137         validatePathComponent(artifact.getArtifactId(), "artifactId");
138         validatePathComponent(artifact.getVersion(), "version");
139         validatePathComponent(artifact.getBaseVersion(), "baseVersion");
140         validatePathComponent(artifact.getClassifier(), "classifier");
141         validatePathComponent(artifact.getExtension(), "extension");
142     }
143 
144     /**
145      * Validates all coordinate components of a {@link Metadata}.
146      *
147      * @see #validatePathComponent(String, String)
148      * @see #validateDotSeparatedPathComponent(String, String)
149      * @since 2.0.21
150      */
151     public static void validateMetadataComponents(Metadata metadata) {
152         validateDotSeparatedPathComponent(metadata.getGroupId(), "groupId");
153         validatePathComponent(metadata.getArtifactId(), "artifactId");
154         validatePathComponent(metadata.getVersion(), "version");
155         // note: type may contain string like ".meta/prefixes.txt"!
156     }
157 }