• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.providers.media.util;
18 
19 import android.media.ExifInterface;
20 import android.os.Trace;
21 import android.util.ArraySet;
22 
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FileNotFoundException;
26 import java.io.IOException;
27 import java.util.Arrays;
28 import java.util.Set;
29 
30 public class RedactionUtils {
31     /**
32      * Set of Exif tags that should be considered for redaction.
33      */
34     private static final String[] REDACTED_EXIF_TAGS = new String[]{
35             ExifInterface.TAG_GPS_ALTITUDE,
36             ExifInterface.TAG_GPS_ALTITUDE_REF,
37             ExifInterface.TAG_GPS_AREA_INFORMATION,
38             ExifInterface.TAG_GPS_DOP,
39             ExifInterface.TAG_GPS_DATESTAMP,
40             ExifInterface.TAG_GPS_DEST_BEARING,
41             ExifInterface.TAG_GPS_DEST_BEARING_REF,
42             ExifInterface.TAG_GPS_DEST_DISTANCE,
43             ExifInterface.TAG_GPS_DEST_DISTANCE_REF,
44             ExifInterface.TAG_GPS_DEST_LATITUDE,
45             ExifInterface.TAG_GPS_DEST_LATITUDE_REF,
46             ExifInterface.TAG_GPS_DEST_LONGITUDE,
47             ExifInterface.TAG_GPS_DEST_LONGITUDE_REF,
48             ExifInterface.TAG_GPS_DIFFERENTIAL,
49             ExifInterface.TAG_GPS_IMG_DIRECTION,
50             ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
51             ExifInterface.TAG_GPS_LATITUDE,
52             ExifInterface.TAG_GPS_LATITUDE_REF,
53             ExifInterface.TAG_GPS_LONGITUDE,
54             ExifInterface.TAG_GPS_LONGITUDE_REF,
55             ExifInterface.TAG_GPS_MAP_DATUM,
56             ExifInterface.TAG_GPS_MEASURE_MODE,
57             ExifInterface.TAG_GPS_PROCESSING_METHOD,
58             ExifInterface.TAG_GPS_SATELLITES,
59             ExifInterface.TAG_GPS_SPEED,
60             ExifInterface.TAG_GPS_SPEED_REF,
61             ExifInterface.TAG_GPS_STATUS,
62             ExifInterface.TAG_GPS_TIMESTAMP,
63             ExifInterface.TAG_GPS_TRACK,
64             ExifInterface.TAG_GPS_TRACK_REF,
65             ExifInterface.TAG_GPS_VERSION_ID,
66     };
67 
68     /**
69      * Set of ISO boxes that should be considered for redaction.
70      */
71     private static final int[] REDACTED_ISO_BOXES = new int[]{
72             IsoInterface.BOX_LOCI,
73             IsoInterface.BOX_XYZ,
74             IsoInterface.BOX_GPS,
75             IsoInterface.BOX_GPS0,
76     };
77 
78     private static final Set<String> sRedactedExifTags = new ArraySet<>(
79             Arrays.asList(REDACTED_EXIF_TAGS));
80 
RedactionUtils()81     private RedactionUtils() {
82         // Utility Class
83     }
84 
85     /**
86      * Set of Exif tags that should be considered for redaction.
87      */
getsRedactedExifTags()88     public static Set<String> getsRedactedExifTags() {
89         return sRedactedExifTags;
90     }
91 
92     /**
93      * Calculates the ranges containing sensitive metadata that should be redacted if the caller
94      * doesn't have the required permissions.
95      *
96      * @param file file to be redacted
97      * @return the ranges to be redacted in a RedactionInfo object, could be empty redaction ranges
98      * if there's sensitive metadata
99      * @throws IOException if an IOException happens while calculating the redaction ranges
100      */
getRedactionRanges(File file)101     public static long[] getRedactionRanges(File file) throws IOException {
102         try (FileInputStream is = new FileInputStream(file)) {
103             return getRedactionRanges(is, MimeUtils.resolveMimeType(file));
104         } catch (FileNotFoundException ignored) {
105             // If file not found, then there's nothing to redact
106             return new long[0];
107         } catch (IOException e) {
108             throw new IOException("Failed to redact " + file, e);
109         }
110     }
111 
112     /**
113      * Calculates the ranges containing sensitive metadata that should be redacted if the caller
114      * doesn't have the required permissions.
115      *
116      * @param fis {@link FileInputStream} to be redacted
117      * @return the ranges to be redacted in a RedactionInfo object, could be empty redaction ranges
118      * if there's sensitive metadata
119      * @throws IOException if an IOException happens while calculating the redaction ranges
120      */
getRedactionRanges(FileInputStream fis, String mimeType)121     public static long[] getRedactionRanges(FileInputStream fis, String mimeType)
122             throws IOException {
123         final LongArray res = new LongArray();
124 
125         Trace.beginSection("MP.getRedactionRanges");
126         try {
127             if (ExifInterface.isSupportedMimeType(mimeType)) {
128                 final ExifInterface exif = new ExifInterface(fis.getFD());
129                 for (String tag : REDACTED_EXIF_TAGS) {
130                     final long[] range = exif.getAttributeRange(tag);
131                     if (range != null) {
132                         res.add(range[0]);
133                         res.add(range[0] + range[1]);
134                     }
135                 }
136                 // Redact xmp where present
137                 res.addAll(XmpDataParser.getRedactionRanges(exif));
138             }
139 
140             if (IsoInterface.isSupportedMimeType(mimeType)) {
141                 final IsoInterface iso = IsoInterface.fromFileDescriptor(fis.getFD());
142                 for (int box : REDACTED_ISO_BOXES) {
143                     final long[] ranges = iso.getBoxRanges(box);
144                     for (int i = 0; i < ranges.length; i += 2) {
145                         long boxTypeOffset = ranges[i] - 4;
146                         res.add(boxTypeOffset);
147                         res.add(ranges[i + 1]);
148                     }
149                 }
150                 // Redact xmp where present
151                 res.addAll(XmpDataParser.getRedactionRanges(iso));
152             }
153 
154             return res.toArray();
155         } finally {
156             Trace.endSection();
157         }
158     }
159 }
160