518 lines
22 KiB
Diff
518 lines
22 KiB
Diff
From feb83a1c8fd2e7670b244d5afd23cba5aca43284 Mon Sep 17 00:00:00 2001
|
|
From: cpovirk <cpovirk@google.com>
|
|
Date: Thu, 25 May 2023 13:18:00 -0700
|
|
Subject: [PATCH] Restrict permissions when creating temporary files and
|
|
directories, or fail if that's not possible.
|
|
|
|
(Also, check that the provided `fileThreshold` is non-negative.)
|
|
|
|
- Fixes https://github.com/google/guava/issues/2575
|
|
- Fixes https://github.com/google/guava/issues/4011
|
|
|
|
RELNOTES=Reimplemented `Files.createTempDir` and `FileBackedOutputStream` to further address [CVE-2020-8908](https://github.com/google/guava/issues/4011) and [Guava issue #2575](https://github.com/google/guava/issues/2575) (CVE forthcoming).
|
|
PiperOrigin-RevId: 535359233
|
|
|
|
Refer: https://github.com/google/guava/commit/feb83a1c8fd2e7670b244d5afd23cba5aca43284
|
|
|
|
---
|
|
.../common/io/FileBackedOutputStreamTest.java | 27 +++
|
|
.../common/io/FilesCreateTempDirTest.java | 41 +++-
|
|
.../common/io/FileBackedOutputStream.java | 16 +-
|
|
guava/src/com/google/common/io/Files.java | 48 ++---
|
|
.../common/io/IgnoreJRERequirement.java | 30 +++
|
|
.../com/google/common/io/TempFileCreator.java | 176 ++++++++++++++++++
|
|
6 files changed, 302 insertions(+), 36 deletions(-)
|
|
create mode 100644 guava/src/com/google/common/io/IgnoreJRERequirement.java
|
|
create mode 100644 guava/src/com/google/common/io/TempFileCreator.java
|
|
|
|
diff --git a/guava-tests/test/com/google/common/io/FileBackedOutputStreamTest.java b/guava-tests/test/com/google/common/io/FileBackedOutputStreamTest.java
|
|
index 66558e9..db74f32 100644
|
|
--- a/guava-tests/test/com/google/common/io/FileBackedOutputStreamTest.java
|
|
+++ b/guava-tests/test/com/google/common/io/FileBackedOutputStreamTest.java
|
|
@@ -16,10 +16,18 @@
|
|
|
|
package com.google.common.io;
|
|
|
|
+import static com.google.common.base.StandardSystemProperty.JAVA_IO_TMPDIR;
|
|
+import static com.google.common.truth.Truth.assertThat;
|
|
+import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
|
|
+import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
|
|
+import static org.junit.Assert.assertThrows;
|
|
+
|
|
import com.google.common.testing.GcFinalization;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.OutputStream;
|
|
+import java.nio.file.attribute.PosixFileAttributeView;
|
|
+import java.nio.file.attribute.PosixFileAttributes;
|
|
import java.util.Arrays;
|
|
|
|
/**
|
|
@@ -59,10 +67,21 @@ public class FileBackedOutputStreamTest extends IoTestCase {
|
|
|
|
// Write data to go over the threshold
|
|
if (chunk2 > 0) {
|
|
+ if (JAVA_IO_TMPDIR.value().equals("/sdcard")) {
|
|
+ assertThrows(IOException.class, () -> write(out, data, chunk1, chunk2, singleByte));
|
|
+ return;
|
|
+ }
|
|
write(out, data, chunk1, chunk2, singleByte);
|
|
file = out.getFile();
|
|
assertEquals(dataSize, file.length());
|
|
assertTrue(file.exists());
|
|
+ assertThat(file.getName()).contains("FileBackedOutputStream");
|
|
+ if (!isAndroid()) {
|
|
+ PosixFileAttributes attributes =
|
|
+ java.nio.file.Files.getFileAttributeView(file.toPath(), PosixFileAttributeView.class)
|
|
+ .readAttributes();
|
|
+ assertThat(attributes.permissions()).containsExactly(OWNER_READ, OWNER_WRITE);
|
|
+ }
|
|
}
|
|
out.close();
|
|
|
|
@@ -129,6 +148,10 @@ public class FileBackedOutputStreamTest extends IoTestCase {
|
|
FileBackedOutputStream out = new FileBackedOutputStream(50);
|
|
ByteSource source = out.asByteSource();
|
|
|
|
+ if (JAVA_IO_TMPDIR.value().equals("/sdcard")) {
|
|
+ assertThrows(IOException.class, () -> out.write(data));
|
|
+ return;
|
|
+ }
|
|
out.write(data);
|
|
assertTrue(Arrays.equals(data, source.read()));
|
|
|
|
@@ -160,4 +183,8 @@ public class FileBackedOutputStreamTest extends IoTestCase {
|
|
|
|
out.close();
|
|
}
|
|
+
|
|
+ private static boolean isAndroid() {
|
|
+ return System.getProperty("java.runtime.name", "").contains("Android");
|
|
+ }
|
|
}
|
|
diff --git a/guava-tests/test/com/google/common/io/FilesCreateTempDirTest.java b/guava-tests/test/com/google/common/io/FilesCreateTempDirTest.java
|
|
index 557689e..62098ef 100644
|
|
--- a/guava-tests/test/com/google/common/io/FilesCreateTempDirTest.java
|
|
+++ b/guava-tests/test/com/google/common/io/FilesCreateTempDirTest.java
|
|
@@ -16,9 +16,18 @@
|
|
|
|
package com.google.common.io;
|
|
|
|
+import static com.google.common.base.StandardSystemProperty.JAVA_IO_TMPDIR;
|
|
import static com.google.common.truth.Truth.assertThat;
|
|
+import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE;
|
|
+import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
|
|
+import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
|
|
+import static org.junit.Assert.assertThrows;
|
|
|
|
import java.io.File;
|
|
+import java.io.IOException;
|
|
+import java.nio.file.attribute.PosixFileAttributeView;
|
|
+import java.nio.file.attribute.PosixFileAttributes;
|
|
+import junit.framework.TestCase;
|
|
|
|
/**
|
|
* Unit test for {@link Files#createTempDir}.
|
|
@@ -26,12 +35,32 @@ import java.io.File;
|
|
* @author Chris Nokleberg
|
|
*/
|
|
|
|
-public class FilesCreateTempDirTest extends IoTestCase {
|
|
- public void testCreateTempDir() {
|
|
+@SuppressWarnings("deprecation") // tests of a deprecated method
|
|
+public class FilesCreateTempDirTest extends TestCase {
|
|
+ public void testCreateTempDir() throws IOException {
|
|
+ if (JAVA_IO_TMPDIR.value().equals("/sdcard")) {
|
|
+ assertThrows(IllegalStateException.class, Files::createTempDir);
|
|
+ return;
|
|
+ }
|
|
File temp = Files.createTempDir();
|
|
- assertTrue(temp.exists());
|
|
- assertTrue(temp.isDirectory());
|
|
- assertThat(temp.listFiles()).isEmpty();
|
|
- assertTrue(temp.delete());
|
|
+ try {
|
|
+ assertTrue(temp.exists());
|
|
+ assertTrue(temp.isDirectory());
|
|
+ assertThat(temp.listFiles()).isEmpty();
|
|
+
|
|
+ if (isAndroid()) {
|
|
+ return;
|
|
+ }
|
|
+ PosixFileAttributes attributes =
|
|
+ java.nio.file.Files.getFileAttributeView(temp.toPath(), PosixFileAttributeView.class)
|
|
+ .readAttributes();
|
|
+ assertThat(attributes.permissions()).containsExactly(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE);
|
|
+ } finally {
|
|
+ assertTrue(temp.delete());
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static boolean isAndroid() {
|
|
+ return System.getProperty("java.runtime.name", "").contains("Android");
|
|
}
|
|
}
|
|
diff --git a/guava/src/com/google/common/io/FileBackedOutputStream.java b/guava/src/com/google/common/io/FileBackedOutputStream.java
|
|
index b002561..23d427c 100644
|
|
--- a/guava/src/com/google/common/io/FileBackedOutputStream.java
|
|
+++ b/guava/src/com/google/common/io/FileBackedOutputStream.java
|
|
@@ -14,6 +14,8 @@
|
|
|
|
package com.google.common.io;
|
|
|
|
+import static com.google.common.base.Preconditions.checkArgument;
|
|
+
|
|
import com.google.common.annotations.Beta;
|
|
import com.google.common.annotations.GwtIncompatible;
|
|
import com.google.common.annotations.VisibleForTesting;
|
|
@@ -31,6 +33,14 @@ import java.io.OutputStream;
|
|
* An {@link OutputStream} that starts buffering to a byte array, but switches to file buffering
|
|
* once the data reaches a configurable size.
|
|
*
|
|
+ * <p>When this stream creates a temporary file, it restricts the file's permissions to the current
|
|
+ * user or, in the case of Android, the current app. If that is not possible (as is the case under
|
|
+ * the very old Android Ice Cream Sandwich release), then this stream throws an exception instead of
|
|
+ * creating a file that would be more accessible. (This behavior is new in Guava 32.0.0. Previous
|
|
+ * versions would create a file that is more accessible, as discussed in <a
|
|
+ * href="https://github.com/google/guava/issues/2575">Guava issue 2575</a>. TODO: b/283778848 - Fill
|
|
+ * in CVE number once it's available.)
|
|
+ *
|
|
* <p>This class is thread-safe.
|
|
*
|
|
* @author Chris Nokleberg
|
|
@@ -70,6 +80,7 @@ public final class FileBackedOutputStream extends OutputStream {
|
|
* {@link ByteSource} returned by {@link #asByteSource} is finalized.
|
|
*
|
|
* @param fileThreshold the number of bytes before the stream should switch to buffering to a file
|
|
+ * @throws IllegalArgumentException if {@code fileThreshold} is negative
|
|
*/
|
|
public FileBackedOutputStream(int fileThreshold) {
|
|
this(fileThreshold, false);
|
|
@@ -82,8 +93,11 @@ public final class FileBackedOutputStream extends OutputStream {
|
|
* @param fileThreshold the number of bytes before the stream should switch to buffering to a file
|
|
* @param resetOnFinalize if true, the {@link #reset} method will be called when the {@link
|
|
* ByteSource} returned by {@link #asByteSource} is finalized
|
|
+ * @throws IllegalArgumentException if {@code fileThreshold} is negative
|
|
*/
|
|
public FileBackedOutputStream(int fileThreshold, boolean resetOnFinalize) {
|
|
+ checkArgument(
|
|
+ fileThreshold >= 0, "fileThreshold must be non-negative, but was %s", fileThreshold);
|
|
this.fileThreshold = fileThreshold;
|
|
this.resetOnFinalize = resetOnFinalize;
|
|
memory = new MemoryOutput();
|
|
@@ -193,7 +207,7 @@ public final class FileBackedOutputStream extends OutputStream {
|
|
*/
|
|
private void update(int len) throws IOException {
|
|
if (file == null && (memory.getCount() + len > fileThreshold)) {
|
|
- File temp = File.createTempFile("FileBackedOutputStream", null);
|
|
+ File temp = TempFileCreator.INSTANCE.createTempFile("FileBackedOutputStream");
|
|
if (resetOnFinalize) {
|
|
// Finalizers are not guaranteed to be called on system shutdown;
|
|
// this is insurance.
|
|
diff --git a/guava/src/com/google/common/io/Files.java b/guava/src/com/google/common/io/Files.java
|
|
index b0c4390..a2ef1d0 100644
|
|
--- a/guava/src/com/google/common/io/Files.java
|
|
+++ b/guava/src/com/google/common/io/Files.java
|
|
@@ -67,9 +67,6 @@ import java.util.List;
|
|
@GwtIncompatible
|
|
public final class Files {
|
|
|
|
- /** Maximum loop count when creating temp directories. */
|
|
- private static final int TEMP_DIR_ATTEMPTS = 10000;
|
|
-
|
|
private Files() {}
|
|
|
|
/**
|
|
@@ -380,17 +377,19 @@ public final class Files {
|
|
* Atomically creates a new directory somewhere beneath the system's temporary directory (as
|
|
* defined by the {@code java.io.tmpdir} system property), and returns its name.
|
|
*
|
|
+ * <p>The temporary directory is created with permissions restricted to the current user or, in
|
|
+ * the case of Android, the current app. If that is not possible (as is the case under the very
|
|
+ * old Android Ice Cream Sandwich release), then this method throws an exception instead of
|
|
+ * creating a directory that would be more accessible. (This behavior is new in Guava 32.0.0.
|
|
+ * Previous versions would create a directory that is more accessible, as discussed in <a
|
|
+ * href="https://github.com/google/guava/issues/4011">CVE-2020-8908</a>.)
|
|
+ *
|
|
* <p>Use this method instead of {@link File#createTempFile(String, String)} when you wish to
|
|
* create a directory, not a regular file. A common pitfall is to call {@code createTempFile},
|
|
* delete the file and create a directory in its place, but this leads a race condition which can
|
|
* be exploited to create security vulnerabilities, especially when executable files are to be
|
|
* written into the directory.
|
|
*
|
|
- * <p>Depending on the environmment that this code is run in, the system temporary directory (and
|
|
- * thus the directory this method creates) may be more visible that a program would like - files
|
|
- * written to this directory may be read or overwritten by hostile programs running on the same
|
|
- * machine.
|
|
- *
|
|
* <p>This method assumes that the temporary volume is writable, has free inodes and free blocks,
|
|
* and that it will not be called thousands of times per second.
|
|
*
|
|
@@ -399,33 +398,24 @@ public final class Files {
|
|
*
|
|
* @return the newly-created directory
|
|
* @throws IllegalStateException if the directory could not be created
|
|
+ * @throws UnsupportedOperationException if the system does not support creating temporary
|
|
+ * directories securely
|
|
* @deprecated For Android users, see the <a
|
|
* href="https://developer.android.com/training/data-storage" target="_blank">Data and File
|
|
* Storage overview</a> to select an appropriate temporary directory (perhaps {@code
|
|
- * context.getCacheDir()}). For developers on Java 7 or later, use {@link
|
|
- * java.nio.file.Files#createTempDirectory}, transforming it to a {@link File} using {@link
|
|
- * java.nio.file.Path#toFile() toFile()} if needed.
|
|
+ * context.getCacheDir()}), and create your own directory under that. (For example, you might
|
|
+ * use {@code new File(context.getCacheDir(), "directoryname").mkdir()}, or, if you need an
|
|
+ * arbitrary number of temporary directories, you might have to generate multiple directory
|
|
+ * names in a loop until {@code mkdir()} returns {@code true}.) For developers on Java 7 or
|
|
+ * later, use {@link java.nio.file.Files#createTempDirectory}, transforming it to a {@link
|
|
+ * File} using {@link java.nio.file.Path#toFile() toFile()} if needed. To restrict permissions
|
|
+ * as this method does, pass {@code
|
|
+ * PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------"))} to your
|
|
+ * call to {@code createTempDirectory}.
|
|
*/
|
|
@Deprecated
|
|
public static File createTempDir() {
|
|
- File baseDir = new File(System.getProperty("java.io.tmpdir"));
|
|
- String baseName = System.currentTimeMillis() + "-";
|
|
-
|
|
- for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
|
|
- File tempDir = new File(baseDir, baseName + counter);
|
|
- if (tempDir.mkdir()) {
|
|
- return tempDir;
|
|
- }
|
|
- }
|
|
- throw new IllegalStateException(
|
|
- "Failed to create directory within "
|
|
- + TEMP_DIR_ATTEMPTS
|
|
- + " attempts (tried "
|
|
- + baseName
|
|
- + "0 to "
|
|
- + baseName
|
|
- + (TEMP_DIR_ATTEMPTS - 1)
|
|
- + ')');
|
|
+ return TempFileCreator.INSTANCE.createTempDir();
|
|
}
|
|
|
|
/**
|
|
diff --git a/guava/src/com/google/common/io/IgnoreJRERequirement.java b/guava/src/com/google/common/io/IgnoreJRERequirement.java
|
|
new file mode 100644
|
|
index 0000000..b1b8e10
|
|
--- /dev/null
|
|
+++ b/guava/src/com/google/common/io/IgnoreJRERequirement.java
|
|
@@ -0,0 +1,30 @@
|
|
+/*
|
|
+ * Copyright 2019 The Guava Authors
|
|
+ *
|
|
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
|
+ * in compliance with the License. You may obtain a copy of the License at
|
|
+ *
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
+ *
|
|
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
|
|
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
|
+ * or implied. See the License for the specific language governing permissions and limitations under
|
|
+ * the License.
|
|
+ */
|
|
+
|
|
+package com.google.common.io;
|
|
+
|
|
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
|
|
+import static java.lang.annotation.ElementType.METHOD;
|
|
+import static java.lang.annotation.ElementType.TYPE;
|
|
+
|
|
+import java.lang.annotation.Target;
|
|
+
|
|
+/**
|
|
+ * Disables Animal Sniffer's checking of compatibility with older versions of Java/Android.
|
|
+ *
|
|
+ * <p>Each package's copy of this annotation needs to be listed in our {@code pom.xml}.
|
|
+ */
|
|
+@Target({METHOD, CONSTRUCTOR, TYPE})
|
|
+@ElementTypesAreNonnullByDefault
|
|
+@interface IgnoreJRERequirement {}
|
|
diff --git a/guava/src/com/google/common/io/TempFileCreator.java b/guava/src/com/google/common/io/TempFileCreator.java
|
|
new file mode 100644
|
|
index 0000000..103427a
|
|
--- /dev/null
|
|
+++ b/guava/src/com/google/common/io/TempFileCreator.java
|
|
@@ -0,0 +1,176 @@
|
|
+/*
|
|
+ * Copyright (C) 2007 The Guava Authors
|
|
+ *
|
|
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
|
+ * in compliance with the License. You may obtain a copy of the License at
|
|
+ *
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
+ *
|
|
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
|
|
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
|
+ * or implied. See the License for the specific language governing permissions and limitations under
|
|
+ * the License.
|
|
+ */
|
|
+
|
|
+package com.google.common.io;
|
|
+
|
|
+import static com.google.common.base.StandardSystemProperty.JAVA_IO_TMPDIR;
|
|
+
|
|
+import com.google.common.annotations.GwtIncompatible;
|
|
+import com.google.common.annotations.J2ktIncompatible;
|
|
+import com.google.j2objc.annotations.J2ObjCIncompatible;
|
|
+import java.io.File;
|
|
+import java.io.IOException;
|
|
+import java.nio.file.Paths;
|
|
+import java.nio.file.attribute.FileAttribute;
|
|
+import java.nio.file.attribute.PosixFilePermission;
|
|
+import java.nio.file.attribute.PosixFilePermissions;
|
|
+import java.util.Set;
|
|
+
|
|
+/**
|
|
+ * Creates temporary files and directories whose permissions are restricted to the current user or,
|
|
+ * in the case of Android, the current app. If that is not possible (as is the case under the very
|
|
+ * old Android Ice Cream Sandwich release), then this class throws an exception instead of creating
|
|
+ * a file or directory that would be more accessible.
|
|
+ */
|
|
+@J2ktIncompatible
|
|
+@GwtIncompatible
|
|
+@J2ObjCIncompatible
|
|
+@ElementTypesAreNonnullByDefault
|
|
+abstract class TempFileCreator {
|
|
+ static final TempFileCreator INSTANCE = pickSecureCreator();
|
|
+
|
|
+ /**
|
|
+ * @throws IllegalStateException if the directory could not be created (to implement the contract
|
|
+ * of {@link Files#createTempDir()}
|
|
+ * @throws UnsupportedOperationException if the system does not support creating temporary
|
|
+ * directories securely
|
|
+ */
|
|
+ abstract File createTempDir();
|
|
+
|
|
+ abstract File createTempFile(String prefix) throws IOException;
|
|
+
|
|
+ private static TempFileCreator pickSecureCreator() {
|
|
+ try {
|
|
+ Class.forName("java.nio.file.Path");
|
|
+ return new JavaNioCreator();
|
|
+ } catch (ClassNotFoundException runningUnderAndroid) {
|
|
+ // Try another way.
|
|
+ }
|
|
+
|
|
+ try {
|
|
+ int version = ((Integer) Class.forName("android.os.Build$VERSION").getField("SDK_INT").get(null)).intValue();
|
|
+ int jellyBean =
|
|
+ ((Integer) Class.forName("android.os.Build$VERSION_CODES").getField("JELLY_BEAN").get(null)).intValue();
|
|
+ /*
|
|
+ * I assume that this check can't fail because JELLY_BEAN will be present only if we're
|
|
+ * running under Jelly Bean or higher. But it seems safest to check.
|
|
+ */
|
|
+ if (version < jellyBean) {
|
|
+ return new ThrowingCreator();
|
|
+ }
|
|
+
|
|
+ // Don't merge these catch() blocks, let alone use ReflectiveOperationException directly:
|
|
+ // b/65343391
|
|
+ } catch (NoSuchFieldException e) {
|
|
+ // The JELLY_BEAN field doesn't exist because we're running on a version before Jelly Bean :)
|
|
+ return new ThrowingCreator();
|
|
+ } catch (ClassNotFoundException e) {
|
|
+ // Should be impossible, but we want to return *something* so that class init succeeds.
|
|
+ return new ThrowingCreator();
|
|
+ } catch (IllegalAccessException e) {
|
|
+ // ditto
|
|
+ return new ThrowingCreator();
|
|
+ }
|
|
+
|
|
+ // Android isolates apps' temporary directories since Jelly Bean:
|
|
+ // https://github.com/google/guava/issues/4011#issuecomment-770020802
|
|
+ // So we can create files there with any permissions and still get security from the isolation.
|
|
+ return new JavaIoCreator();
|
|
+ }
|
|
+
|
|
+ @IgnoreJRERequirement // used only when Path is available
|
|
+ private static final class JavaNioCreator extends TempFileCreator {
|
|
+ private static final FileAttribute<Set<PosixFilePermission>> RWX_USER_ONLY =
|
|
+ PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------"));
|
|
+ private static final FileAttribute<Set<PosixFilePermission>> RW_USER_ONLY =
|
|
+ PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-------"));
|
|
+
|
|
+ @Override
|
|
+ File createTempDir() {
|
|
+ try {
|
|
+ return java.nio.file.Files.createTempDirectory(
|
|
+ Paths.get(JAVA_IO_TMPDIR.value()), /* prefix= */ null, RWX_USER_ONLY)
|
|
+ .toFile();
|
|
+ } catch (IOException e) {
|
|
+ throw new IllegalStateException("Failed to create directory", e);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ File createTempFile(String prefix) throws IOException {
|
|
+ return java.nio.file.Files.createTempFile(
|
|
+ Paths.get(JAVA_IO_TMPDIR.value()),
|
|
+ /* prefix= */ prefix,
|
|
+ /* suffix= */ null,
|
|
+ RW_USER_ONLY)
|
|
+ .toFile();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static final class JavaIoCreator extends TempFileCreator {
|
|
+ @Override
|
|
+ File createTempDir() {
|
|
+ File baseDir = new File(JAVA_IO_TMPDIR.value());
|
|
+ @SuppressWarnings("GoodTime") // reading system time without TimeSource
|
|
+ String baseName = System.currentTimeMillis() + "-";
|
|
+
|
|
+ for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
|
|
+ File tempDir = new File(baseDir, baseName + counter);
|
|
+ if (tempDir.mkdir()) {
|
|
+ return tempDir;
|
|
+ }
|
|
+ }
|
|
+ throw new IllegalStateException(
|
|
+ "Failed to create directory within "
|
|
+ + TEMP_DIR_ATTEMPTS
|
|
+ + " attempts (tried "
|
|
+ + baseName
|
|
+ + "0 to "
|
|
+ + baseName
|
|
+ + (TEMP_DIR_ATTEMPTS - 1)
|
|
+ + ')');
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ File createTempFile(String prefix) throws IOException {
|
|
+ return File.createTempFile(
|
|
+ /* prefix= */ prefix,
|
|
+ /* suffix= */ null,
|
|
+ /* directory= */ null /* defaults to java.io.tmpdir */);
|
|
+ }
|
|
+
|
|
+ /** Maximum loop count when creating temp directories. */
|
|
+ private static final int TEMP_DIR_ATTEMPTS = 10000;
|
|
+ }
|
|
+
|
|
+ private static final class ThrowingCreator extends TempFileCreator {
|
|
+ private static final String MESSAGE =
|
|
+ "Guava cannot securely create temporary files or directories under SDK versions before"
|
|
+ + " Jelly Bean. You can create one yourself, either in the insecure default directory"
|
|
+ + " or in a more secure directory, such as context.getCacheDir(). For more information,"
|
|
+ + " see the Javadoc for Files.createTempDir().";
|
|
+
|
|
+ @Override
|
|
+ File createTempDir() {
|
|
+ throw new IllegalStateException(MESSAGE);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ File createTempFile(String prefix) throws IOException {
|
|
+ throw new IOException(MESSAGE);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private TempFileCreator() {}
|
|
+}
|
|
--
|
|
2.33.0
|
|
|