commit 53bf6bc84b9cc2fde2a8d1c82f12cae9cb3b510d Author: mar-v-in Date: Tue Jan 6 03:55:57 2015 +0100 Initial commit diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..4e2ecc3 --- /dev/null +++ b/Android.mk @@ -0,0 +1,7 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := SafeParcel +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..1f776a8 --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..63a4dd3 --- /dev/null +++ b/build.gradle @@ -0,0 +1,25 @@ +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.0.0' + } +} +apply plugin: 'com.android.library' +android { + compileSdkVersion 21 + buildToolsVersion "21.0.2" + lintOptions.abortOnError false + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + aidl.srcDirs = ['src'] + } + } +} + +dependencies { +} diff --git a/src/org/microg/safeparcel/SafeParcelReader.java b/src/org/microg/safeparcel/SafeParcelReader.java new file mode 100644 index 0000000..80d9576 --- /dev/null +++ b/src/org/microg/safeparcel/SafeParcelReader.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2014 μg Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.safeparcel; + +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +public class SafeParcelReader { + public static int halfOf(int i) { + return i & 0xFFFF; + } + + public static int readSingleInt(Parcel parcel) { + return parcel.readInt(); + } + + private static int readStart(Parcel parcel, int first) { + if ((first & 0xFFFF0000) != -65536) + return first >> 16 & 0xFFFF; + return parcel.readInt(); + } + + private static void readStart(Parcel parcel, int position, int length) { + int i = readStart(parcel, position); + if (i != length) + throw new ReadException("Expected size " + length + " got " + i + " (0x" + Integer.toHexString(i) + ")", parcel); + } + + public static int readStart(Parcel parcel) { + int first = readSingleInt(parcel); + int length = readStart(parcel, first); + int start = parcel.dataPosition(); + if (halfOf(first) != SafeParcelable.SAFE_PARCEL_MAGIC) + throw new ReadException("Expected object header. Got 0x" + Integer.toHexString(first), parcel); + int end = start + length; + if ((end < start) || (end > parcel.dataSize())) + throw new ReadException("Size read is invalid start=" + start + " end=" + end, parcel); + return end; + } + + public static int readInt(Parcel parcel, int position) { + readStart(parcel, position, 4); + return parcel.readInt(); + } + + public static byte readByte(Parcel parcel, int position) { + readStart(parcel, position, 4); + return (byte) parcel.readInt(); + } + + public static short readShort(Parcel parcel, int position) { + readStart(parcel, position, 4); + return (short) parcel.readInt(); + } + + public static boolean readBool(Parcel parcel, int position) { + readStart(parcel, position, 4); + return parcel.readInt() != 0; + } + + public static long readLong(Parcel parcel, int position) { + readStart(parcel, position, 8); + return parcel.readLong(); + } + + public static float readFloat(Parcel parcel, int position) { + readStart(parcel, position, 4); + return parcel.readFloat(); + } + + public static double readDouble(Parcel parcel, int position) { + readStart(parcel, position, 8); + return parcel.readDouble(); + } + + public static String readString(Parcel parcel, int position) { + int length = readStart(parcel, position); + int start = parcel.dataPosition(); + if (length == 0) + return null; + String string = parcel.readString(); + parcel.setDataPosition(start + length); + return string; + } + + public static IBinder readBinder(Parcel parcel, int position) { + int length = readStart(parcel, position); + int start = parcel.dataPosition(); + if (length == 0) + return null; + IBinder binder = parcel.readStrongBinder(); + parcel.setDataPosition(start + length); + return binder; + } + + public static T readParcelable(Parcel parcel, int position, Parcelable.Creator creator) { + int length = readStart(parcel, position); + int start = parcel.dataPosition(); + if (length == 0) + return null; + T t = creator.createFromParcel(parcel); + parcel.setDataPosition(start + length); + return t; + } + + public static void skip(Parcel parcel, int position) { + int i = readStart(parcel, position); + parcel.setDataPosition(parcel.dataPosition() + i); + } + + public static class ReadException extends RuntimeException { + public ReadException(String message, Parcel parcel) { + super(message); + } + } +} diff --git a/src/org/microg/safeparcel/SafeParcelUtil.java b/src/org/microg/safeparcel/SafeParcelUtil.java new file mode 100644 index 0000000..3c1e859 --- /dev/null +++ b/src/org/microg/safeparcel/SafeParcelUtil.java @@ -0,0 +1,179 @@ +package org.microg.safeparcel; + +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +public class SafeParcelUtil { + private static final String TAG = "SafeParcel"; + + public static void writeObject(SafeParcelable object, Parcel parcel, int flags) { + if (object == null) + throw new NullPointerException(); + Class clazz = object.getClass(); + int start = SafeParcelWriter.writeStart(parcel); + while (clazz != null) { + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(SafeParceled.class)) { + try { + writeField(object, parcel, field, flags); + } catch (Exception e) { + Log.w(TAG, "Error writing field: " + e); + } + } + } + clazz = clazz.getSuperclass(); + } + SafeParcelWriter.writeEnd(parcel, start); + } + + public static void readObject(SafeParcelable object, Parcel parcel) { + if (object == null) + throw new NullPointerException(); + Class clazz = object.getClass(); + Map fieldMap = new HashMap<>(); + while (clazz != null) { + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(SafeParceled.class)) { + int fieldNum = field.getAnnotation(SafeParceled.class).value(); + if (fieldMap.containsKey(fieldNum)) { + throw new RuntimeException( + "Field number " + fieldNum + " is used twice in " + + clazz.getName() + " for fields " + field.getName() + + " and " + fieldMap.get(fieldNum).getName()); + } + fieldMap.put(fieldNum, field); + } + } + clazz = clazz.getSuperclass(); + } + int end = SafeParcelReader.readStart(parcel); + while (parcel.dataPosition() < end) { + int position = SafeParcelReader.readSingleInt(parcel); + int fieldNum = SafeParcelReader.halfOf(position); + if (!fieldMap.containsKey(fieldNum)) { + Log.w(TAG, + "Unknown field num " + fieldNum + " in " + clazz.getName() + ", skipping."); + SafeParcelReader.skip(parcel, position); + } else { + try { + readField(object, parcel, fieldMap.get(fieldNum), position); + } catch (Exception e) { + Log.w(TAG, "Error reading field: " + fieldNum + " in " + clazz.getName() + + ", skipping.", e); + SafeParcelReader.skip(parcel, position); + } + } + } + if (parcel.dataPosition() > end) { + throw new RuntimeException("Overread allowed size end=" + end); + } + } + + private static Parcelable.Creator getCreator(Field field) + throws IllegalAccessException { + try { + return (Parcelable.Creator) field.getType().getDeclaredField("CREATOR").get(null); + } catch (NoSuchFieldException e) { + throw new RuntimeException( + field.getType() + " is an Parcelable without CREATOR"); + } + } + + private static void writeField(SafeParcelable object, Parcel parcel, Field field, int flags) + throws IllegalAccessException { + int num = field.getAnnotation(SafeParceled.class).value(); + boolean mayNull = field.getAnnotation(SafeParceled.class).mayNull(); + boolean acc = field.isAccessible(); + field.setAccessible(true); + switch (SafeParcelType.fromClass(field.getType())) { + case Parcelable: + SafeParcelWriter.write(parcel, num, (Parcelable) field.get(object), flags, mayNull); + break; + case Binder: + SafeParcelWriter.write(parcel, num, (IBinder) field.get(object), mayNull); + break; + case Integer: + SafeParcelWriter.write(parcel, num, (Integer) field.get(object)); + break; + case Long: + SafeParcelWriter.write(parcel, num, (Long) field.get(object)); + break; + case Boolean: + SafeParcelWriter.write(parcel, num, (Boolean) field.get(object)); + break; + case Float: + SafeParcelWriter.write(parcel, num, (Float) field.get(object)); + break; + case Double: + SafeParcelWriter.write(parcel, num, (Double) field.get(object)); + break; + case String: + SafeParcelWriter.write(parcel, num, (String) field.get(object), mayNull); + } + field.setAccessible(acc); + } + + private static void readField(SafeParcelable object, Parcel parcel, Field field, int position) + throws IllegalAccessException { + boolean acc = field.isAccessible(); + field.setAccessible(true); + switch (SafeParcelType.fromClass(field.getType())) { + case Parcelable: + field.set(object, SafeParcelReader + .readParcelable(parcel, position, getCreator(field))); + break; + case Binder: + field.set(object, SafeParcelReader.readBinder(parcel, + position)); + break; + case Integer: + field.set(object, SafeParcelReader.readInt(parcel, position)); + break; + case Long: + field.set(object, SafeParcelReader.readLong(parcel, position)); + break; + case Boolean: + field.set(object, SafeParcelReader.readBool(parcel, position)); + break; + case Float: + field.set(object, SafeParcelReader.readFloat(parcel, position)); + break; + case Double: + field.set(object, SafeParcelReader.readDouble(parcel, position)); + break; + case String: + field.set(object, SafeParcelReader.readString(parcel, position)); + } + field.setAccessible(acc); + } + + private enum SafeParcelType { + Parcelable, Binder, Integer, Long, Boolean, Float, Double, String; + + public static SafeParcelType fromClass(Class clazz) { + if (Parcelable.class.isAssignableFrom(clazz)) + return Parcelable; + if (IBinder.class.isAssignableFrom(clazz)) + return Binder; + if (clazz == int.class || clazz == Integer.class) + return Integer; + if (clazz == boolean.class || clazz == Boolean.class) + return Boolean; + if (clazz == long.class || clazz == Long.class) + return Long; + if (clazz == float.class || clazz == Float.class) + return Float; + if (clazz == double.class || clazz == Double.class) + return Float; + if (clazz == java.lang.String.class) + return String; + throw new RuntimeException("Type is not yet usable with SafeParcelUtil: " + clazz); + } + } +} diff --git a/src/org/microg/safeparcel/SafeParcelWriter.java b/src/org/microg/safeparcel/SafeParcelWriter.java new file mode 100644 index 0000000..19bcd92 --- /dev/null +++ b/src/org/microg/safeparcel/SafeParcelWriter.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2014 μg Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.safeparcel; + +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +public class SafeParcelWriter { + + private static void writeStart(Parcel parcel, int position, int length) { + if (length >= 65535) { + parcel.writeInt(0xFFFF0000 | position); + parcel.writeInt(length); + } else { + parcel.writeInt(length << 16 | position); + } + } + + public static int writeStart(Parcel parcel) { + return writeStart(parcel, SafeParcelable.SAFE_PARCEL_MAGIC); + } + + private static int writeStart(Parcel parcel, int position) { + parcel.writeInt(0xFFFF0000 | position); + parcel.writeInt(0); + return parcel.dataPosition(); + } + + public static void writeEnd(Parcel parcel, int start) { + int end = parcel.dataPosition(); + int length = end - start; + parcel.setDataPosition(start - 4); + parcel.writeInt(length); + parcel.setDataPosition(end); + } + + public static void write(Parcel parcel, int position, Boolean val) { + if (val == null) return; + writeStart(parcel, position, 4); + parcel.writeInt(val ? 1 : 0); + } + + public static void write(Parcel parcel, int position, Byte val) { + if (val == null) return; + writeStart(parcel, position, 4); + parcel.writeInt(val); + } + + public static void write(Parcel parcel, int position, Short val) { + if (val == null) return; + writeStart(parcel, position, 4); + parcel.writeInt(val); + } + + public static void write(Parcel parcel, int position, Integer val) { + if (val == null) return; + writeStart(parcel, position, 4); + parcel.writeInt(val); + } + + public static void write(Parcel parcel, int position, Long val) { + if (val == null) return; + writeStart(parcel, position, 8); + parcel.writeLong(val); + } + + public static void write(Parcel parcel, int position, Float val) { + if (val == null) return; + writeStart(parcel, position, 4); + parcel.writeFloat(val); + } + + public static void write(Parcel parcel, int position, Double val) { + if (val == null) return; + writeStart(parcel, position, 8); + parcel.writeDouble(val); + } + + public static void write(Parcel parcel, int position, String val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeStart(parcel, position, 0); + } + } else { + int start = writeStart(parcel, position); + parcel.writeString(val); + writeEnd(parcel, start); + } + } + + public static void write(Parcel parcel, int position, Parcelable val, int flags, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeStart(parcel, position, 0); + } + } else { + int start = writeStart(parcel, position); + val.writeToParcel(parcel, flags); + writeEnd(parcel, start); + } + } + + public static void write(Parcel parcel, int position, Bundle val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeStart(parcel, position, 0); + } + } else { + int start = writeStart(parcel, position); + parcel.writeBundle(val); + writeEnd(parcel, start); + } + } + + public static void write(Parcel parcel, int position, byte[] val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeStart(parcel, position, 0); + } + } else { + int start = writeStart(parcel, position); + parcel.writeByteArray(val); + writeEnd(parcel, start); + } + } + + public static void write(Parcel parcel, int position, String[] val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeStart(parcel, position, 0); + } + } else { + int start = writeStart(parcel, position); + parcel.writeStringArray(val); + writeEnd(parcel, start); + } + } + + public static void writeStringList(Parcel parcel, int position, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeStart(parcel, position, 0); + } + } else { + int start = writeStart(parcel, position); + parcel.writeStringList(val); + writeEnd(parcel, start); + } + } + + private static void writeArrayPart(Parcel parcel, T val, int flags) { + int before = parcel.dataPosition(); + parcel.writeInt(1); + int start = parcel.dataPosition(); + val.writeToParcel(parcel, flags); + int end = parcel.dataPosition(); + parcel.setDataPosition(before); + parcel.writeInt(end - start); + parcel.setDataPosition(end); + } + + public static void write(Parcel parcel, int position, T[] val, int flags, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeStart(parcel, position, 0); + } + } else { + int start = writeStart(parcel, position); + parcel.writeInt(val.length); + for (T t : val) { + if (t == null) { + parcel.writeInt(0); + } else { + writeArrayPart(parcel, t, flags); + } + } + writeEnd(parcel, start); + } + } + + public static void write(Parcel parcel, int position, Parcel val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeStart(parcel, position, 0); + } + } else { + int start = writeStart(parcel, position); + parcel.appendFrom(val, 0, val.dataSize()); + writeEnd(parcel, start); + } + } + + public static void write(Parcel parcel, int position, List val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeStart(parcel, position, 0); + } + } else { + int start = writeStart(parcel, position); + parcel.writeList(val); + writeEnd(parcel, start); + } + } + + public static void write(Parcel parcel, int position, IBinder val, boolean mayNull) { + if (val == null) { + if (mayNull) { + writeStart(parcel, position, 0); + } + } else { + int start = writeStart(parcel, position); + parcel.writeStrongBinder(val); + writeEnd(parcel, start); + } + } + +} diff --git a/src/org/microg/safeparcel/SafeParcelable.java b/src/org/microg/safeparcel/SafeParcelable.java new file mode 100644 index 0000000..ac9cc30 --- /dev/null +++ b/src/org/microg/safeparcel/SafeParcelable.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2014 μg Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.safeparcel; + +import android.os.Parcelable; + +public interface SafeParcelable extends Parcelable { + public static final String NULL = "SAFE_PARCELABLE_NULL_STRING"; + int SAFE_PARCEL_MAGIC = 20293; +} diff --git a/src/org/microg/safeparcel/SafeParceled.java b/src/org/microg/safeparcel/SafeParceled.java new file mode 100644 index 0000000..fb86600 --- /dev/null +++ b/src/org/microg/safeparcel/SafeParceled.java @@ -0,0 +1,14 @@ +package org.microg.safeparcel; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SafeParceled { + int value(); + + boolean mayNull() default false; +}