18a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki/*
28a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * Copyright (C) 2012 The Android Open Source Project
38a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki *
48a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * Licensed under the Apache License, Version 2.0 (the "License");
58a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * you may not use this file except in compliance with the License.
68a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * You may obtain a copy of the License at
78a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki *
88a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki *      http://www.apache.org/licenses/LICENSE-2.0
98a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki *
108a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * Unless required by applicable law or agreed to in writing, software
118a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * distributed under the License is distributed on an "AS IS" BASIS,
128a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
138a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * See the License for the specific language governing permissions and
148a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * limitations under the License.
158a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki */
168a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
178a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukipackage com.android.providers.contacts.debug;
188a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
19623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onukiimport com.android.providers.contacts.util.Hex;
20623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
218a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport android.content.Context;
22623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onukiimport android.net.Uri;
238a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport android.util.Log;
248a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
258a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport java.io.File;
268a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport java.io.FileInputStream;
278a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport java.io.FileOutputStream;
288a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport java.io.IOException;
298a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport java.io.InputStream;
30623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onukiimport java.security.SecureRandom;
31a6ec38053a00fb399ca174931c149e3740c7420aMakoto Onukiimport java.util.zip.Deflater;
328a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport java.util.zip.ZipEntry;
338a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukiimport java.util.zip.ZipOutputStream;
348a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
358a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki/**
368a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki * Compress all files under the app data dir into a single zip file.
37623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki *
38623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki * Make sure not to output dump filenames anywhere, including logcat.
398a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki */
408a6e02add7c70666cdb506310c134af7d91c323cMakoto Onukipublic class DataExporter {
418a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    private static String TAG = "DataExporter";
428a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
438a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    public static final String ZIP_MIME_TYPE = "application/zip";
448a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
45623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    public static final String DUMP_FILE_DIRECTORY_NAME = "dumpedfiles";
46623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
47623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    public static final String OUT_FILE_SUFFIX = "-contacts-db.zip";
48c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng    public static final String VALID_FILE_NAME_REGEX = "[0-9A-Fa-f]+-contacts-db\\.zip";
49623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
508a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    /**
51623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki     * Compress all files under the app data dir into a single zip file, and return the content://
52623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki     * URI to the file, which can be read via {@link DumpFileProvider}.
538a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki     */
54623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    public static Uri exportData(Context context) throws IOException {
55623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        final String fileName = generateRandomName() + OUT_FILE_SUFFIX;
56623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        final File outFile = getOutputFile(context, fileName);
57623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
58623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        // Remove all existing ones.
59623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        removeDumpFiles(context);
608a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
61623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        Log.i(TAG, "Dump started...");
62623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
63623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        ensureOutputDirectory(context);
64dfff82913ea4201ecd983d40899e8459593f4495Paul Duffin
65dfff82913ea4201ecd983d40899e8459593f4495Paul Duffin        try (ZipOutputStream os = new ZipOutputStream(new FileOutputStream(outFile))) {
66dfff82913ea4201ecd983d40899e8459593f4495Paul Duffin            os.setLevel(Deflater.BEST_COMPRESSION);
67623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            addDirectory(context, os, context.getFilesDir().getParentFile(), "contacts-files");
688a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        }
69623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        Log.i(TAG, "Dump finished.");
70623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        return DumpFileProvider.AUTHORITY_URI.buildUpon().appendPath(fileName).build();
71623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
72623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
73623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    /** @return long random string for a file name */
74623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    private static String generateRandomName() {
75623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        final SecureRandom rng = new SecureRandom();
76623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        final byte[] random = new byte[256 / 8];
77623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        rng.nextBytes(random);
78623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
79623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        return Hex.encodeHex(random, true);
80623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
81623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
82c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng    public static void ensureValidFileName(String fileName) {
83c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng        // Do not allow queries to use relative paths to leave the root directory. Otherwise they
84c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng        // can gain access to other files such as the contacts database.
85c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng        if (fileName.contains("..")) {
86c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng            throw new IllegalArgumentException(".. path specifier not allowed. Bad file name: " +
87c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng                    fileName);
88c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng        }
89c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng        // White list dump files.
90c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng        if (!fileName.matches(VALID_FILE_NAME_REGEX)) {
91c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng            throw new IllegalArgumentException("Only " + VALID_FILE_NAME_REGEX +
92c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng                    " files are supported. Bad file name: " + fileName);
93c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng        }
94c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng    }
95c43a8d4c928b0d362339cd418486e2aa91769b70Chiao Cheng
96623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    private static File getOutputDirectory(Context context) {
97623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        return new File(context.getCacheDir(), DUMP_FILE_DIRECTORY_NAME);
98623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
99623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
100623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    private static void ensureOutputDirectory(Context context) {
101623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        final File directory = getOutputDirectory(context);
102623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        if (!directory.exists()) {
103623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            directory.mkdir();
104623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        }
105623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
106623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
107623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    public static File getOutputFile(Context context, String fileName) {
108623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        return new File(getOutputDirectory(context), fileName);
109623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
110623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
111623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    public static boolean dumpFileExists(Context context) {
112623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        return getOutputDirectory(context).exists();
113623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
114623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
115623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    public static void removeDumpFiles(Context context) {
116623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        removeFileOrDirectory(getOutputDirectory(context));
117623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    }
118623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
119623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    private static void removeFileOrDirectory(File file) {
120623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        if (!file.exists()) return;
121623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
122623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        if (file.isFile()) {
123623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            Log.i(TAG, "Removing " + file);
124623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            file.delete();
125623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            return;
126623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        }
127623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki
128623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        if (file.isDirectory()) {
129623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            for (File child : file.listFiles()) {
130623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                removeFileOrDirectory(child);
131623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            }
132623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            Log.i(TAG, "Removing " + file);
133623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            file.delete();
134623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        }
1358a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    }
1368a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
1378a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    /**
1388a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki     * Add all files under {@code current} to {@code os} zip stream
1398a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki     */
140623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki    private static void addDirectory(Context context, ZipOutputStream os, File current,
141623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki            String storedPath) throws IOException {
1428a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        for (File child : current.listFiles()) {
1438a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            final String childStoredPath = storedPath + "/" + child.getName();
1448a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
1458a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            if (child.isDirectory()) {
146623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                // Don't need the cache directory, which also contains the dump files.
147623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                if (child.equals(context.getCacheDir())) {
148623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                    continue;
149623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                }
150623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                // This check is redundant as the output directory should be in the cache dir,
151623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                // but just in case...
152623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                if (child.getName().equals(DUMP_FILE_DIRECTORY_NAME)) {
153623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                    continue;
154623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                }
155623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki                addDirectory(context, os, child, childStoredPath);
1568a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            } else if (child.isFile()) {
1578a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki                addFile(os, child, childStoredPath);
1588a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            } else {
1598a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki                // Shouldn't happen; skip.
1608a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            }
1618a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        }
1628a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    }
1638a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
1648a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    /**
1658a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki     * Add a single file {@code current} to {@code os} zip stream using the file name
1668a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki     * {@code storedPath}.
1678a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki     */
1688a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    private static void addFile(ZipOutputStream os, File current, String storedPath)
1698a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            throws IOException {
170623659ebf4875e63bf4fef1e0b00096e09121853Makoto Onuki        Log.i(TAG, "Adding " + current.getAbsolutePath() + " ...");
1718a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        final InputStream is = new FileInputStream(current);
1728a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        os.putNextEntry(new ZipEntry(storedPath));
1738a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki
1748a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        final byte[] buf = new byte[32 * 1024];
1758a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        int totalLen = 0;
1768a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        while (true) {
1778a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            int len = is.read(buf);
1788a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            if (len <= 0) {
1798a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki                break;
1808a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            }
1818a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            os.write(buf, 0, len);
1828a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki            totalLen += len;
1838a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        }
1848a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        os.closeEntry();
1858a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki        Log.i(TAG, "Added " + current.getAbsolutePath() + " as " + storedPath +
1868a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki                " (" + totalLen + " bytes)");
1878a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki    }
1888a6e02add7c70666cdb506310c134af7d91c323cMakoto Onuki}
189