FileUtils.java revision 15447798a38d2b5acb1998731340255f4203f294
19066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/* 29066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Copyright (C) 2006 The Android Open Source Project 39066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 49066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); 59066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * you may not use this file except in compliance with the License. 69066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * You may obtain a copy of the License at 79066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 89066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0 99066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Unless required by applicable law or agreed to in writing, software 119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, 129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * See the License for the specific language governing permissions and 149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * limitations under the License. 159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpackage android.os; 189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1985ced632680642fce680d141ddd10299ff849233Jeff Sharkeyimport android.annotation.NonNull; 2015447798a38d2b5acb1998731340255f4203f294Jeff Sharkeyimport android.annotation.Nullable; 2162539a220c6810f66b63060326bd1668f7d6b029Ben Kwaimport android.provider.DocumentsContract.Document; 2234385d352da19805ae948215e2edbeedd16b7941Elliott Hughesimport android.system.ErrnoException; 2334385d352da19805ae948215e2edbeedd16b7941Elliott Hughesimport android.system.Os; 240cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkeyimport android.text.TextUtils; 25d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkeyimport android.util.Log; 26184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkeyimport android.util.Slog; 2762539a220c6810f66b63060326bd1668f7d6b029Ben Kwaimport android.webkit.MimeTypeMap; 28184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey 294f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkeyimport com.android.internal.annotations.VisibleForTesting; 304f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey 3190619816d99154d504a14774c6f2d5f4254ff780Guang Zhuimport java.io.BufferedInputStream; 329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.ByteArrayOutputStream; 339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.File; 34184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkeyimport java.io.FileDescriptor; 359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.FileInputStream; 366d25a990afffd5eb385aba3043d5dfad36f1539aWink Savilleimport java.io.FileNotFoundException; 379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.FileOutputStream; 38da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwoodimport java.io.FileWriter; 399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.IOException; 409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.InputStream; 414f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkeyimport java.nio.charset.StandardCharsets; 42d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkeyimport java.util.Arrays; 43d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkeyimport java.util.Comparator; 4462539a220c6810f66b63060326bd1668f7d6b029Ben Kwaimport java.util.Objects; 459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.regex.Pattern; 466d25a990afffd5eb385aba3043d5dfad36f1539aWink Savilleimport java.util.zip.CRC32; 476d25a990afffd5eb385aba3043d5dfad36f1539aWink Savilleimport java.util.zip.CheckedInputStream; 489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/** 509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Tools for managing files. Not for public consumption. 519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @hide 529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 536d25a990afffd5eb385aba3043d5dfad36f1539aWink Savillepublic class FileUtils { 54d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey private static final String TAG = "FileUtils"; 55d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey 569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static final int S_IRWXU = 00700; 579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static final int S_IRUSR = 00400; 589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static final int S_IWUSR = 00200; 599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static final int S_IXUSR = 00100; 609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static final int S_IRWXG = 00070; 629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static final int S_IRGRP = 00040; 639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static final int S_IWGRP = 00020; 649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static final int S_IXGRP = 00010; 659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static final int S_IRWXO = 00007; 679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static final int S_IROTH = 00004; 689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static final int S_IWOTH = 00002; 699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static final int S_IXOTH = 00001; 709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** Regular expression for safe filenames: no spaces or metacharacters */ 729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private static final Pattern SAFE_FILENAME_PATTERN = Pattern.compile("[\\w%+,./=_-]+"); 739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 7485ced632680642fce680d141ddd10299ff849233Jeff Sharkey private static final File[] EMPTY = new File[0]; 7585ced632680642fce680d141ddd10299ff849233Jeff Sharkey 76184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey /** 77184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * Set owner and mode of of given {@link File}. 78184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * 79184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * @param mode to apply through {@code chmod} 80184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * @param uid to apply through {@code chown}, or -1 to leave unchanged 81184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * @param gid to apply through {@code chown}, or -1 to leave unchanged 82184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * @return 0 on success, otherwise errno. 83184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey */ 84184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey public static int setPermissions(File path, int mode, int uid, int gid) { 85184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey return setPermissions(path.getAbsolutePath(), mode, uid, gid); 86184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey } 87184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey 88184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey /** 89184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * Set owner and mode of of given path. 90184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * 91184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * @param mode to apply through {@code chmod} 92184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * @param uid to apply through {@code chown}, or -1 to leave unchanged 93184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * @param gid to apply through {@code chown}, or -1 to leave unchanged 94184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * @return 0 on success, otherwise errno. 95184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey */ 96184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey public static int setPermissions(String path, int mode, int uid, int gid) { 97184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey try { 9834385d352da19805ae948215e2edbeedd16b7941Elliott Hughes Os.chmod(path, mode); 99184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey } catch (ErrnoException e) { 100184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey Slog.w(TAG, "Failed to chmod(" + path + "): " + e); 101184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey return e.errno; 102184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey } 103184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey 104184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey if (uid >= 0 || gid >= 0) { 105184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey try { 10634385d352da19805ae948215e2edbeedd16b7941Elliott Hughes Os.chown(path, uid, gid); 107184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey } catch (ErrnoException e) { 108184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey Slog.w(TAG, "Failed to chown(" + path + "): " + e); 109184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey return e.errno; 110184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey } 111184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey } 112184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey 113184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey return 0; 114184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey } 1159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 116184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey /** 117184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * Set owner and mode of of given {@link FileDescriptor}. 118184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * 119184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * @param mode to apply through {@code chmod} 120184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * @param uid to apply through {@code chown}, or -1 to leave unchanged 121184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * @param gid to apply through {@code chown}, or -1 to leave unchanged 122184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * @return 0 on success, otherwise errno. 123184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey */ 124184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) { 125184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey try { 12634385d352da19805ae948215e2edbeedd16b7941Elliott Hughes Os.fchmod(fd, mode); 127184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey } catch (ErrnoException e) { 128184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey Slog.w(TAG, "Failed to fchmod(): " + e); 129184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey return e.errno; 130184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey } 131184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey 132184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey if (uid >= 0 || gid >= 0) { 133184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey try { 13434385d352da19805ae948215e2edbeedd16b7941Elliott Hughes Os.fchown(fd, uid, gid); 135184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey } catch (ErrnoException e) { 136184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey Slog.w(TAG, "Failed to fchown(): " + e); 137184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey return e.errno; 138184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey } 139184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey } 140184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey 141184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey return 0; 142184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey } 143184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey 144184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey /** 145184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey * Return owning UID of given path, otherwise -1. 146184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey */ 147184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey public static int getUid(String path) { 148184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey try { 14934385d352da19805ae948215e2edbeedd16b7941Elliott Hughes return Os.stat(path).st_uid; 150184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey } catch (ErrnoException e) { 151184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey return -1; 152184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey } 153184a0100abc431fc3d6d8dd1b20212b84958cadaJeff Sharkey } 154053f61d6a6e23825e680dc49982e55c5b4299d61Dianne Hackborn 1558bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn /** 1568bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn * Perform an fsync on the given FileOutputStream. The stream at this 1578bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn * point must be flushed but not yet closed. 1588bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn */ 1598bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn public static boolean sync(FileOutputStream stream) { 1608bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn try { 1618bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn if (stream != null) { 1628bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn stream.getFD().sync(); 1638bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn } 1648bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn return true; 1658bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn } catch (IOException e) { 1668bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn } 1678bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn return false; 1688bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn } 1698bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn 1709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // copy a file from srcFile to destFile, return true if succeed, return 1719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // false if fail 1729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static boolean copyFile(File srcFile, File destFile) { 1739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project boolean result = false; 1749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project try { 1759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project InputStream in = new FileInputStream(srcFile); 1769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project try { 1779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project result = copyToFile(in, destFile); 1789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } finally { 1799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project in.close(); 1809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } catch (IOException e) { 1829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project result = false; 1839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return result; 1859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 18690619816d99154d504a14774c6f2d5f4254ff780Guang Zhu 1879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 1889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Copy data from a source stream to destFile. 1899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Return true if succeed, return false if failed. 1909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 1919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static boolean copyToFile(InputStream inputStream, File destFile) { 1929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project try { 1931afd1c90ebe789b8d3a137004127a50d2db7e3b5Dianne Hackborn if (destFile.exists()) { 1941afd1c90ebe789b8d3a137004127a50d2db7e3b5Dianne Hackborn destFile.delete(); 1951afd1c90ebe789b8d3a137004127a50d2db7e3b5Dianne Hackborn } 1968bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn FileOutputStream out = new FileOutputStream(destFile); 1979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project try { 1989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project byte[] buffer = new byte[4096]; 1999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int bytesRead; 2009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project while ((bytesRead = inputStream.read(buffer)) >= 0) { 2019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project out.write(buffer, 0, bytesRead); 2029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } finally { 2048bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn out.flush(); 2058bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn try { 2068bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn out.getFD().sync(); 2078bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn } catch (IOException e) { 2088bdf5935c0db4a66ab33a10b43398d2523cfa15dDianne Hackborn } 2099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project out.close(); 2109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return true; 2129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } catch (IOException e) { 2139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return false; 2149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 2189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Check if a filename is "safe" (no metacharacters or spaces). 2199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param file The file to check 2209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 2219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static boolean isFilenameSafe(File file) { 2229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Note, we check whether it matches what's known to be safe, 2239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // rather than what's known to be unsafe. Non-ASCII, control 2249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // characters, etc. are all unsafe by default. 2259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return SAFE_FILENAME_PATTERN.matcher(file.getPath()).matches(); 2269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 2299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Read a text file into a String, optionally limiting the length. 2309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param file to read (will not seek, so things like /proc files are OK) 2319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param max length (positive for head, negative of tail, 0 for no limit) 2329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param ellipsis to add of the file was truncated (can be null) 2339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @return the contents of the file, possibly truncated 2349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @throws IOException if something goes wrong reading the file 2359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 2369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public static String readTextFile(File file, int max, String ellipsis) throws IOException { 2379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project InputStream input = new FileInputStream(file); 23890619816d99154d504a14774c6f2d5f4254ff780Guang Zhu // wrapping a BufferedInputStream around it because when reading /proc with unbuffered 23990619816d99154d504a14774c6f2d5f4254ff780Guang Zhu // input stream, bytes read not equal to buffer size is not necessarily the correct 24090619816d99154d504a14774c6f2d5f4254ff780Guang Zhu // indication for EOF; but it is true for BufferedInputStream due to its implementation. 24190619816d99154d504a14774c6f2d5f4254ff780Guang Zhu BufferedInputStream bis = new BufferedInputStream(input); 2429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project try { 24342471dd5552a346dd82a58a663159875ccc4fb79Dan Egnor long size = file.length(); 24442471dd5552a346dd82a58a663159875ccc4fb79Dan Egnor if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes 24542471dd5552a346dd82a58a663159875ccc4fb79Dan Egnor if (size > 0 && (max == 0 || size < max)) max = (int) size; 2469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project byte[] data = new byte[max + 1]; 24790619816d99154d504a14774c6f2d5f4254ff780Guang Zhu int length = bis.read(data); 2489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (length <= 0) return ""; 2499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (length <= max) return new String(data, 0, length); 2509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (ellipsis == null) return new String(data, 0, max); 2519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return new String(data, 0, max) + ellipsis; 25242471dd5552a346dd82a58a663159875ccc4fb79Dan Egnor } else if (max < 0) { // "tail" mode: keep the last N 2539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int len; 2549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project boolean rolled = false; 255d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey byte[] last = null; 256d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey byte[] data = null; 2579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project do { 2589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (last != null) rolled = true; 2599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project byte[] tmp = last; last = data; data = tmp; 2609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (data == null) data = new byte[-max]; 26190619816d99154d504a14774c6f2d5f4254ff780Guang Zhu len = bis.read(data); 2629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } while (len == data.length); 2639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 2649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (last == null && len <= 0) return ""; 2659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (last == null) return new String(data, 0, len); 2669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (len > 0) { 2679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project rolled = true; 2689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project System.arraycopy(last, len, last, 0, last.length - len); 2699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project System.arraycopy(data, 0, last, last.length - len, len); 2709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (ellipsis == null || !rolled) return new String(last); 2729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return ellipsis + new String(last); 27342471dd5552a346dd82a58a663159875ccc4fb79Dan Egnor } else { // "cat" mode: size unknown, read it all in streaming fashion 2749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ByteArrayOutputStream contents = new ByteArrayOutputStream(); 2759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int len; 2769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project byte[] data = new byte[1024]; 2779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project do { 27890619816d99154d504a14774c6f2d5f4254ff780Guang Zhu len = bis.read(data); 2799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (len > 0) contents.write(data, 0, len); 2809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } while (len == data.length); 2819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return contents.toString(); 2829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } finally { 28490619816d99154d504a14774c6f2d5f4254ff780Guang Zhu bis.close(); 2859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project input.close(); 2869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 2879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 288da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood 289da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood /** 290da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood * Writes string to file. Basically same as "echo -n $string > $filename" 291da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood * 292da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood * @param filename 293da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood * @param string 294da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood * @throws IOException 295da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood */ 296da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood public static void stringToFile(String filename, String string) throws IOException { 297da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood FileWriter out = new FileWriter(filename); 298da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood try { 299da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood out.write(string); 300da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood } finally { 301da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood out.close(); 302da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood } 303da8bb74b9d9ffcb095815db800d0816c411f1fbaMike Lockwood } 3041b9a6a6e58fd73b5d1b6a434d17f0a69806858ecWink Saville 3056d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville /** 3066d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville * Computes the checksum of a file using the CRC32 checksum routine. 3076d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville * The value of the checksum is returned. 3086d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville * 3096d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville * @param file the file to checksum, must not be null 3106d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville * @return the checksum value or an exception is thrown. 3116d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville */ 3126d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville public static long checksumCrc32(File file) throws FileNotFoundException, IOException { 3136d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville CRC32 checkSummer = new CRC32(); 3146d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville CheckedInputStream cis = null; 3156d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville 3166d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville try { 3176d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville cis = new CheckedInputStream( new FileInputStream(file), checkSummer); 3186d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville byte[] buf = new byte[128]; 3196d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville while(cis.read(buf) >= 0) { 3206d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville // Just read for checksum to get calculated. 3216d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville } 3226d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville return checkSummer.getValue(); 3236d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville } finally { 3246d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville if (cis != null) { 3256d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville try { 3266d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville cis.close(); 3276d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville } catch (IOException e) { 3286d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville } 3296d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville } 3306d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville } 3316d25a990afffd5eb385aba3043d5dfad36f1539aWink Saville } 332d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey 333d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey /** 334d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey * Delete older files in a directory until only those matching the given 335d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey * constraints remain. 336d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey * 337d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey * @param minCount Always keep at least this many files. 338d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey * @param minAge Always keep files younger than this age. 339ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey * @return if any files were deleted. 340d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey */ 341ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey public static boolean deleteOlderFiles(File dir, int minCount, long minAge) { 342d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey if (minCount < 0 || minAge < 0) { 343d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey throw new IllegalArgumentException("Constraints must be positive or 0"); 344d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey } 345d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey 346d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey final File[] files = dir.listFiles(); 347ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey if (files == null) return false; 348d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey 349d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey // Sort with newest files first 350d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey Arrays.sort(files, new Comparator<File>() { 351d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey @Override 352d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey public int compare(File lhs, File rhs) { 353d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey return (int) (rhs.lastModified() - lhs.lastModified()); 354d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey } 355d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey }); 356d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey 357d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey // Keep at least minCount files 358ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey boolean deleted = false; 359d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey for (int i = minCount; i < files.length; i++) { 360d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey final File file = files[i]; 361d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey 362d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey // Keep files newer than minAge 363d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey final long age = System.currentTimeMillis() - file.lastModified(); 364d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey if (age > minAge) { 365ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey if (file.delete()) { 366ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey Log.d(TAG, "Deleted old file " + file); 367ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey deleted = true; 368ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey } 369d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey } 370d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey } 371ebf8ad5d91b22eb4359c75711a5b70ddcce0723dJeff Sharkey return deleted; 372d9526907d1a51ef0b35bfbbeee43fa209d8b5bbfJeff Sharkey } 3734ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey 3744ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey /** 3754ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey * Test if a file lives under the given directory, either as a direct child 3764ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey * or a distant grandchild. 3774ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey * <p> 3784ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey * Both files <em>must</em> have been resolved using 3794ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey * {@link File#getCanonicalFile()} to avoid symlink or path traversal 3804ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey * attacks. 3814ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey */ 3824887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey public static boolean contains(File[] dirs, File file) { 3834887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey for (File dir : dirs) { 3844887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey if (contains(dir, file)) { 3854887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey return true; 3864887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey } 3874887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey } 3884887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey return false; 3894887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey } 3904887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey 3914887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey /** 3924887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey * Test if a file lives under the given directory, either as a direct child 3934887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey * or a distant grandchild. 3944887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey * <p> 3954887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey * Both files <em>must</em> have been resolved using 3964887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey * {@link File#getCanonicalFile()} to avoid symlink or path traversal 3974887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey * attacks. 3984887789e44cdb16b042a35e8ec03983213e88ac6Jeff Sharkey */ 3994ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey public static boolean contains(File dir, File file) { 40050a05454795c93ac483f5cb6819e74cb17be1b5bJeff Sharkey if (dir == null || file == null) return false; 401d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey 40221de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey String dirPath = dir.getAbsolutePath(); 40321de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey String filePath = file.getAbsolutePath(); 4044ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey 4054ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey if (dirPath.equals(filePath)) { 4064ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey return true; 4074ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey } 4084ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey 4094ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey if (!dirPath.endsWith("/")) { 4104ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey dirPath += "/"; 4114ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey } 4124ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey return filePath.startsWith(dirPath); 4134ca728c064aeab644f6d044e0285eaa056818b8aJeff Sharkey } 4143a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey 41557dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey public static boolean deleteContents(File dir) { 4163a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey File[] files = dir.listFiles(); 41757dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey boolean success = true; 4183a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey if (files != null) { 4193a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey for (File file : files) { 4203a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey if (file.isDirectory()) { 42157dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey success &= deleteContents(file); 4223a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey } 42373767b9d607d99b3a027619b5c6b7f1a09b7673dJeff Sharkey if (!file.delete()) { 42473767b9d607d99b3a027619b5c6b7f1a09b7673dJeff Sharkey Log.w(TAG, "Failed to delete " + file); 42573767b9d607d99b3a027619b5c6b7f1a09b7673dJeff Sharkey success = false; 42673767b9d607d99b3a027619b5c6b7f1a09b7673dJeff Sharkey } 4273a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey } 4283a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey } 42957dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey return success; 4303a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey } 4313a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey 4320cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey private static boolean isValidExtFilenameChar(char c) { 4330cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey switch (c) { 4340cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey case '\0': 4350cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey case '/': 4360cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey return false; 4370cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey default: 4380cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey return true; 4390cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey } 4400cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey } 4410cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey 4423a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey /** 4430cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey * Check if given filename is valid for an ext4 filesystem. 4443a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey */ 4453a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey public static boolean isValidExtFilename(String name) { 4460cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey return (name != null) && name.equals(buildValidExtFilename(name)); 4470cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey } 4480cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey 4490cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey /** 4500cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey * Mutate the given filename to make it valid for an ext4 filesystem, 4510cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey * replacing any invalid characters with "_". 4520cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey */ 4530cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey public static String buildValidExtFilename(String name) { 4543a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) { 4550cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey return "(invalid)"; 4563a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey } 4570cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey final StringBuilder res = new StringBuilder(name.length()); 4583a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey for (int i = 0; i < name.length(); i++) { 4593a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey final char c = name.charAt(i); 4600cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey if (isValidExtFilenameChar(c)) { 4610cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey res.append(c); 4620cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey } else { 4630cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey res.append('_'); 4640cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey } 4650cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey } 4664f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey trimFilename(res, 255); 4670cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey return res.toString(); 4680cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey } 4690cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey 4700cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey private static boolean isValidFatFilenameChar(char c) { 4710cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey if ((0x00 <= c && c <= 0x1f)) { 4720cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey return false; 4730cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey } 4740cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey switch (c) { 4750cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey case '"': 4760cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey case '*': 4770cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey case '/': 4780cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey case ':': 4790cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey case '<': 4800cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey case '>': 4810cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey case '?': 4820cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey case '\\': 4830cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey case '|': 4840cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey case 0x7F: 4853a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey return false; 4860cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey default: 4870cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey return true; 4880cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey } 4890cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey } 4900cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey 4910cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey /** 4920cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey * Check if given filename is valid for a FAT filesystem. 4930cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey */ 4940cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey public static boolean isValidFatFilename(String name) { 4950cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey return (name != null) && name.equals(buildValidFatFilename(name)); 4960cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey } 4970cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey 4980cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey /** 4990cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey * Mutate the given filename to make it valid for a FAT filesystem, 5000cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey * replacing any invalid characters with "_". 5010cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey */ 5020cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey public static String buildValidFatFilename(String name) { 5030cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) { 5040cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey return "(invalid)"; 5050cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey } 5060cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey final StringBuilder res = new StringBuilder(name.length()); 5070cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey for (int i = 0; i < name.length(); i++) { 5080cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey final char c = name.charAt(i); 5090cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey if (isValidFatFilenameChar(c)) { 5100cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey res.append(c); 5110cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey } else { 5120cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey res.append('_'); 5133a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey } 5143a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey } 5154f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey // Even though vfat allows 255 UCS-2 chars, we might eventually write to 5164f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey // ext4 through a FUSE layer, so use that limit. 5174f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey trimFilename(res, 255); 5184f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey return res.toString(); 5194f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey } 5204f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey 5214f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey @VisibleForTesting 5224f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey public static String trimFilename(String str, int maxBytes) { 5234f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey final StringBuilder res = new StringBuilder(str); 5244f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey trimFilename(res, maxBytes); 5250cce5355b45d835f95a8918b8b803fd977d374e4Jeff Sharkey return res.toString(); 5263a44f3f1b446315ef894e01d2ab9b5388c2bd8c4Jeff Sharkey } 52757dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey 5284f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey private static void trimFilename(StringBuilder res, int maxBytes) { 5294f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey byte[] raw = res.toString().getBytes(StandardCharsets.UTF_8); 5304f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey if (raw.length > maxBytes) { 5314f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey maxBytes -= 3; 5324f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey while (raw.length > maxBytes) { 5334f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey res.deleteCharAt(res.length() / 2); 5344f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey raw = res.toString().getBytes(StandardCharsets.UTF_8); 5354f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey } 5364f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey res.insert(res.length() / 2, "..."); 5374f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey } 5384f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey } 5394f5e8b3ca489245005b76176ac6d28f5f184f3feJeff Sharkey 54057dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey public static String rewriteAfterRename(File beforeDir, File afterDir, String path) { 541d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey if (path == null) return null; 54257dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey final File result = rewriteAfterRename(beforeDir, afterDir, new File(path)); 54357dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey return (result != null) ? result.getAbsolutePath() : null; 54457dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey } 54557dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey 546d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey public static String[] rewriteAfterRename(File beforeDir, File afterDir, String[] paths) { 547d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey if (paths == null) return null; 548d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey final String[] result = new String[paths.length]; 549d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey for (int i = 0; i < paths.length; i++) { 550d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey result[i] = rewriteAfterRename(beforeDir, afterDir, paths[i]); 551d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey } 552d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey return result; 553d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey } 554d746057f2414cba2bdc69257cc5be8cb681bb592Jeff Sharkey 55557dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey /** 55657dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey * Given a path under the "before" directory, rewrite it to live under the 55757dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey * "after" directory. For example, {@code /before/foo/bar.txt} would become 55857dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey * {@code /after/foo/bar.txt}. 55957dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey */ 56057dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey public static File rewriteAfterRename(File beforeDir, File afterDir, File file) { 56141be35dd011dbd7ed3a9d08e8fe3eb85ea0e5b64Jeff Sharkey if (file == null || beforeDir == null || afterDir == null) return null; 56257dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey if (contains(beforeDir, file)) { 56357dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey final String splice = file.getAbsolutePath().substring( 56457dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey beforeDir.getAbsolutePath().length()); 56557dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey return new File(afterDir, splice); 56657dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey } 56757dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey return null; 56857dcf5b177b56195421535938544f32d8b591b42Jeff Sharkey } 56962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa 57062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa /** 57162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa * Generates a unique file name under the given parent directory. If the display name doesn't 57262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa * have an extension that matches the requested MIME type, the default extension for that MIME 57362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa * type is appended. If a file already exists, the name is appended with a numerical value to 57462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa * make it unique. 57562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa * 57662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa * For example, the display name 'example' with 'text/plain' MIME might produce 57762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa * 'example.txt' or 'example (1).txt', etc. 57862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa * 57962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa * @throws FileNotFoundException 58062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa */ 58162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa public static File buildUniqueFile(File parent, String mimeType, String displayName) 58262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa throws FileNotFoundException { 58362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa String name; 58462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa String ext; 58562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa 58662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa if (Document.MIME_TYPE_DIR.equals(mimeType)) { 58762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa name = displayName; 58862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa ext = null; 58962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa } else { 59062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa String mimeTypeFromExt; 59162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa 59262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa // Extract requested extension from display name 59362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa final int lastDot = displayName.lastIndexOf('.'); 59462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa if (lastDot >= 0) { 59562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa name = displayName.substring(0, lastDot); 59662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa ext = displayName.substring(lastDot + 1); 59762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa mimeTypeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension( 59862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa ext.toLowerCase()); 59962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa } else { 60062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa name = displayName; 60162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa ext = null; 60262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa mimeTypeFromExt = null; 60362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa } 60462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa 60562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa if (mimeTypeFromExt == null) { 60662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa mimeTypeFromExt = "application/octet-stream"; 60762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa } 60862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa 60962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa final String extFromMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType( 61062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa mimeType); 61162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa if (Objects.equals(mimeType, mimeTypeFromExt) || Objects.equals(ext, extFromMimeType)) { 61262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa // Extension maps back to requested MIME type; allow it 61362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa } else { 61462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa // No match; insist that create file matches requested MIME 61562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa name = displayName; 61662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa ext = extFromMimeType; 61762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa } 61862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa } 61962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa 62062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa File file = buildFile(parent, name, ext); 62162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa 62262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa // If conflicting file, try adding counter suffix 62362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa int n = 0; 62462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa while (file.exists()) { 62562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa if (n++ >= 32) { 62662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa throw new FileNotFoundException("Failed to create unique file"); 62762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa } 62862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa file = buildFile(parent, name + " (" + n + ")", ext); 62962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa } 63062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa 63162539a220c6810f66b63060326bd1668f7d6b029Ben Kwa return file; 63262539a220c6810f66b63060326bd1668f7d6b029Ben Kwa } 63362539a220c6810f66b63060326bd1668f7d6b029Ben Kwa 63462539a220c6810f66b63060326bd1668f7d6b029Ben Kwa private static File buildFile(File parent, String name, String ext) { 63562539a220c6810f66b63060326bd1668f7d6b029Ben Kwa if (TextUtils.isEmpty(ext)) { 63662539a220c6810f66b63060326bd1668f7d6b029Ben Kwa return new File(parent, name); 63762539a220c6810f66b63060326bd1668f7d6b029Ben Kwa } else { 63862539a220c6810f66b63060326bd1668f7d6b029Ben Kwa return new File(parent, name + "." + ext); 63962539a220c6810f66b63060326bd1668f7d6b029Ben Kwa } 64062539a220c6810f66b63060326bd1668f7d6b029Ben Kwa } 64185ced632680642fce680d141ddd10299ff849233Jeff Sharkey 64285ced632680642fce680d141ddd10299ff849233Jeff Sharkey public static @NonNull File[] listFilesOrEmpty(File dir) { 64385ced632680642fce680d141ddd10299ff849233Jeff Sharkey File[] res = dir.listFiles(); 64485ced632680642fce680d141ddd10299ff849233Jeff Sharkey if (res != null) { 64585ced632680642fce680d141ddd10299ff849233Jeff Sharkey return res; 64685ced632680642fce680d141ddd10299ff849233Jeff Sharkey } else { 64785ced632680642fce680d141ddd10299ff849233Jeff Sharkey return EMPTY; 64885ced632680642fce680d141ddd10299ff849233Jeff Sharkey } 64985ced632680642fce680d141ddd10299ff849233Jeff Sharkey } 65015447798a38d2b5acb1998731340255f4203f294Jeff Sharkey 65115447798a38d2b5acb1998731340255f4203f294Jeff Sharkey public static @Nullable File newFileOrNull(@Nullable String path) { 65215447798a38d2b5acb1998731340255f4203f294Jeff Sharkey return (path != null) ? new File(path) : null; 65315447798a38d2b5acb1998731340255f4203f294Jeff Sharkey } 6549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project} 655