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; 17 18 import android.content.Context; 19 import com.google.android.libraries.mobiledatadownload.Flags; 20 import com.google.android.libraries.mobiledatadownload.file.transforms.TransformProtos; 21 import com.google.android.libraries.mobiledatadownload.internal.logging.LogUtil; 22 import com.google.android.libraries.mobiledatadownload.internal.util.FileGroupUtil; 23 import com.google.mobiledatadownload.TransformProto.Transforms; 24 import com.google.mobiledatadownload.internal.MetadataProto.DataFile; 25 import com.google.mobiledatadownload.internal.MetadataProto.DataFile.ChecksumType; 26 import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal; 27 import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal.AllowedReaders; 28 import com.google.mobiledatadownload.internal.MetadataProto.DeltaFile; 29 import com.google.mobiledatadownload.internal.MetadataProto.DeltaFile.DiffDecoder; 30 import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions.DeviceNetworkPolicy; 31 32 /** DataFileGroupValidator - validates the passed in DataFileGroup */ 33 public class DataFileGroupValidator { 34 35 private static final String TAG = "DataFileGroupValidator"; 36 37 /** 38 * Checks if the data file group that we received is a valid group. A group is valid if all the 39 * data required to download it is present. If any field that is not required is set, it will be 40 * ignored. 41 * 42 * <p>This is just a sanity check. For example, it doesn't check if the file is present at the 43 * given location. 44 * 45 * @param dataFileGroup The data file group on which sanity check needs to be done. 46 * @return true if the group is valid. 47 */ 48 // TODO(b/124072754): Change to package private once all code is refactored. isValidGroup( DataFileGroupInternal dataFileGroup, Context context, Flags flags)49 public static boolean isValidGroup( 50 DataFileGroupInternal dataFileGroup, Context context, Flags flags) { 51 // Check if the group name is empty. 52 if (dataFileGroup.getGroupName().isEmpty()) { 53 LogUtil.e("%s Group name missing in added group", TAG); 54 return false; 55 } 56 57 if (dataFileGroup.getGroupName().contains(MddConstants.SPLIT_CHAR)) { 58 LogUtil.e("%s Group name = %s contains '|'", TAG, dataFileGroup.getGroupName()); 59 return false; 60 } 61 62 if (dataFileGroup.getOwnerPackage().contains(MddConstants.SPLIT_CHAR)) { 63 LogUtil.e("%s Owner package = %s contains '|'", TAG, dataFileGroup.getOwnerPackage()); 64 return false; 65 } 66 67 // Check if any file is missing any of the required fields. 68 for (DataFile dataFile : dataFileGroup.getFileList()) { 69 if (dataFile.getFileId().isEmpty() 70 || dataFile.getFileId().contains(MddConstants.SPLIT_CHAR) 71 || !isValidDataFile(dataFile)) { 72 LogUtil.e( 73 "%s File details missing in added group = %s, file id = %s", 74 TAG, dataFileGroup.getGroupName(), dataFile.getFileId()); 75 return false; 76 } 77 if (!hasValidTransforms(dataFileGroup, dataFile, flags)) { 78 return false; 79 } 80 if (!hasValidDeltaFiles(dataFileGroup.getGroupName(), dataFile)) { 81 return false; 82 } 83 // Check if sideloaded files are present and if sideloading is enabled 84 if (FileGroupUtil.isSideloadedFile(dataFile) && !flags.enableSideloading()) { 85 LogUtil.e( 86 "%s File detected as sideloaded, but sideloading is not enabled. group = %s, file id =" 87 + " %s, file url = %s", 88 TAG, dataFileGroup.getGroupName(), dataFile.getFileId(), dataFile.getUrlToDownload()); 89 return false; 90 } 91 } 92 93 // Check if a file id is repeated. 94 for (int i = 0; i < dataFileGroup.getFileCount(); i++) { 95 for (int j = i + 1; j < dataFileGroup.getFileCount(); j++) { 96 if (dataFileGroup.getFile(i).getFileId().equals(dataFileGroup.getFile(j).getFileId())) { 97 LogUtil.e( 98 "%s Repeated file id in added group = %s, file id = %s", 99 TAG, dataFileGroup.getGroupName(), dataFileGroup.getFile(i).getFileId()); 100 return false; 101 } 102 } 103 } 104 105 if (dataFileGroup 106 .getDownloadConditions() 107 .getDeviceNetworkPolicy() 108 .equals(DeviceNetworkPolicy.DOWNLOAD_FIRST_ON_WIFI_THEN_ON_ANY_NETWORK) 109 && dataFileGroup.getDownloadConditions().getDownloadFirstOnWifiPeriodSecs() <= 0) { 110 LogUtil.e( 111 "%s For DOWNLOAD_FIRST_ON_WIFI_THEN_ON_ANY_NETWORK policy, " 112 + "the download_first_on_wifi_period_secs must be > 0", 113 TAG); 114 return false; 115 } 116 117 if (!Migrations.isMigratedToNewFileKey(context) 118 && dataFileGroup.getAllowedReadersEnum().equals(AllowedReaders.ALL_APPS)) { 119 LogUtil.e( 120 "%s For AllowedReaders ALL_APPS policy, the device should be migrated to new key", TAG); 121 return false; 122 } 123 124 return true; 125 } 126 hasValidTransforms( DataFileGroupInternal dataFileGroup, DataFile dataFile, Flags flags)127 private static boolean hasValidTransforms( 128 DataFileGroupInternal dataFileGroup, DataFile dataFile, Flags flags) { 129 // Verify for Download transforms 130 if (dataFile.hasDownloadTransforms()) { 131 if (!isValidTransforms(dataFile.getDownloadTransforms())) { 132 return false; 133 } 134 if (!hasValidZipDownloadTransform(dataFileGroup.getGroupName(), dataFile, flags)) { 135 return false; 136 } 137 if (dataFile.getChecksumType() != ChecksumType.NONE 138 && !dataFile.hasDownloadedFileChecksum()) { 139 LogUtil.e( 140 "Download checksum must be provided. Group = %s, file id = %s", 141 dataFileGroup.getGroupName(), dataFile.getFileId()); 142 return false; 143 } 144 } 145 // Verify for Read transforms 146 if (dataFile.hasReadTransforms() && !isValidTransforms(dataFile.getReadTransforms())) { 147 return false; 148 } 149 return true; 150 } 151 hasValidZipDownloadTransform( String groupName, DataFile dataFile, Flags flags)152 private static boolean hasValidZipDownloadTransform( 153 String groupName, DataFile dataFile, Flags flags) { 154 if (FileGroupUtil.hasZipDownloadTransform(dataFile)) { 155 if (!flags.enableZipFolder()) { 156 LogUtil.e( 157 "Feature enableZipFolder is not enabled. Group = %s, file id = %s", 158 groupName, dataFile.getFileId()); 159 return false; 160 } 161 if (dataFile.getDownloadTransforms().getTransformCount() > 1) { 162 LogUtil.e( 163 "Download zip folder transform cannot not be applied with other transforms. Group =" 164 + " %s, file id = %s", 165 groupName, dataFile.getFileId()); 166 return false; 167 } 168 if (!"*".equals(dataFile.getDownloadTransforms().getTransform(0).getZip().getTarget())) { 169 LogUtil.e( 170 "Download zip folder transform can only have * as target. Group = %s, file id = %s", 171 groupName, dataFile.getFileId()); 172 return false; 173 } 174 } 175 return true; 176 } 177 isValidTransforms(Transforms transforms)178 private static boolean isValidTransforms(Transforms transforms) { 179 try { 180 TransformProtos.toEncodedFragment(transforms); 181 return true; 182 } catch (IllegalArgumentException illegalArgumentException) { 183 LogUtil.e(illegalArgumentException, "Invalid transform specification"); 184 return false; 185 } 186 } 187 hasValidDeltaFiles(String groupName, DataFile dataFile)188 private static boolean hasValidDeltaFiles(String groupName, DataFile dataFile) { 189 for (DeltaFile deltaFile : dataFile.getDeltaFileList()) { 190 if (!isValidDeltaFile(deltaFile)) { 191 LogUtil.e( 192 "%s Delta File of Datafile details missing in added group = %s, file id = %s" 193 + ", delta file UrlToDownload = %s.", 194 TAG, groupName, dataFile.getFileId(), deltaFile.getUrlToDownload()); 195 return false; 196 } 197 } 198 return true; 199 } 200 isValidDataFile(DataFile dataFile)201 private static boolean isValidDataFile(DataFile dataFile) { 202 // When a data file has zip transform, downloaded file checksum is used for identifying the data 203 // file; otherwise, checksum is used. 204 boolean hasNonEmptyChecksum; 205 if (FileGroupUtil.hasZipDownloadTransform(dataFile)) { 206 hasNonEmptyChecksum = 207 dataFile.hasDownloadedFileChecksum() && !dataFile.getDownloadedFileChecksum().isEmpty(); 208 } else { 209 hasNonEmptyChecksum = dataFile.hasChecksum() && !dataFile.getChecksum().isEmpty(); 210 } 211 212 boolean validChecksum; 213 switch (dataFile.getChecksumType()) { 214 // Default stands for SHA1. 215 case DEFAULT: 216 validChecksum = hasNonEmptyChecksum; 217 break; 218 case NONE: 219 validChecksum = !hasNonEmptyChecksum; 220 break; 221 default: 222 validChecksum = false; 223 } 224 225 // File checksum is not needed for zip folder download transforms. 226 validChecksum |= FileGroupUtil.hasZipDownloadTransform(dataFile) && !hasNonEmptyChecksum; 227 228 boolean validAndroidSharingConfig = 229 dataFile.getAndroidSharingChecksumType() == DataFile.AndroidSharingChecksumType.SHA2_256 230 ? !dataFile.getAndroidSharingChecksum().isEmpty() 231 : true; 232 233 return !dataFile.getUrlToDownload().isEmpty() 234 && !dataFile.getUrlToDownload().contains(MddConstants.SPLIT_CHAR) 235 && dataFile.getByteSize() >= 0 236 && validChecksum 237 && validAndroidSharingConfig 238 && !FileGroupUtil.getFileChecksum(dataFile).contains(MddConstants.SPLIT_CHAR); 239 } 240 isValidDeltaFile(DeltaFile deltaFile)241 private static boolean isValidDeltaFile(DeltaFile deltaFile) { 242 return !deltaFile.getUrlToDownload().isEmpty() 243 && !deltaFile.getUrlToDownload().contains(MddConstants.SPLIT_CHAR) 244 && deltaFile.hasByteSize() 245 && deltaFile.getByteSize() >= 0 246 && !deltaFile.getChecksum().isEmpty() 247 && !deltaFile.getChecksum().contains(MddConstants.SPLIT_CHAR) 248 && deltaFile.hasDiffDecoder() 249 && !deltaFile.getDiffDecoder().equals(DiffDecoder.UNSPECIFIED) 250 && deltaFile.hasBaseFile() 251 && !deltaFile.getBaseFile().getChecksum().isEmpty() 252 && !deltaFile.getBaseFile().getChecksum().contains(MddConstants.SPLIT_CHAR); 253 } 254 } 255