001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.util; 020 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.Map; 024 025import org.eclipse.aether.artifact.Artifact; 026import org.eclipse.aether.metadata.Metadata; 027 028import static java.util.Objects.requireNonNull; 029 030/** 031 * A reusable utility class for file paths. 032 * 033 * @since 2.0.13 034 */ 035public final class PathUtils { 036 private PathUtils() { 037 // hide constructor 038 } 039 040 private static final Map<String, String> ILLEGAL_PATH_SEGMENT_REPLACEMENTS; 041 042 static { 043 HashMap<String, String> illegalPathSegmentReplacements = new HashMap<>(); 044 illegalPathSegmentReplacements.put("\\", "-BACKSLASH-"); 045 illegalPathSegmentReplacements.put("/", "-SLASH-"); 046 illegalPathSegmentReplacements.put(":", "-COLON-"); 047 illegalPathSegmentReplacements.put("\"", "-QUOTE-"); 048 illegalPathSegmentReplacements.put("<", "-LT-"); 049 illegalPathSegmentReplacements.put(">", "-GT-"); 050 illegalPathSegmentReplacements.put("|", "-PIPE-"); 051 illegalPathSegmentReplacements.put("?", "-QMARK-"); 052 illegalPathSegmentReplacements.put("*", "-ASTERISK-"); 053 ILLEGAL_PATH_SEGMENT_REPLACEMENTS = Collections.unmodifiableMap(illegalPathSegmentReplacements); 054 } 055 056 /** 057 * Method that makes sure that passed in string is valid "path segment" string. It achieves it by potentially 058 * changing it, replacing illegal characters in it with legal ones. 059 * <p> 060 * Note: this method considers empty string as "valid path segment", it is caller duty to ensure empty string 061 * is not used as path segment alone. 062 * <p> 063 * This method is simplistic on purpose, and if frequently used, best if results are cached (per session) 064 */ 065 public static String stringToPathSegment(String string) { 066 requireNonNull(string); 067 StringBuilder result = new StringBuilder(string); 068 for (Map.Entry<String, String> entry : ILLEGAL_PATH_SEGMENT_REPLACEMENTS.entrySet()) { 069 String illegal = entry.getKey(); 070 int pos = result.indexOf(illegal); 071 while (pos >= 0) { 072 result.replace(pos, pos + illegal.length(), entry.getValue()); 073 pos = result.indexOf(illegal); 074 } 075 } 076 return result.toString(); 077 } 078 079 /** 080 * Validates that a coordinate component does not contain path traversal sequences 081 * or path separator characters that could cause the composed path to escape 082 * the local repository directory. 083 * 084 * @since 2.0.21 085 */ 086 public static void validatePathComponent(String value, String label) { 087 if (value != null && !value.isEmpty()) { 088 // Important: "equals .." and not "contains ..", as if escape attempted, it will contain path separators 089 // OTOH: version "1.." is valid version string! 090 if (value.equals("..") || value.contains("/") || value.contains("\\")) { 091 throw new IllegalArgumentException( 092 "Invalid " + label + ": must not contain '..', '/' or '\\': " + value); 093 } 094 } 095 } 096 097 /** 098 * Validates all coordinate components of an {@link Artifact}. 099 * 100 * @see #validatePathComponent(String, String) 101 * @since 2.0.21 102 */ 103 public static void validateArtifactComponents(Artifact artifact) { 104 validatePathComponent(artifact.getGroupId(), "groupId"); 105 validatePathComponent(artifact.getArtifactId(), "artifactId"); 106 validatePathComponent(artifact.getVersion(), "version"); 107 validatePathComponent(artifact.getBaseVersion(), "baseVersion"); 108 validatePathComponent(artifact.getClassifier(), "classifier"); 109 validatePathComponent(artifact.getExtension(), "extension"); 110 } 111 112 /** 113 * Validates all coordinate components of a {@link Metadata}. 114 * 115 * @see #validatePathComponent(String, String) 116 * @since 2.0.21 117 */ 118 public static void validateMetadataComponents(Metadata metadata) { 119 validatePathComponent(metadata.getGroupId(), "groupId"); 120 validatePathComponent(metadata.getArtifactId(), "artifactId"); 121 validatePathComponent(metadata.getVersion(), "version"); 122 // note: type may contain string like ".meta/prefixes.txt"! 123 } 124}