All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.common.io.TempFileCreator Maven / Gradle / Ivy

/*
 * 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 static com.google.common.base.StandardSystemProperty.USER_NAME;
import static com.google.common.base.Throwables.throwIfUnchecked;
import static java.nio.file.attribute.AclEntryFlag.DIRECTORY_INHERIT;
import static java.nio.file.attribute.AclEntryFlag.FILE_INHERIT;
import static java.nio.file.attribute.AclEntryType.ALLOW;
import static java.nio.file.attribute.PosixFilePermissions.asFileAttribute;
import static java.util.Objects.requireNonNull;

import com.google.common.annotations.GwtIncompatible;
import com.google.common.annotations.J2ktIncompatible;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.j2objc.annotations.J2ObjCIncompatible;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.FileSystems;
import java.nio.file.Paths;
import java.nio.file.attribute.AclEntry;
import java.nio.file.attribute.AclEntryPermission;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermissions;
import java.nio.file.attribute.UserPrincipal;
import java.util.EnumSet;
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()}, such as 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 = (int) Class.forName("android.os.Build$VERSION").getField("SDK_INT").get(null);
      int jellyBean =
          (int) Class.forName("android.os.Build$VERSION_CODES").getField("JELLY_BEAN").get(null);
      /*
       * 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();
  }

  /**
   * Creates the permissions normally used for Windows filesystems, looking up the user afresh, even
   * if previous calls have initialized the {@code PermissionSupplier} fields.
   *
   * 

This lets us test the effects of different values of the {@code user.name} system property * without needing a separate VM or classloader. */ @IgnoreJRERequirement // used only when Path is available (and only from tests) @VisibleForTesting static void testMakingUserPermissionsFromScratch() throws IOException { // All we're testing is whether it throws. FileAttribute unused = JavaNioCreator.userPermissions().get(); } @IgnoreJRERequirement // used only when Path is available private static final class JavaNioCreator extends TempFileCreator { @Override File createTempDir() { try { return java.nio.file.Files.createTempDirectory( Paths.get(JAVA_IO_TMPDIR.value()), /* prefix= */ null, directoryPermissions.get()) .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, filePermissions.get()) .toFile(); } @IgnoreJRERequirement // see enclosing class (whose annotation Animal Sniffer ignores here...) private interface PermissionSupplier { FileAttribute get() throws IOException; } private static final PermissionSupplier filePermissions; private static final PermissionSupplier directoryPermissions; static { Set views = FileSystems.getDefault().supportedFileAttributeViews(); if (views.contains("posix")) { filePermissions = () -> asFileAttribute(PosixFilePermissions.fromString("rw-------")); directoryPermissions = () -> asFileAttribute(PosixFilePermissions.fromString("rwx------")); } else if (views.contains("acl")) { filePermissions = directoryPermissions = userPermissions(); } else { filePermissions = directoryPermissions = () -> { throw new IOException("unrecognized FileSystem type " + FileSystems.getDefault()); }; } } private static PermissionSupplier userPermissions() { try { UserPrincipal user = FileSystems.getDefault() .getUserPrincipalLookupService() .lookupPrincipalByName(getUsername()); ImmutableList acl = ImmutableList.of( AclEntry.newBuilder() .setType(ALLOW) .setPrincipal(user) .setPermissions(EnumSet.allOf(AclEntryPermission.class)) .setFlags(DIRECTORY_INHERIT, FILE_INHERIT) .build()); FileAttribute> attribute = new FileAttribute>() { @Override public String name() { return "acl:acl"; } @Override public ImmutableList value() { return acl; } }; return () -> attribute; } catch (IOException e) { // We throw a new exception each time so that the stack trace is right. return () -> { throw new IOException("Could not find user", e); }; } } private static String getUsername() { /* * https://github.com/google/guava/issues/6634: ProcessHandle has more accurate information, * but that class isn't available under all environments that we support. We use it if * available and fall back if not. */ String fromSystemProperty = requireNonNull(USER_NAME.value()); try { Class processHandleClass = Class.forName("java.lang.ProcessHandle"); Class processHandleInfoClass = Class.forName("java.lang.ProcessHandle$Info"); Class optionalClass = Class.forName("java.util.Optional"); /* * We don't *need* to use reflection to access Optional: It's available on all JDKs we * support, and Android code won't get this far, anyway, because ProcessHandle is * unavailable. But given how much other reflection we're using, we might as well use it * here, too, so that we don't need to also suppress an AndroidApiChecker error. */ Method currentMethod = processHandleClass.getMethod("current"); Method infoMethod = processHandleClass.getMethod("info"); Method userMethod = processHandleInfoClass.getMethod("user"); Method orElseMethod = optionalClass.getMethod("orElse", Object.class); Object current = currentMethod.invoke(null); Object info = infoMethod.invoke(current); Object user = userMethod.invoke(info); return (String) requireNonNull(orElseMethod.invoke(user, fromSystemProperty)); } catch (ClassNotFoundException runningUnderAndroidOrJava8) { /* * I'm not sure that we could actually get here for *Android*: I would expect us to enter * the POSIX code path instead. And if we tried this code path, we'd have trouble unless we * were running under a new enough version of Android to support NIO. * * So this is probably just the "Windows Java 8" case. In that case, if we wanted *another* * layer of fallback before consulting the system property, we could try * com.sun.security.auth.module.NTSystem. * * But for now, we use the value from the system property as our best guess. */ return fromSystemProperty; } catch (InvocationTargetException e) { throwIfUnchecked(e.getCause()); // in case it's an Error or something return fromSystemProperty; // should be impossible } catch (NoSuchMethodException shouldBeImpossible) { return fromSystemProperty; } catch (IllegalAccessException shouldBeImpossible) { /* * We don't merge these into `catch (ReflectiveOperationException ...)` or an equivalent * multicatch because ReflectiveOperationException isn't available under Android: * b/124188803 */ return fromSystemProperty; } } } 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() {} }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy