1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.commons.io.file; 19 20 import static org.junit.jupiter.api.Assertions.assertArrayEquals; 21 import static org.junit.jupiter.api.Assertions.assertEquals; 22 import static org.junit.jupiter.api.Assertions.assertFalse; 23 import static org.junit.jupiter.api.Assertions.assertNotEquals; 24 import static org.junit.jupiter.api.Assertions.assertNotNull; 25 import static org.junit.jupiter.api.Assertions.assertNull; 26 import static org.junit.jupiter.api.Assertions.assertThrows; 27 import static org.junit.jupiter.api.Assertions.assertTrue; 28 import static org.junit.jupiter.api.Assumptions.assumeFalse; 29 30 import java.io.File; 31 import java.io.IOException; 32 import java.io.OutputStream; 33 import java.net.URI; 34 import java.net.URISyntaxException; 35 import java.net.URL; 36 import java.nio.charset.StandardCharsets; 37 import java.nio.file.DirectoryStream; 38 import java.nio.file.FileSystem; 39 import java.nio.file.FileSystems; 40 import java.nio.file.Files; 41 import java.nio.file.LinkOption; 42 import java.nio.file.Path; 43 import java.nio.file.Paths; 44 import java.nio.file.attribute.DosFileAttributeView; 45 import java.nio.file.attribute.FileTime; 46 import java.nio.file.attribute.PosixFileAttributes; 47 import java.util.GregorianCalendar; 48 import java.util.HashMap; 49 import java.util.Iterator; 50 import java.util.Map; 51 52 import org.apache.commons.io.FileUtils; 53 import org.apache.commons.io.FilenameUtils; 54 import org.apache.commons.io.filefilter.NameFileFilter; 55 import org.apache.commons.io.test.TestUtils; 56 import org.apache.commons.lang3.ArrayUtils; 57 import org.apache.commons.lang3.StringUtils; 58 import org.apache.commons.lang3.SystemUtils; 59 import org.junit.jupiter.api.Test; 60 61 /** 62 * Tests {@link PathUtils}. 63 */ 64 public class PathUtilsTest extends AbstractTempDirTest { 65 66 private static final String STRING_FIXTURE = "Hello World"; 67 68 private static final byte[] BYTE_ARRAY_FIXTURE = STRING_FIXTURE.getBytes(StandardCharsets.UTF_8); 69 70 private static final String TEST_JAR_NAME = "test.jar"; 71 72 private static final String TEST_JAR_PATH = "src/test/resources/org/apache/commons/io/test.jar"; 73 74 private static final String PATH_FIXTURE = "NOTICE.txt"; 75 76 /** 77 * Creates directory test fixtures. 78 * <ol> 79 * <li>tempDirPath/subdir</li> 80 * <li>tempDirPath/symlinked-dir -> tempDirPath/subdir</li> 81 * </ol> 82 * 83 * @return Path to tempDirPath/subdir 84 * @throws IOException if an I/O error occurs or the parent directory does not exist. 85 */ createTempSymlinkedRelativeDir()86 private Path createTempSymlinkedRelativeDir() throws IOException { 87 final Path targetDir = tempDirPath.resolve("subdir"); 88 final Path symlinkDir = tempDirPath.resolve("symlinked-dir"); 89 Files.createDirectory(targetDir); 90 Files.createSymbolicLink(symlinkDir, targetDir); 91 return symlinkDir; 92 } 93 current()94 private Path current() { 95 return PathUtils.current(); 96 } 97 getLastModifiedMillis(final Path file)98 private Long getLastModifiedMillis(final Path file) throws IOException { 99 return Files.getLastModifiedTime(file).toMillis(); 100 } 101 getNonExistentPath()102 private Path getNonExistentPath() { 103 return Paths.get("/does not exist/for/certain"); 104 } 105 openArchive(final Path p, final boolean createNew)106 private FileSystem openArchive(final Path p, final boolean createNew) throws IOException { 107 if (createNew) { 108 final Map<String, String> env = new HashMap<>(); 109 env.put("create", "true"); 110 final URI fileUri = p.toAbsolutePath().toUri(); 111 final URI uri = URI.create("jar:" + fileUri.toASCIIString()); 112 return FileSystems.newFileSystem(uri, env, null); 113 } 114 return FileSystems.newFileSystem(p, (ClassLoader) null); 115 } 116 setLastModifiedMillis(final Path file, final long millis)117 private void setLastModifiedMillis(final Path file, final long millis) throws IOException { 118 Files.setLastModifiedTime(file, FileTime.fromMillis(millis)); 119 } 120 121 @Test testCopyDirectoryForDifferentFilesystemsWithAbsolutePath()122 public void testCopyDirectoryForDifferentFilesystemsWithAbsolutePath() throws IOException { 123 final Path archivePath = Paths.get(TEST_JAR_PATH); 124 try (FileSystem archive = openArchive(archivePath, false)) { 125 // relative jar -> absolute dir 126 Path sourceDir = archive.getPath("dir1"); 127 PathUtils.copyDirectory(sourceDir, tempDirPath); 128 assertTrue(Files.exists(tempDirPath.resolve("f1"))); 129 130 // absolute jar -> absolute dir 131 sourceDir = archive.getPath("/next"); 132 PathUtils.copyDirectory(sourceDir, tempDirPath); 133 assertTrue(Files.exists(tempDirPath.resolve("dir"))); 134 } 135 } 136 137 @Test testCopyDirectoryForDifferentFilesystemsWithAbsolutePathReverse()138 public void testCopyDirectoryForDifferentFilesystemsWithAbsolutePathReverse() throws IOException { 139 try (FileSystem archive = openArchive(tempDirPath.resolve(TEST_JAR_NAME), true)) { 140 // absolute dir -> relative jar 141 Path targetDir = archive.getPath("target"); 142 Files.createDirectory(targetDir); 143 final Path sourceDir = Paths.get("src/test/resources/org/apache/commons/io/dirs-2-file-size-2").toAbsolutePath(); 144 PathUtils.copyDirectory(sourceDir, targetDir); 145 assertTrue(Files.exists(targetDir.resolve("dirs-a-file-size-1"))); 146 147 // absolute dir -> absolute jar 148 targetDir = archive.getPath("/"); 149 PathUtils.copyDirectory(sourceDir, targetDir); 150 assertTrue(Files.exists(targetDir.resolve("dirs-a-file-size-1"))); 151 } 152 } 153 154 @Test testCopyDirectoryForDifferentFilesystemsWithRelativePath()155 public void testCopyDirectoryForDifferentFilesystemsWithRelativePath() throws IOException { 156 final Path archivePath = Paths.get(TEST_JAR_PATH); 157 try (FileSystem archive = openArchive(archivePath, false); final FileSystem targetArchive = openArchive(tempDirPath.resolve(TEST_JAR_NAME), true)) { 158 final Path targetDir = targetArchive.getPath("targetDir"); 159 Files.createDirectory(targetDir); 160 // relative jar -> relative dir 161 Path sourceDir = archive.getPath("next"); 162 PathUtils.copyDirectory(sourceDir, targetDir); 163 assertTrue(Files.exists(targetDir.resolve("dir"))); 164 165 // absolute jar -> relative dir 166 sourceDir = archive.getPath("/dir1"); 167 PathUtils.copyDirectory(sourceDir, targetDir); 168 assertTrue(Files.exists(targetDir.resolve("f1"))); 169 } 170 } 171 172 @Test testCopyDirectoryForDifferentFilesystemsWithRelativePathReverse()173 public void testCopyDirectoryForDifferentFilesystemsWithRelativePathReverse() throws IOException { 174 try (FileSystem archive = openArchive(tempDirPath.resolve(TEST_JAR_NAME), true)) { 175 // relative dir -> relative jar 176 Path targetDir = archive.getPath("target"); 177 Files.createDirectory(targetDir); 178 final Path sourceDir = Paths.get("src/test/resources/org/apache/commons/io/dirs-2-file-size-2"); 179 PathUtils.copyDirectory(sourceDir, targetDir); 180 assertTrue(Files.exists(targetDir.resolve("dirs-a-file-size-1"))); 181 182 // relative dir -> absolute jar 183 targetDir = archive.getPath("/"); 184 PathUtils.copyDirectory(sourceDir, targetDir); 185 assertTrue(Files.exists(targetDir.resolve("dirs-a-file-size-1"))); 186 } 187 } 188 189 @Test testCopyFile()190 public void testCopyFile() throws IOException { 191 final Path sourceFile = Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1/file-size-1.bin"); 192 final Path targetFile = PathUtils.copyFileToDirectory(sourceFile, tempDirPath); 193 assertTrue(Files.exists(targetFile)); 194 assertEquals(Files.size(sourceFile), Files.size(targetFile)); 195 } 196 197 @Test testCopyURL()198 public void testCopyURL() throws IOException { 199 final Path sourceFile = Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1/file-size-1.bin"); 200 final URL url = new URL("file:///" + FilenameUtils.getPath(sourceFile.toAbsolutePath().toString()) + sourceFile.getFileName()); 201 final Path targetFile = PathUtils.copyFileToDirectory(url, tempDirPath); 202 assertTrue(Files.exists(targetFile)); 203 assertEquals(Files.size(sourceFile), Files.size(targetFile)); 204 } 205 206 @Test testCreateDirectoriesAlreadyExists()207 public void testCreateDirectoriesAlreadyExists() throws IOException { 208 assertEquals(tempDirPath.getParent(), PathUtils.createParentDirectories(tempDirPath)); 209 } 210 211 @SuppressWarnings("resource") // FileSystems.getDefault() is a singleton 212 @Test testCreateDirectoriesForRoots()213 public void testCreateDirectoriesForRoots() throws IOException { 214 for (final Path path : FileSystems.getDefault().getRootDirectories()) { 215 final Path parent = path.getParent(); 216 assertNull(parent); 217 assertEquals(parent, PathUtils.createParentDirectories(path)); 218 } 219 } 220 221 @Test testCreateDirectoriesForRootsLinkOptionNull()222 public void testCreateDirectoriesForRootsLinkOptionNull() throws IOException { 223 for (final File f : File.listRoots()) { 224 final Path path = f.toPath(); 225 assertEquals(path.getParent(), PathUtils.createParentDirectories(path, (LinkOption) null)); 226 } 227 } 228 229 @Test testCreateDirectoriesNew()230 public void testCreateDirectoriesNew() throws IOException { 231 assertEquals(tempDirPath, PathUtils.createParentDirectories(tempDirPath.resolve("child"))); 232 } 233 234 @Test testCreateDirectoriesSymlink()235 public void testCreateDirectoriesSymlink() throws IOException { 236 final Path symlinkedDir = createTempSymlinkedRelativeDir(); 237 final String leafDirName = "child"; 238 final Path newDirFollowed = PathUtils.createParentDirectories(symlinkedDir.resolve(leafDirName), PathUtils.NULL_LINK_OPTION); 239 assertEquals(Files.readSymbolicLink(symlinkedDir), newDirFollowed); 240 } 241 242 @Test testCreateDirectoriesSymlinkClashing()243 public void testCreateDirectoriesSymlinkClashing() throws IOException { 244 final Path symlinkedDir = createTempSymlinkedRelativeDir(); 245 assertEquals(symlinkedDir, PathUtils.createParentDirectories(symlinkedDir.resolve("child"))); 246 } 247 248 @Test testGetLastModifiedFileTime_File_Present()249 public void testGetLastModifiedFileTime_File_Present() throws IOException { 250 assertNotNull(PathUtils.getLastModifiedFileTime(current().toFile())); 251 } 252 253 @Test testGetLastModifiedFileTime_Path_Absent()254 public void testGetLastModifiedFileTime_Path_Absent() throws IOException { 255 assertNull(PathUtils.getLastModifiedFileTime(getNonExistentPath())); 256 } 257 258 @Test testGetLastModifiedFileTime_Path_FileTime_Absent()259 public void testGetLastModifiedFileTime_Path_FileTime_Absent() throws IOException { 260 final FileTime fromMillis = FileTime.fromMillis(0); 261 assertEquals(fromMillis, PathUtils.getLastModifiedFileTime(getNonExistentPath(), fromMillis)); 262 } 263 264 @Test testGetLastModifiedFileTime_Path_Present()265 public void testGetLastModifiedFileTime_Path_Present() throws IOException { 266 assertNotNull(PathUtils.getLastModifiedFileTime(current())); 267 } 268 269 @Test testGetLastModifiedFileTime_URI_Present()270 public void testGetLastModifiedFileTime_URI_Present() throws IOException { 271 assertNotNull(PathUtils.getLastModifiedFileTime(current().toUri())); 272 } 273 274 @Test testGetLastModifiedFileTime_URL_Present()275 public void testGetLastModifiedFileTime_URL_Present() throws IOException, URISyntaxException { 276 assertNotNull(PathUtils.getLastModifiedFileTime(current().toUri().toURL())); 277 } 278 279 @Test testGetTempDirectory()280 public void testGetTempDirectory() { 281 final Path tempDirectory = Paths.get(System.getProperty("java.io.tmpdir")); 282 assertEquals(tempDirectory, PathUtils.getTempDirectory()); 283 } 284 285 @Test testIsDirectory()286 public void testIsDirectory() throws IOException { 287 assertFalse(PathUtils.isDirectory(null)); 288 289 assertTrue(PathUtils.isDirectory(tempDirPath)); 290 try (TempFile testFile1 = TempFile.create(tempDirPath, "prefix", null)) { 291 assertFalse(PathUtils.isDirectory(testFile1.get())); 292 293 Path ref = null; 294 try (TempDirectory tempDir = TempDirectory.create(getClass().getCanonicalName())) { 295 ref = tempDir.get(); 296 assertTrue(PathUtils.isDirectory(tempDir.get())); 297 } 298 assertFalse(PathUtils.isDirectory(ref)); 299 } 300 } 301 302 @Test testIsPosix()303 public void testIsPosix() throws IOException { 304 boolean isPosix; 305 try { 306 Files.getPosixFilePermissions(current()); 307 isPosix = true; 308 } catch (final UnsupportedOperationException e) { 309 isPosix = false; 310 } 311 assertEquals(isPosix, PathUtils.isPosix(current())); 312 } 313 314 @Test testIsRegularFile()315 public void testIsRegularFile() throws IOException { 316 assertFalse(PathUtils.isRegularFile(null)); 317 318 assertFalse(PathUtils.isRegularFile(tempDirPath)); 319 try (TempFile testFile1 = TempFile.create(tempDirPath, "prefix", null)) { 320 assertTrue(PathUtils.isRegularFile(testFile1.get())); 321 322 Files.delete(testFile1.get()); 323 assertFalse(PathUtils.isRegularFile(testFile1.get())); 324 } 325 } 326 327 @Test testNewDirectoryStream()328 public void testNewDirectoryStream() throws Exception { 329 final PathFilter pathFilter = new NameFileFilter(PATH_FIXTURE); 330 try (DirectoryStream<Path> stream = PathUtils.newDirectoryStream(current(), pathFilter)) { 331 final Iterator<Path> iterator = stream.iterator(); 332 final Path path = iterator.next(); 333 assertEquals(PATH_FIXTURE, path.getFileName().toString()); 334 assertFalse(iterator.hasNext()); 335 } 336 } 337 338 @Test testNewOutputStreamExistingFileAppendFalse()339 public void testNewOutputStreamExistingFileAppendFalse() throws IOException { 340 testNewOutputStreamNewFile(false); 341 testNewOutputStreamNewFile(false); 342 } 343 344 @Test testNewOutputStreamExistingFileAppendTrue()345 public void testNewOutputStreamExistingFileAppendTrue() throws IOException { 346 testNewOutputStreamNewFile(true); 347 final Path file = writeToNewOutputStream(true); 348 assertArrayEquals(ArrayUtils.addAll(BYTE_ARRAY_FIXTURE, BYTE_ARRAY_FIXTURE), Files.readAllBytes(file)); 349 } 350 testNewOutputStreamNewFile(final boolean append)351 public void testNewOutputStreamNewFile(final boolean append) throws IOException { 352 final Path file = writeToNewOutputStream(append); 353 assertArrayEquals(BYTE_ARRAY_FIXTURE, Files.readAllBytes(file)); 354 } 355 356 @Test testNewOutputStreamNewFileAppendFalse()357 public void testNewOutputStreamNewFileAppendFalse() throws IOException { 358 testNewOutputStreamNewFile(false); 359 } 360 361 @Test testNewOutputStreamNewFileAppendTrue()362 public void testNewOutputStreamNewFileAppendTrue() throws IOException { 363 testNewOutputStreamNewFile(true); 364 } 365 366 @Test testNewOutputStreamNewFileInsideExistingSymlinkedDir()367 public void testNewOutputStreamNewFileInsideExistingSymlinkedDir() throws IOException { 368 final Path symlinkDir = createTempSymlinkedRelativeDir(); 369 final Path file = symlinkDir.resolve("test.txt"); 370 try (OutputStream outputStream = PathUtils.newOutputStream(file, new LinkOption[] {})) { 371 // empty 372 } 373 try (OutputStream outputStream = PathUtils.newOutputStream(file, null)) { 374 // empty 375 } 376 try (OutputStream outputStream = PathUtils.newOutputStream(file, true)) { 377 // empty 378 } 379 try (OutputStream outputStream = PathUtils.newOutputStream(file, false)) { 380 // empty 381 } 382 } 383 384 @Test testReadAttributesPosix()385 public void testReadAttributesPosix() throws IOException { 386 boolean isPosix; 387 try { 388 Files.getPosixFilePermissions(current()); 389 isPosix = true; 390 } catch (final UnsupportedOperationException e) { 391 isPosix = false; 392 } 393 assertEquals(isPosix, PathUtils.readAttributes(current(), PosixFileAttributes.class) != null); 394 } 395 396 @Test testReadStringEmptyFile()397 public void testReadStringEmptyFile() throws IOException { 398 final Path path = Paths.get("src/test/resources/org/apache/commons/io/test-file-empty.bin"); 399 assertEquals(StringUtils.EMPTY, PathUtils.readString(path, StandardCharsets.UTF_8)); 400 assertEquals(StringUtils.EMPTY, PathUtils.readString(path, null)); 401 } 402 403 @Test testReadStringSimpleUtf8()404 public void testReadStringSimpleUtf8() throws IOException { 405 final Path path = Paths.get("src/test/resources/org/apache/commons/io/test-file-simple-utf8.bin"); 406 final String expected = "ABC\r\n"; 407 assertEquals(expected, PathUtils.readString(path, StandardCharsets.UTF_8)); 408 assertEquals(expected, PathUtils.readString(path, null)); 409 } 410 411 @Test testSetReadOnlyFile()412 public void testSetReadOnlyFile() throws IOException { 413 final Path resolved = tempDirPath.resolve("testSetReadOnlyFile.txt"); 414 // Ask now, as we are allowed before editing parent permissions. 415 final boolean isPosix = PathUtils.isPosix(tempDirPath); 416 417 // TEMP HACK 418 assumeFalse(SystemUtils.IS_OS_LINUX); 419 420 PathUtils.writeString(resolved, "test", StandardCharsets.UTF_8); 421 final boolean readable = Files.isReadable(resolved); 422 final boolean writable = Files.isWritable(resolved); 423 final boolean regularFile = Files.isRegularFile(resolved); 424 final boolean executable = Files.isExecutable(resolved); 425 final boolean hidden = Files.isHidden(resolved); 426 final boolean directory = Files.isDirectory(resolved); 427 final boolean symbolicLink = Files.isSymbolicLink(resolved); 428 // Sanity checks 429 assertTrue(readable); 430 assertTrue(writable); 431 // Test A 432 PathUtils.setReadOnly(resolved, false); 433 assertTrue(Files.isReadable(resolved), "isReadable"); 434 assertTrue(Files.isWritable(resolved), "isWritable"); 435 // Again, shouldn't blow up. 436 PathUtils.setReadOnly(resolved, false); 437 assertTrue(Files.isReadable(resolved), "isReadable"); 438 assertTrue(Files.isWritable(resolved), "isWritable"); 439 // 440 assertEquals(regularFile, Files.isReadable(resolved)); 441 assertEquals(executable, Files.isExecutable(resolved)); 442 assertEquals(hidden, Files.isHidden(resolved)); 443 assertEquals(directory, Files.isDirectory(resolved)); 444 assertEquals(symbolicLink, Files.isSymbolicLink(resolved)); 445 // Test B 446 PathUtils.setReadOnly(resolved, true); 447 if (isPosix) { 448 // On POSIX, now that the parent is not WX, the file is not readable. 449 assertFalse(Files.isReadable(resolved), "isReadable"); 450 } else { 451 assertTrue(Files.isReadable(resolved), "isReadable"); 452 } 453 assertFalse(Files.isWritable(resolved), "isWritable"); 454 final DosFileAttributeView dosFileAttributeView = PathUtils.getDosFileAttributeView(resolved); 455 if (dosFileAttributeView != null) { 456 assertTrue(dosFileAttributeView.readAttributes().isReadOnly()); 457 } 458 if (isPosix) { 459 assertFalse(Files.isReadable(resolved)); 460 } else { 461 assertEquals(regularFile, Files.isReadable(resolved)); 462 } 463 assertEquals(executable, Files.isExecutable(resolved)); 464 assertEquals(hidden, Files.isHidden(resolved)); 465 assertEquals(directory, Files.isDirectory(resolved)); 466 assertEquals(symbolicLink, Files.isSymbolicLink(resolved)); 467 // 468 PathUtils.setReadOnly(resolved, false); 469 PathUtils.deleteFile(resolved); 470 } 471 472 @Test testTouch()473 public void testTouch() throws IOException { 474 assertThrows(NullPointerException.class, () -> FileUtils.touch(null)); 475 476 final Path file = managedTempDirPath.resolve("touch.txt"); 477 Files.deleteIfExists(file); 478 assertFalse(Files.exists(file), "Bad test: test file still exists"); 479 PathUtils.touch(file); 480 assertTrue(Files.exists(file), "touch() created file"); 481 try (OutputStream out = Files.newOutputStream(file)) { 482 assertEquals(0, Files.size(file), "Created empty file."); 483 out.write(0); 484 } 485 assertEquals(1, Files.size(file), "Wrote one byte to file"); 486 final long y2k = new GregorianCalendar(2000, 0, 1).getTime().getTime(); 487 setLastModifiedMillis(file, y2k); // 0L fails on Win98 488 assertEquals(y2k, getLastModifiedMillis(file), "Bad test: set lastModified set incorrect value"); 489 final long nowMillis = System.currentTimeMillis(); 490 PathUtils.touch(file); 491 assertEquals(1, Files.size(file), "FileUtils.touch() didn't empty the file."); 492 assertNotEquals(y2k, getLastModifiedMillis(file), "FileUtils.touch() changed lastModified"); 493 final int delta = 3000; 494 assertTrue(getLastModifiedMillis(file) >= nowMillis - delta, "FileUtils.touch() changed lastModified to more than now-3s"); 495 assertTrue(getLastModifiedMillis(file) <= nowMillis + delta, "FileUtils.touch() changed lastModified to less than now+3s"); 496 } 497 498 @Test testWriteStringToFile1()499 public void testWriteStringToFile1() throws Exception { 500 final Path file = tempDirPath.resolve("write.txt"); 501 PathUtils.writeString(file, "Hello /u1234", StandardCharsets.UTF_8); 502 final byte[] text = "Hello /u1234".getBytes(StandardCharsets.UTF_8); 503 TestUtils.assertEqualContent(text, file); 504 } 505 506 /** 507 * Tests newOutputStream() here and don't use Files.write obviously. 508 */ writeToNewOutputStream(final boolean append)509 private Path writeToNewOutputStream(final boolean append) throws IOException { 510 final Path file = tempDirPath.resolve("test1.txt"); 511 try (OutputStream os = PathUtils.newOutputStream(file, append)) { 512 os.write(BYTE_ARRAY_FIXTURE); 513 } 514 return file; 515 } 516 517 } 518