• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
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 package com.google.android.libraries.mobiledatadownload.internal.downloader;
17 
18 import android.net.Uri;
19 import com.google.android.libraries.mobiledatadownload.file.OpenContext;
20 import com.google.android.libraries.mobiledatadownload.file.Opener;
21 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
22 import com.google.android.libraries.mobiledatadownload.file.openers.ReadStreamOpener;
23 import com.google.android.libraries.mobiledatadownload.file.openers.WriteStreamOpener;
24 import com.google.common.io.ByteStreams;
25 import java.io.IOException;
26 import java.io.OutputStream;
27 import java.util.zip.ZipEntry;
28 import java.util.zip.ZipException;
29 import java.util.zip.ZipInputStream;
30 
31 /**
32  * An opener takes in an output folder URI and expands all resources in the zip input stream to the
33  * folder.
34  */
35 public final class ZipFolderOpener implements Opener<Void> {
36 
37   private final Uri targetFolderUri;
38   private final SaferZipUtils zipUtils;
39 
ZipFolderOpener(Uri targetFolderUri)40   private ZipFolderOpener(Uri targetFolderUri) {
41     this.targetFolderUri = targetFolderUri;
42     this.zipUtils = new SaferZipUtils() {};
43   }
44 
create(Uri targetFolderUri)45   public static ZipFolderOpener create(Uri targetFolderUri) {
46     return new ZipFolderOpener(targetFolderUri);
47   }
48 
49   @Override
open(OpenContext openContext)50   public Void open(OpenContext openContext) throws IOException {
51     SynchronousFileStorage fileStorage = openContext.storage();
52     try (ZipInputStream zipInputStream =
53         new ZipInputStream(ReadStreamOpener.create().withBufferedIo().open(openContext))) {
54       // Iterate all entries and write to target URI one by one
55       ZipEntry zipEntry;
56       while ((zipEntry = zipInputStream.getNextEntry()) != null) {
57         String path = zipUtils.getValidatedName(zipEntry);
58         Uri uri = targetFolderUri.buildUpon().appendPath(path).build();
59         if (zipEntry.isDirectory()) {
60           fileStorage.createDirectory(uri);
61         } else {
62           try (OutputStream out = fileStorage.open(uri, WriteStreamOpener.create())) {
63             ByteStreams.copy(zipInputStream, out);
64           }
65         }
66       }
67     } catch (IOException ioe) {
68       // Cleanup the target directory if any error occurred.
69       fileStorage.deleteRecursively(targetFolderUri);
70       throw ioe;
71     }
72     return null;
73   }
74 
75   /** Utilities for safely accessing ZipEntry APIs. */
76   private interface SaferZipUtils {
77     /**
78      * Return the name of a ZipEntry after verifying that it does not exploit any path traversal
79      * attacks.
80      *
81      * @throws ZipException if {@code zipEntry} contains any possible path traversal characters.
82      */
getValidatedName(ZipEntry entry)83     default String getValidatedName(ZipEntry entry) throws ZipException {
84       return entry.getName();
85     }
86   }
87 }
88