1 /* 2 * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package jdk.internal.module; 26 27 import java.io.File; 28 import java.io.IOException; 29 import java.nio.file.FileSystem; 30 import java.nio.file.Files; 31 import java.nio.file.NoSuchFileException; 32 import java.nio.file.Path; 33 import java.nio.file.attribute.BasicFileAttributes; 34 35 /** 36 * A helper class to support working with resources in modules. Also provides 37 * support for translating resource names to file paths. 38 */ 39 public final class Resources { 40 private Resources() { } 41 42 /** 43 * Return true if a resource can be encapsulated. Resource with names 44 * ending in ".class" or "/" cannot be encapsulated. Resource names 45 * that map to a legal package name can be encapsulated. 46 */ 47 public static boolean canEncapsulate(String name) { 48 int len = name.length(); 49 if (len > 6 && name.endsWith(".class")) { 50 return false; 51 } else { 52 return Checks.isPackageName(toPackageName(name)); 53 } 54 } 55 56 /** 57 * Derive a <em>package name</em> for a resource. The package name 58 * returned by this method may not be a legal package name. This method 59 * returns null if the resource name ends with a "/" (a directory) 60 * or the resource name does not contain a "/". 61 */ 62 public static String toPackageName(String name) { 63 int index = name.lastIndexOf('/'); 64 if (index == -1 || index == name.length()-1) { 65 return ""; 66 } else { 67 return name.substring(0, index).replace("/", "."); 68 } 69 } 70 71 /** 72 * Returns a resource name corresponding to the relative file path 73 * between {@code dir} and {@code file}. If the file is a directory 74 * then the name will end with a "/", except the top-level directory 75 * where the empty string is returned. 76 */ 77 public static String toResourceName(Path dir, Path file) { 78 String s = dir.relativize(file) 79 .toString() 80 .replace(File.separatorChar, '/'); 81 if (!s.isEmpty() && Files.isDirectory(file)) 82 s += "/"; 83 return s; 84 } 85 86 /** 87 * Returns a file path to a resource in a file tree. If the resource 88 * name has a trailing "/" then the file path will locate a directory. 89 * Returns {@code null} if the resource does not map to a file in the 90 * tree file. 91 */ 92 public static Path toFilePath(Path dir, String name) throws IOException { 93 boolean expectDirectory = name.endsWith("/"); 94 if (expectDirectory) { 95 name = name.substring(0, name.length() - 1); // drop trailing "/" 96 } 97 Path path = toSafeFilePath(dir.getFileSystem(), name); 98 if (path != null) { 99 Path file = dir.resolve(path); 100 try { 101 BasicFileAttributes attrs; 102 attrs = Files.readAttributes(file, BasicFileAttributes.class); 103 if (attrs.isDirectory() 104 || (!attrs.isDirectory() && !expectDirectory)) 105 return file; 106 } catch (NoSuchFileException ignore) { } 107 } 108 return null; 109 } 110 111 /** 112 * Map a resource name to a "safe" file path. Returns {@code null} if 113 * the resource name cannot be converted into a "safe" file path. 114 * 115 * Resource names with empty elements, or elements that are "." or ".." 116 * are rejected, as are resource names that translates to a file path 117 * with a root component. 118 */ 119 private static Path toSafeFilePath(FileSystem fs, String name) { 120 // scan elements of resource name 121 int next; 122 int off = 0; 123 while ((next = name.indexOf('/', off)) != -1) { 124 int len = next - off; 125 if (!mayTranslate(name, off, len)) { 126 return null; 127 } 128 off = next + 1; 129 } 130 int rem = name.length() - off; 131 if (!mayTranslate(name, off, rem)) { 132 return null; 133 } 134 135 // convert to file path 136 Path path; 137 if (File.separatorChar == '/') { 138 path = fs.getPath(name); 139 } else { 140 // not allowed to embed file separators 141 if (name.contains(File.separator)) 142 return null; 143 path = fs.getPath(name.replace('/', File.separatorChar)); 144 } 145 146 // file path not allowed to have root component 147 return (path.getRoot() == null) ? path : null; 148 } 149 150 /** 151 * Returns {@code true} if the element in a resource name is a candidate 152 * to translate to the element of a file path. 153 */ 154 private static boolean mayTranslate(String name, int off, int len) { 155 if (len <= 2) { 156 if (len == 0) 157 return false; 158 boolean starsWithDot = (name.charAt(off) == '.'); 159 if (len == 1 && starsWithDot) 160 return false; 161 if (len == 2 && starsWithDot && (name.charAt(off+1) == '.')) 162 return false; 163 } 164 return true; 165 } 166 167 }