1 /*
2  * Copyright (C) 2018 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 
17 package com.android.car.systemupdater;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.util.Log;
22 
23 import com.android.internal.util.Preconditions;
24 
25 import java.io.BufferedReader;
26 import java.io.File;
27 import java.io.IOException;
28 import java.io.InputStreamReader;
29 import java.util.Arrays;
30 import java.util.Enumeration;
31 import java.util.Locale;
32 import java.util.zip.ZipEntry;
33 import java.util.zip.ZipFile;
34 
35 /** Parse an A/B update zip file. */
36 class UpdateParser {
37 
38     private static final String TAG = "UpdateLayoutFragment";
39     private static final String PAYLOAD_BIN_FILE = "payload.bin";
40     private static final String PAYLOAD_PROPERTIES = "payload_properties.txt";
41     private static final String FILE_URL_PREFIX = "file://";
42     private static final int ZIP_FILE_HEADER = 30;
43 
UpdateParser()44     private UpdateParser() {
45     }
46 
47     /**
48      * Parse a zip file containing a system update and return a non null ParsedUpdate.
49      */
50     @Nullable
parse(@onNull File file)51     static ParsedUpdate parse(@NonNull File file) throws IOException {
52         Preconditions.checkNotNull(file);
53 
54         long payloadOffset = 0;
55         long payloadSize = 0;
56         boolean payloadFound = false;
57         String[] props = null;
58 
59         try (ZipFile zipFile = new ZipFile(file)) {
60             Enumeration<? extends ZipEntry> entries = zipFile.entries();
61             while (entries.hasMoreElements()) {
62                 ZipEntry entry = entries.nextElement();
63                 long fileSize = entry.getCompressedSize();
64                 if (!payloadFound) {
65                     payloadOffset += ZIP_FILE_HEADER + entry.getName().length();
66                     if (entry.getExtra() != null) {
67                         payloadOffset += entry.getExtra().length;
68                     }
69                 }
70 
71                 if (entry.isDirectory()) {
72                     continue;
73                 } else if (entry.getName().equals(PAYLOAD_BIN_FILE)) {
74                     payloadSize = fileSize;
75                     payloadFound = true;
76                 } else if (entry.getName().equals(PAYLOAD_PROPERTIES)) {
77                     try (BufferedReader buffer = new BufferedReader(
78                             new InputStreamReader(zipFile.getInputStream(entry)))) {
79                         props = buffer.lines().toArray(String[]::new);
80                     }
81                 }
82                 if (!payloadFound) {
83                     payloadOffset += fileSize;
84                 }
85 
86                 if (Log.isLoggable(TAG, Log.DEBUG)) {
87                     Log.d(TAG, String.format("Entry %s", entry.getName()));
88                 }
89             }
90         }
91         return new ParsedUpdate(file, payloadOffset, payloadSize, props);
92     }
93 
94     /** Information parsed from an update file. */
95     static class ParsedUpdate {
96         final String mUrl;
97         final long mOffset;
98         final long mSize;
99         final String[] mProps;
100 
ParsedUpdate(File file, long offset, long size, String[] props)101         ParsedUpdate(File file, long offset, long size, String[] props) {
102             mUrl = FILE_URL_PREFIX + file.getAbsolutePath();
103             mOffset = offset;
104             mSize = size;
105             mProps = props;
106         }
107 
108         /** Verify the update information is correct. */
isValid()109         boolean isValid() {
110             return mOffset >= 0 && mSize > 0 && mProps != null;
111         }
112 
113         @Override
toString()114         public String toString() {
115             return String.format(Locale.getDefault(),
116                     "ParsedUpdate: URL=%s, offset=%d, size=%s, props=%s",
117                     mUrl, mOffset, mSize, Arrays.toString(mProps));
118         }
119     }
120 }
121