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.file.transforms; 17 18 import static com.google.android.libraries.mobiledatadownload.file.common.internal.Charsets.UTF_8; 19 20 import android.util.Base64; 21 import com.google.android.libraries.mobiledatadownload.file.common.internal.LiteTransformFragments; 22 import com.google.common.base.Joiner; 23 import com.google.common.base.Preconditions; 24 import com.google.common.collect.ImmutableList; 25 import com.google.mobiledatadownload.TransformProto; 26 import java.io.UnsupportedEncodingException; 27 import java.net.URLEncoder; 28 29 /** Utility to convert Transform proto into URI fragment. */ 30 public final class TransformProtos { 31 32 public static final TransformProto.Transform DEFAULT_COMPRESS_SPEC = 33 TransformProto.Transform.newBuilder() 34 .setCompress(TransformProto.CompressTransform.getDefaultInstance()) 35 .build(); 36 37 public static final TransformProto.Transform DEFAULT_ENCRYPT_SPEC = 38 TransformProto.Transform.newBuilder() 39 .setEncrypt(TransformProto.EncryptTransform.getDefaultInstance()) 40 .build(); 41 42 public static final TransformProto.Transform DEFAULT_INTEGRITY_SPEC = 43 TransformProto.Transform.newBuilder() 44 .setIntegrity(TransformProto.IntegrityTransform.getDefaultInstance()) 45 .build(); 46 encryptTransformSpecWithKey(byte[] key)47 public static TransformProto.Transform encryptTransformSpecWithKey(byte[] key) { 48 return encryptTransformSpecWithBase64Key(Base64.encodeToString(key, Base64.NO_WRAP)); 49 } 50 encryptTransformSpecWithBase64Key(String base64Key)51 public static TransformProto.Transform encryptTransformSpecWithBase64Key(String base64Key) { 52 return TransformProto.Transform.newBuilder() 53 .setEncrypt(TransformProto.EncryptTransform.newBuilder().setAesGcmKeyBase64(base64Key)) 54 .build(); 55 } 56 integrityTransformSpecWithSha256(String sha256)57 public static TransformProto.Transform integrityTransformSpecWithSha256(String sha256) { 58 return TransformProto.Transform.newBuilder() 59 .setIntegrity(TransformProto.IntegrityTransform.newBuilder().setSha256(sha256)) 60 .build(); 61 } 62 zipTransformSpecWithTarget(String target)63 public static TransformProto.Transform zipTransformSpecWithTarget(String target) { 64 return TransformProto.Transform.newBuilder() 65 .setZip(TransformProto.ZipTransform.newBuilder().setTarget(target)) 66 .build(); 67 } 68 69 /** 70 * Translate from proto representation to an encoded fragment, which is suitable for appending to 71 * a URI. 72 * 73 * <p>To build an {@link android.net.Uri} with such a fragment: <code> 74 * Fragment fragment = TransformProtos.toEncodedFragment(transformProto); 75 * Uri uri = origUri.buildUpon().encodedFragment(fragment.toString()).build(); 76 * </code> 77 * 78 * @param transformsProto The proto to translate to a fragment. 79 * @return The encoded fragment representation of the proto. 80 */ toEncodedFragment(TransformProto.Transforms transformsProto)81 public static String toEncodedFragment(TransformProto.Transforms transformsProto) { 82 ImmutableList.Builder<String> encodedSpecs = ImmutableList.builder(); 83 for (TransformProto.Transform transformProto : transformsProto.getTransformList()) { 84 encodedSpecs.add(toEncodedSpec(transformProto)); 85 } 86 return LiteTransformFragments.joinTransformSpecs(encodedSpecs.build()); 87 } 88 89 /** 90 * Translate proto representation to an encoded transform spec. 91 * 92 * @param transformProto The proto to translate to a spec. 93 * @return An encoded spec suitable for joining with other specs to form fragment. 94 */ toEncodedSpec(TransformProto.Transform transformProto)95 public static String toEncodedSpec(TransformProto.Transform transformProto) { 96 String encodedSpec; 97 switch (transformProto.getTransformCase()) { 98 case COMPRESS: 99 encodedSpec = "compress"; 100 break; 101 case ENCRYPT: 102 TransformProto.EncryptTransform encrypt = transformProto.getEncrypt(); 103 if (encrypt.hasAesGcmKeyBase64()) { 104 encodedSpec = "encrypt(aes_gcm_key=" + urlEncode(encrypt.getAesGcmKeyBase64()) + ")"; 105 } else { 106 encodedSpec = "encrypt"; 107 } 108 break; 109 case INTEGRITY: 110 TransformProto.IntegrityTransform integrity = transformProto.getIntegrity(); 111 if (integrity.hasSha256()) { 112 encodedSpec = "integrity(sha256=" + urlEncode(integrity.getSha256()) + ")"; 113 } else { 114 encodedSpec = "integrity"; 115 } 116 break; 117 case ZIP: 118 TransformProto.ZipTransform zip = transformProto.getZip(); 119 Preconditions.checkArgument(zip.hasTarget()); 120 encodedSpec = "zip(target=" + urlEncode(zip.getTarget()) + ")"; 121 break; 122 case CUSTOM: 123 TransformProto.CustomTransform custom = transformProto.getCustom(); 124 String params = ""; 125 if (custom.getSubparamCount() > 0) { 126 ImmutableList.Builder<String> subparams = ImmutableList.builder(); 127 for (TransformProto.CustomTransform.SubParam subparam : custom.getSubparamList()) { 128 Preconditions.checkArgument(subparam.hasKey()); 129 if (subparam.hasValue()) { 130 subparams.add(subparam.getKey() + "=" + urlEncode(subparam.getValue())); 131 } else { 132 subparams.add(subparam.getKey()); 133 } 134 } 135 params = "(" + Joiner.on(",").join(subparams.build()) + ")"; 136 } 137 encodedSpec = custom.getName() + params; 138 break; 139 default: 140 throw new IllegalArgumentException("No transform specified"); 141 } 142 return encodedSpec; 143 } 144 urlEncode(String str)145 private static final String urlEncode(String str) { 146 try { 147 return URLEncoder.encode(str, UTF_8.displayName()); 148 } catch (UnsupportedEncodingException e) { 149 throw new IllegalStateException(e); // Expects UTF8 to be available. 150 } 151 } 152 } 153