001/* 002 * Copyright 2020 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.core.util; 017 018import java.nio.charset.StandardCharsets; 019import java.util.function.Predicate; 020 021/** 022 * Utility class for encoding file names and paths.<br> 023 * These methods are used to provide safe names to be stored on disk.<br> 024 * The encode* methods %-encode all reserved chars, plus the '%' char itself.<br> 025 * The filter* methods replace reserved chars with the '_' char.<br><br> 026 * Reserved chars are <pre>\ / ? : * | " < ></pre> 027 */ 028public final class FilenameUtils 029{ 030 /** 031 * Character used to replace reserved chars in the filter* methods. 032 */ 033 public static final char REPLACEMENT_CHAR = '_'; 034 035 private static Predicate<Character> _isReserved = c -> ';' == c || '\\' == c || '/' == c || '?' == c || ':' == c || '*' == c || '|' == c || '"' == c || '<' == c || '>' == c; 036 037 private FilenameUtils() 038 { 039 // empty 040 } 041 042 /** 043 * %-encode a file path to be stored on disk. 044 * @param path the file path to encode 045 * @return the encoded file path. 046 */ 047 public static String encodePath(String path) 048 { 049 return _encodePath(path, Predicate.not(_isReserved.or(c -> '%' == c)).or(c -> '/' == c), true); 050 } 051 052 /** 053 * %-encode a file name to be stored on disk. 054 * @param name the file name to encode 055 * @return the encoded file name. 056 */ 057 public static String encodeName(String name) 058 { 059 return _encodePath(name, Predicate.not(_isReserved.or(c -> '%' == c)), true); 060 } 061 062 /** 063 * Replace reserved characters from a file path with a _. 064 * @param path the file path to transform 065 * @return the filtered file path 066 */ 067 public static String filterPath(String path) 068 { 069 return _encodePath(path, Predicate.not(_isReserved).or(c -> '/' == c), false); 070 } 071 072 /** 073 * Replace reserved characters from a file name with a _. 074 * @param name the file name to transform 075 * @return the filtered file name 076 */ 077 public static String filterName(String name) 078 { 079 return _encodePath(name, Predicate.not(_isReserved), false); 080 } 081 082 @SuppressWarnings("all") 083 // very similar to URIUtils#encode but this one allows UTF-8 chars and not only ASCII bytes 084 private static String _encodePath(String path, Predicate<Character> charactersToKeep, boolean encode) 085 { 086 StringBuilder result = new StringBuilder(); 087 088 char[] chars = path.toCharArray(); 089 090 for (char c : chars) 091 { 092 if (charactersToKeep.test(c)) 093 { 094 result.append(c); 095 } 096 else if (encode) 097 { 098 byte[] bytes = String.valueOf(c).getBytes(StandardCharsets.UTF_8); 099 for (byte b : bytes) 100 { 101 if (b < 0) 102 { 103 b += 256; 104 } 105 106 result.append('%'); 107 char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16)); 108 char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16)); 109 result.append(hex1); 110 result.append(hex2); 111 } 112 } 113 else 114 { 115 result.append(REPLACEMENT_CHAR); 116 } 117 } 118 119 return result.toString(); 120 } 121 122 /** 123 * Decodes an URI-encoded String. 124 * @param source the String to decode. 125 * @return the decoded String. 126 */ 127 @SuppressWarnings("all") 128 public static String decode(String source) 129 { 130 return URIUtils.decode(source); 131 } 132}