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 android.database;
18 
19 import android.annotation.NonNull;
20 import android.util.SparseArray;
21 
22 import java.util.Map;
23 
24 /**
25  * Cursor that offers to redact values of requested columns.
26  *
27  * @hide
28  */
29 public class RedactingCursor extends CrossProcessCursorWrapper {
30     private final SparseArray<Object> mRedactions;
31 
RedactingCursor(@onNull Cursor cursor, SparseArray<Object> redactions)32     private RedactingCursor(@NonNull Cursor cursor, SparseArray<Object> redactions) {
33         super(cursor);
34         mRedactions = redactions;
35     }
36 
37     /**
38      * Create a wrapped instance of the given {@link Cursor} which redacts the
39      * requested columns so they always return specific values when accessed.
40      * <p>
41      * If a redacted column appears multiple times in the underlying cursor, all
42      * instances will be redacted. If none of the redacted columns appear in the
43      * given cursor, the given cursor will be returned untouched to improve
44      * performance.
45      */
create(@onNull Cursor cursor, @NonNull Map<String, Object> redactions)46     public static Cursor create(@NonNull Cursor cursor, @NonNull Map<String, Object> redactions) {
47         final SparseArray<Object> internalRedactions = new SparseArray<>();
48 
49         final String[] columns = cursor.getColumnNames();
50         for (int i = 0; i < columns.length; i++) {
51             if (redactions.containsKey(columns[i])) {
52                 internalRedactions.put(i, redactions.get(columns[i]));
53             }
54         }
55 
56         if (internalRedactions.size() == 0) {
57             return cursor;
58         } else {
59             return new RedactingCursor(cursor, internalRedactions);
60         }
61     }
62 
63     @Override
fillWindow(int position, CursorWindow window)64     public void fillWindow(int position, CursorWindow window) {
65         // Fill window directly to ensure data is redacted
66         DatabaseUtils.cursorFillWindow(this, position, window);
67     }
68 
69     @Override
getWindow()70     public CursorWindow getWindow() {
71         // Returning underlying window risks leaking redacted data
72         return null;
73     }
74 
75     @Override
getWrappedCursor()76     public Cursor getWrappedCursor() {
77         throw new UnsupportedOperationException(
78                 "Returning underlying cursor risks leaking redacted data");
79     }
80 
81     @Override
getDouble(int columnIndex)82     public double getDouble(int columnIndex) {
83         final int i = mRedactions.indexOfKey(columnIndex);
84         if (i >= 0) {
85             return (double) mRedactions.valueAt(i);
86         } else {
87             return super.getDouble(columnIndex);
88         }
89     }
90 
91     @Override
getFloat(int columnIndex)92     public float getFloat(int columnIndex) {
93         final int i = mRedactions.indexOfKey(columnIndex);
94         if (i >= 0) {
95             return (float) mRedactions.valueAt(i);
96         } else {
97             return super.getFloat(columnIndex);
98         }
99     }
100 
101     @Override
getInt(int columnIndex)102     public int getInt(int columnIndex) {
103         final int i = mRedactions.indexOfKey(columnIndex);
104         if (i >= 0) {
105             return (int) mRedactions.valueAt(i);
106         } else {
107             return super.getInt(columnIndex);
108         }
109     }
110 
111     @Override
getLong(int columnIndex)112     public long getLong(int columnIndex) {
113         final int i = mRedactions.indexOfKey(columnIndex);
114         if (i >= 0) {
115             return (long) mRedactions.valueAt(i);
116         } else {
117             return super.getLong(columnIndex);
118         }
119     }
120 
121     @Override
getShort(int columnIndex)122     public short getShort(int columnIndex) {
123         final int i = mRedactions.indexOfKey(columnIndex);
124         if (i >= 0) {
125             return (short) mRedactions.valueAt(i);
126         } else {
127             return super.getShort(columnIndex);
128         }
129     }
130 
131     @Override
getString(int columnIndex)132     public String getString(int columnIndex) {
133         final int i = mRedactions.indexOfKey(columnIndex);
134         if (i >= 0) {
135             return (String) mRedactions.valueAt(i);
136         } else {
137             return super.getString(columnIndex);
138         }
139     }
140 
141     @Override
copyStringToBuffer(int columnIndex, CharArrayBuffer buffer)142     public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
143         final int i = mRedactions.indexOfKey(columnIndex);
144         if (i >= 0) {
145             buffer.data = ((String) mRedactions.valueAt(i)).toCharArray();
146             buffer.sizeCopied = buffer.data.length;
147         } else {
148             super.copyStringToBuffer(columnIndex, buffer);
149         }
150     }
151 
152     @Override
getBlob(int columnIndex)153     public byte[] getBlob(int columnIndex) {
154         final int i = mRedactions.indexOfKey(columnIndex);
155         if (i >= 0) {
156             return (byte[]) mRedactions.valueAt(i);
157         } else {
158             return super.getBlob(columnIndex);
159         }
160     }
161 
162     @Override
getType(int columnIndex)163     public int getType(int columnIndex) {
164         final int i = mRedactions.indexOfKey(columnIndex);
165         if (i >= 0) {
166             return DatabaseUtils.getTypeOfObject(mRedactions.valueAt(i));
167         } else {
168             return super.getType(columnIndex);
169         }
170     }
171 
172     @Override
isNull(int columnIndex)173     public boolean isNull(int columnIndex) {
174         final int i = mRedactions.indexOfKey(columnIndex);
175         if (i >= 0) {
176             return mRedactions.valueAt(i) == null;
177         } else {
178             return super.isNull(columnIndex);
179         }
180     }
181 }
182