1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.webkit;
18
19import android.content.Context;
20import android.net.http.Headers;
21import android.util.Log;
22
23import java.io.File;
24import java.io.FileInputStream;
25import java.io.FileNotFoundException;
26import java.io.IOException;
27import java.io.InputStream;
28import java.io.OutputStream;
29import java.util.Map;
30
31
32/**
33 * Manages the HTTP cache used by an application's {@link WebView} instances.
34 * @deprecated Access to the HTTP cache will be removed in a future release.
35 * @hide Since {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
36 */
37// The class CacheManager provides the persistent cache of content that is
38// received over the network. The component handles parsing of HTTP headers and
39// utilizes the relevant cache headers to determine if the content should be
40// stored and if so, how long it is valid for. Network requests are provided to
41// this component and if they can not be resolved by the cache, the HTTP headers
42// are attached, as appropriate, to the request for revalidation of content. The
43// class also manages the cache size.
44//
45// CacheManager may only be used if your activity contains a WebView.
46@Deprecated
47public final class CacheManager {
48
49    private static final String LOGTAG = "cache";
50
51    static final String HEADER_KEY_IFMODIFIEDSINCE = "if-modified-since";
52    static final String HEADER_KEY_IFNONEMATCH = "if-none-match";
53
54    private static File mBaseDir;
55
56    /**
57     * Represents a resource stored in the HTTP cache. Instances of this class
58     * can be obtained by calling
59     * {@link CacheManager#getCacheFile CacheManager.getCacheFile(String, Map<String, String>))}.
60     *
61     * @deprecated Access to the HTTP cache will be removed in a future release.
62     */
63    @Deprecated
64    public static class CacheResult {
65        // these fields are saved to the database
66        int httpStatusCode;
67        long contentLength;
68        long expires;
69        String expiresString;
70        String localPath;
71        String lastModified;
72        String etag;
73        String mimeType;
74        String location;
75        String encoding;
76        String contentdisposition;
77        String crossDomain;
78
79        // these fields are NOT saved to the database
80        InputStream inStream;
81        OutputStream outStream;
82        File outFile;
83
84        /**
85         * Gets the status code of this cache entry.
86         *
87         * @return the status code of this cache entry
88         */
89        public int getHttpStatusCode() {
90            return httpStatusCode;
91        }
92
93        /**
94         * Gets the content length of this cache entry.
95         *
96         * @return the content length of this cache entry
97         */
98        public long getContentLength() {
99            return contentLength;
100        }
101
102        /**
103         * Gets the path of the file used to store the content of this cache
104         * entry, relative to the base directory of the cache. See
105         * {@link CacheManager#getCacheFileBaseDir CacheManager.getCacheFileBaseDir()}.
106         *
107         * @return the path of the file used to store this cache entry
108         */
109        public String getLocalPath() {
110            return localPath;
111        }
112
113        /**
114         * Gets the expiry date of this cache entry, expressed in milliseconds
115         * since midnight, January 1, 1970 UTC.
116         *
117         * @return the expiry date of this cache entry
118         */
119        public long getExpires() {
120            return expires;
121        }
122
123        /**
124         * Gets the expiry date of this cache entry, expressed as a string.
125         *
126         * @return the expiry date of this cache entry
127         *
128         */
129        public String getExpiresString() {
130            return expiresString;
131        }
132
133        /**
134         * Gets the date at which this cache entry was last modified, expressed
135         * as a string.
136         *
137         * @return the date at which this cache entry was last modified
138         */
139        public String getLastModified() {
140            return lastModified;
141        }
142
143        /**
144         * Gets the entity tag of this cache entry.
145         *
146         * @return the entity tag of this cache entry
147         */
148        public String getETag() {
149            return etag;
150        }
151
152        /**
153         * Gets the MIME type of this cache entry.
154         *
155         * @return the MIME type of this cache entry
156         */
157        public String getMimeType() {
158            return mimeType;
159        }
160
161        /**
162         * Gets the value of the HTTP 'Location' header with which this cache
163         * entry was received.
164         *
165         * @return the HTTP 'Location' header for this cache entry
166         */
167        public String getLocation() {
168            return location;
169        }
170
171        /**
172         * Gets the encoding of this cache entry.
173         *
174         * @return the encoding of this cache entry
175         */
176        public String getEncoding() {
177            return encoding;
178        }
179
180        /**
181         * Gets the value of the HTTP 'Content-Disposition' header with which
182         * this cache entry was received.
183         *
184         * @return the HTTP 'Content-Disposition' header for this cache entry
185         *
186         */
187        public String getContentDisposition() {
188            return contentdisposition;
189        }
190
191        /**
192         * Gets the input stream to the content of this cache entry, to allow
193         * content to be read. See
194         * {@link CacheManager#getCacheFile CacheManager.getCacheFile(String, Map<String, String>)}.
195         *
196         * @return an input stream to the content of this cache entry
197         */
198        public InputStream getInputStream() {
199            return inStream;
200        }
201
202        /**
203         * Gets an output stream to the content of this cache entry, to allow
204         * content to be written. See
205         * {@link CacheManager#saveCacheFile CacheManager.saveCacheFile(String, CacheResult)}.
206         *
207         * @return an output stream to the content of this cache entry
208         */
209        // Note that this is always null for objects returned by getCacheFile()!
210        public OutputStream getOutputStream() {
211            return outStream;
212        }
213
214
215        /**
216         * Sets an input stream to the content of this cache entry.
217         *
218         * @param stream an input stream to the content of this cache entry
219         */
220        public void setInputStream(InputStream stream) {
221            this.inStream = stream;
222        }
223
224        /**
225         * Sets the encoding of this cache entry.
226         *
227         * @param encoding the encoding of this cache entry
228         */
229        public void setEncoding(String encoding) {
230            this.encoding = encoding;
231        }
232
233        /**
234         * @hide
235         */
236        public void setContentLength(long contentLength) {
237            this.contentLength = contentLength;
238        }
239    }
240
241    /**
242     * Initializes the HTTP cache. This method must be called before any
243     * CacheManager methods are used. Note that this is called automatically
244     * when a {@link WebView} is created.
245     *
246     * @param context the application context
247     */
248    static void init(Context context) {
249        // This isn't actually where the real cache lives, but where we put files for the
250        // purpose of getCacheFile().
251        mBaseDir = new File(context.getCacheDir(), "webviewCacheChromiumStaging");
252        if (!mBaseDir.exists()) {
253            mBaseDir.mkdirs();
254        }
255    }
256
257    /**
258     * Gets the base directory in which the files used to store the contents of
259     * cache entries are placed. See
260     * {@link CacheManager.CacheResult#getLocalPath CacheManager.CacheResult.getLocalPath()}.
261     *
262     * @return the base directory of the cache
263     * @deprecated Access to the HTTP cache will be removed in a future release.
264     */
265    @Deprecated
266    public static File getCacheFileBaseDir() {
267        return mBaseDir;
268    }
269
270    /**
271     * Gets whether the HTTP cache is disabled.
272     *
273     * @return true if the HTTP cache is disabled
274     * @deprecated Access to the HTTP cache will be removed in a future release.
275     */
276    @Deprecated
277    public static boolean cacheDisabled() {
278        return false;
279    }
280
281    /**
282     * Starts a cache transaction. Returns true if this is the only running
283     * transaction. Otherwise, this transaction is nested inside currently
284     * running transactions and false is returned.
285     *
286     * @return true if this is the only running transaction
287     * @deprecated This method no longer has any effect and always returns false.
288     */
289    @Deprecated
290    public static boolean startCacheTransaction() {
291        return false;
292    }
293
294    /**
295     * Ends the innermost cache transaction and returns whether this was the
296     * only running transaction.
297     *
298     * @return true if this was the only running transaction
299     * @deprecated This method no longer has any effect and always returns false.
300     */
301    @Deprecated
302    public static boolean endCacheTransaction() {
303        return false;
304    }
305
306    /**
307     * Gets the cache entry for the specified URL, or null if none is found.
308     * If a non-null value is provided for the HTTP headers map, and the cache
309     * entry needs validation, appropriate headers will be added to the map.
310     * The input stream of the CacheEntry object should be closed by the caller
311     * when access to the underlying file is no longer required.
312     *
313     * @param url the URL for which a cache entry is requested
314     * @param headers a map from HTTP header name to value, to be populated
315     *                for the returned cache entry
316     * @return the cache entry for the specified URL
317     * @deprecated Access to the HTTP cache will be removed in a future release.
318     */
319    @Deprecated
320    public static CacheResult getCacheFile(String url,
321            Map<String, String> headers) {
322        return getCacheFile(url, 0, headers);
323    }
324
325    static CacheResult getCacheFile(String url, long postIdentifier,
326            Map<String, String> headers) {
327        CacheResult result = nativeGetCacheResult(url);
328        if (result == null) {
329            return null;
330        }
331        // A temporary local file will have been created native side and localPath set
332        // appropriately.
333        File src = new File(mBaseDir, result.localPath);
334        try {
335            // Open the file here so that even if it is deleted, the content
336            // is still readable by the caller until close() is called.
337            result.inStream = new FileInputStream(src);
338        } catch (FileNotFoundException e) {
339            Log.v(LOGTAG, "getCacheFile(): Failed to open file: " + e);
340            // TODO: The files in the cache directory can be removed by the
341            // system. If it is gone, what should we do?
342            return null;
343        }
344
345        // A null value for headers is used by CACHE_MODE_CACHE_ONLY to imply
346        // that we should provide the cache result even if it is expired.
347        // Note that a negative expires value means a time in the far future.
348        if (headers != null && result.expires >= 0
349                && result.expires <= System.currentTimeMillis()) {
350            if (result.lastModified == null && result.etag == null) {
351                return null;
352            }
353            // Return HEADER_KEY_IFNONEMATCH or HEADER_KEY_IFMODIFIEDSINCE
354            // for requesting validation.
355            if (result.etag != null) {
356                headers.put(HEADER_KEY_IFNONEMATCH, result.etag);
357            }
358            if (result.lastModified != null) {
359                headers.put(HEADER_KEY_IFMODIFIEDSINCE, result.lastModified);
360            }
361        }
362
363        if (DebugFlags.CACHE_MANAGER) {
364            Log.v(LOGTAG, "getCacheFile for url " + url);
365        }
366
367        return result;
368    }
369
370    /**
371     * Given a URL and its full headers, gets a CacheResult if a local cache
372     * can be stored. Otherwise returns null. The mimetype is passed in so that
373     * the function can use the mimetype that will be passed to WebCore which
374     * could be different from the mimetype defined in the headers.
375     * forceCache is for out-of-package callers to force creation of a
376     * CacheResult, and is used to supply surrogate responses for URL
377     * interception.
378     *
379     * @return a CacheResult for a given URL
380     */
381    static CacheResult createCacheFile(String url, int statusCode,
382            Headers headers, String mimeType, boolean forceCache) {
383        // This method is public but hidden. We break functionality.
384        return null;
385    }
386
387    /**
388     * Adds a cache entry to the HTTP cache for the specicifed URL. Also closes
389     * the cache entry's output stream.
390     *
391     * @param url the URL for which the cache entry should be added
392     * @param cacheResult the cache entry to add
393     * @deprecated Access to the HTTP cache will be removed in a future release.
394     */
395    @Deprecated
396    public static void saveCacheFile(String url, CacheResult cacheResult) {
397        saveCacheFile(url, 0, cacheResult);
398    }
399
400    static void saveCacheFile(String url, long postIdentifier,
401            CacheResult cacheRet) {
402        try {
403            cacheRet.outStream.close();
404        } catch (IOException e) {
405            return;
406        }
407
408        // This method is exposed in the public API but the API provides no
409        // way to obtain a new CacheResult object with a non-null output
410        // stream ...
411        // - CacheResult objects returned by getCacheFile() have a null
412        //   output stream.
413        // - new CacheResult objects have a null output stream and no
414        //   setter is provided.
415        // Since this method throws a null pointer exception in this case,
416        // it is effectively useless from the point of view of the public
417        // API.
418        //
419        // With the Chromium HTTP stack we continue to throw the same
420        // exception for 'backwards compatibility' with the Android HTTP
421        // stack.
422        //
423        // This method is not used from within this package, and for public API
424        // use, we should already have thrown an exception above.
425        assert false;
426    }
427
428    /**
429     * Removes all cache files.
430     *
431     * @return whether the removal succeeded
432     */
433    static boolean removeAllCacheFiles() {
434        // delete cache files in a separate thread to not block UI.
435        final Runnable clearCache = new Runnable() {
436            public void run() {
437                // delete all cache files
438                try {
439                    String[] files = mBaseDir.list();
440                    // if mBaseDir doesn't exist, files can be null.
441                    if (files != null) {
442                        for (int i = 0; i < files.length; i++) {
443                            File f = new File(mBaseDir, files[i]);
444                            if (!f.delete()) {
445                                Log.e(LOGTAG, f.getPath() + " delete failed.");
446                            }
447                        }
448                    }
449                } catch (SecurityException e) {
450                    // Ignore SecurityExceptions.
451                }
452            }
453        };
454        new Thread(clearCache).start();
455        return true;
456    }
457
458    private static native CacheResult nativeGetCacheResult(String url);
459}
460