diff --git a/Platform/Android/Android.mk b/Platform/Android/Android.mk index 66a5fc5ea..d320d7053 100644 --- a/Platform/Android/Android.mk +++ b/Platform/Android/Android.mk @@ -1,17 +1,17 @@ # Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. -# -# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# -# Licensed under Gnu Affero General Public License Version 3 (provided, notwithstanding this -# notice, Readium Foundation reserves the right to license this material under a different -# separate license, and if you have done so, the terms of that separate license control and +# +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# Licensed under Gnu Affero General Public License Version 3 (provided, notwithstanding this +# notice, Readium Foundation reserves the right to license this material under a different +# separate license, and if you have done so, the terms of that separate license control and # the following references to GPL do not apply). -# -# This program is free software: you can redistribute it and/or modify it under the terms -# of the GNU Affero General Public License as published by the Free Software Foundation, -# either version 3 of the License, or (at your option) any later version. You should have -# received a copy of the GNU Affero General Public License along with this program. If not, +# +# This program is free software: you can redistribute it and/or modify it under the terms +# of the GNU Affero General Public License as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. You should have +# received a copy of the GNU Affero General Public License along with this program. If not, # see . LOCAL_PATH := $(call my-dir)/../.. @@ -277,6 +277,7 @@ LOCAL_SRC_FILES := \ ePub3/ePub/filter_chain_byte_stream_range.cpp \ ePub3/ePub/filter_manager_impl.cpp \ ePub3/ePub/filter_manager.cpp \ + ePub3/ePub/filter.cpp \ ePub3/ePub/PassThroughFilter.cpp \ ePub3/ePub/font_obfuscation.cpp \ ePub3/ePub/glossary.cpp \ diff --git a/Platform/Android/jni/epub3.cpp b/Platform/Android/jni/epub3.cpp index 816989782..e1dc780f5 100644 --- a/Platform/Android/jni/epub3.cpp +++ b/Platform/Android/jni/epub3.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -70,6 +71,9 @@ static const char *javaEPub3_appendBytesToBufferSignature = "(Ljava/nio/ByteBuff static const char *javaEPub3_handleSdkErrorMethodName = "handleSdkError"; static const char *javaEPub3_handleSdkErrorSignature = "(Ljava/lang/String;Z)Z"; +static const char *javaEPub3_handleContentFilterErrorMethodName = "handleContentFilterError"; +static const char *javaEPub3_handleContentFilterErrorSignature = "(Ljava/lang/String;JLjava/lang/String;)V"; + /* * Exported variables @@ -95,6 +99,11 @@ jmethodID createManifestItem_ID; jmethodID addManifestItemToList_ID; +// Global variable to store the current Java virtual machine. +// This will be needed by the ContentFilterErrorHandler() function. +JavaVM *g_vm = NULL; + + /* * Internal variables **************************************************/ @@ -110,7 +119,7 @@ static jmethodID addStringToList_ID; static jmethodID createBuffer_ID; static jmethodID appendBytesToBuffer_ID; static jmethodID handleSdkError_ID; - +static jmethodID handleContentFilterError_ID; /* * Exported functions @@ -190,6 +199,10 @@ jboolean javaEPub3_handleSdkError(JNIEnv *env, jstring message, jboolean isSever return b; } +void javaEPub3_handleContentFilterError(JNIEnv *env, jstring filterId, jlong errorCode, jstring message) { + env->CallStaticVoidMethod(javaEPub3Class, handleContentFilterError_ID, filterId, errorCode, message); +} + /* * Internal functions **************************************************/ @@ -211,11 +224,13 @@ static int onLoad_cacheJavaElements_epub3(JNIEnv *env) { javaEPub3_appendBytesToBufferMethodName, javaEPub3_appendBytesToBufferSignature, ONLOAD_ERROR); INIT_STATIC_METHOD_ID_RETVAL(handleSdkError_ID, javaEPub3Class, javaEPub3ClassName, javaEPub3_handleSdkErrorMethodName, javaEPub3_handleSdkErrorSignature, ONLOAD_ERROR); + INIT_STATIC_METHOD_ID_RETVAL(handleContentFilterError_ID, javaEPub3Class, javaEPub3ClassName, + javaEPub3_handleContentFilterErrorMethodName, javaEPub3_handleContentFilterErrorSignature, ONLOAD_ERROR); // Return JNI_VERSION for OK, if not one of the lines above already returned ONLOAD_ERROR return JNI_VERSION; } -// needed only for the LauncherErrorHandler() C++ callback, +// needed only for the following C++ callbacks, // set by initializeReadiumSDK() static JNIEnv* m_env = nullptr; @@ -238,6 +253,30 @@ static bool LauncherErrorHandler(const ePub3::error_details& err) //return ePub3::DefaultErrorHandler(err); } +/** + * Callback to be invoked when any ContentFilter has an error. + */ + +static void ContentFilterErrorHandler(const std::string &filterId, unsigned int errorCode, const std::string &message) +{ + // Making sure that we have the right JNI environment pointer for every thread. + // This function may be called in different threads, and you must have the right + // JNI environment pointer for the current thread, otherwise it will crash the app. + JNIEnv *env; + if (g_vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK) { + LOGE("ContentFilterErrorHandler: failed to get environment. VM doesn't support JNI version 1.6"); + return; + } + + jstring jFilterId = env->NewStringUTF(filterId.c_str()); + jstring jMessage = env->NewStringUTF(message.c_str()); + + javaEPub3_handleContentFilterError(env, jFilterId, (jlong)errorCode, jMessage); + + env->DeleteLocalRef(jFilterId); + env->DeleteLocalRef(jMessage); +} + /** * Initializes the Readium SDK. */ @@ -249,6 +288,9 @@ static void initializeReadiumSDK(JNIEnv* env) ePub3::ErrorHandlerFn launcherErrorHandler = LauncherErrorHandler; ePub3::SetErrorHandler(launcherErrorHandler); + + ePub3::ContentFilterErrorHandlerFn contentFilterErrorHandler = ContentFilterErrorHandler; + ePub3::ContentFilter::ResetContentFilterErrorHandler(contentFilterErrorHandler); ePub3::InitializeSdk(); ePub3::PopulateFilterManager(); @@ -266,6 +308,8 @@ static void initializeReadiumSDK(JNIEnv* env) */ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + g_vm = vm; + // Get the JNI Environment to be able to initialize the cached the java elements JNIEnv* env; if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK) { diff --git a/Platform/Android/jni/epub3.h b/Platform/Android/jni/epub3.h index e62d37d04..ab23493be 100644 --- a/Platform/Android/jni/epub3.h +++ b/Platform/Android/jni/epub3.h @@ -111,6 +111,8 @@ void javaEPub3_appendBytesToBuffer(JNIEnv *env, jobject buffer, jbyteArray data) jboolean javaEPub3_handleSdkError(JNIEnv *env, jstring message, jboolean isSevereEpubError); +void javaEPub3_handleContentFilterError(JNIEnv *env, jstring filterId, jlong errorCode, jstring message); + /* * JNI functions **************************************************/ diff --git a/Platform/Android/src/org/readium/sdk/android/ContentFilterErrorHandler.java b/Platform/Android/src/org/readium/sdk/android/ContentFilterErrorHandler.java new file mode 100644 index 000000000..7041261a3 --- /dev/null +++ b/Platform/Android/src/org/readium/sdk/android/ContentFilterErrorHandler.java @@ -0,0 +1,21 @@ +// Copyright (c) 2015 Readium Foundation and/or its licensees. All rights reserved. +// +// This program is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// Licensed under Gnu Affero General Public License Version 3 (provided, notwithstanding this notice, +// Readium Foundation reserves the right to license this material under a different separate license, +// and if you have done so, the terms of that separate license control and the following references +// to GPL do not apply). +// +// This program is free software: you can redistribute it and/or modify it under the terms of the GNU +// Affero General Public License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. You should have received a copy of the GNU +// Affero General Public License along with this program. If not, see . + + +package org.readium.sdk.android; + +public interface ContentFilterErrorHandler { + void handleContentFilterError(String filterId, long errorCode, String message); +} diff --git a/Platform/Android/src/org/readium/sdk/android/EPub3.java b/Platform/Android/src/org/readium/sdk/android/EPub3.java index 5e156eb05..2fa967626 100644 --- a/Platform/Android/src/org/readium/sdk/android/EPub3.java +++ b/Platform/Android/src/org/readium/sdk/android/EPub3.java @@ -169,4 +169,16 @@ private static boolean handleSdkError(String message, boolean isSevereEpubError) // never throws an exception return true; } + + private static ContentFilterErrorHandler m_contentFilterErrorHandler = null; + + public static void setContentFilterErrorHandler(ContentFilterErrorHandler handler) { + m_contentFilterErrorHandler = handler; + } + + private static void handleContentFilterError(String filterId, long errorCode, String message) { + if (m_contentFilterErrorHandler != null) { + m_contentFilterErrorHandler.handleContentFilterError(filterId, errorCode, message); + } + } } diff --git a/Platform/Apple/RDServices/Main/RDContainer.h b/Platform/Apple/RDServices/Main/RDContainer.h index e7c35f58c..ba591bcd0 100644 --- a/Platform/Apple/RDServices/Main/RDContainer.h +++ b/Platform/Apple/RDServices/Main/RDContainer.h @@ -29,11 +29,19 @@ #import + +extern NSString * const EPub3ContentFilterErrorDomain; +extern NSString * const EPub3ContentFilterIdentifierKey; +extern NSString * const EPub3ContentFilterErrorCodeKey; +extern NSString * const EPub3ContentFilterErrorMessage; + + @class RDContainer; @protocol RDContainerDelegate - (BOOL)container:(RDContainer *)container handleSdkError:(NSString *)message isSevereEpubError:(BOOL)isSevereEpubError; +- (void)container:(RDContainer *)container handleContentFilterError:(NSError *)error; @end diff --git a/Platform/Apple/RDServices/Main/RDContainer.mm b/Platform/Apple/RDServices/Main/RDContainer.mm index b9265e48e..65edd7166 100644 --- a/Platform/Apple/RDServices/Main/RDContainer.mm +++ b/Platform/Apple/RDServices/Main/RDContainer.mm @@ -31,8 +31,14 @@ #import #import #import +#import #import "RDPackage.h" +NSString * const EPub3ContentFilterErrorDomain = @"ePub3ContentFilterErrorDomain"; +NSString * const EPub3ContentFilterIdentifierKey = @"ePub3ContentFilterIdentifierKey"; +NSString * const EPub3ContentFilterErrorCodeKey = @"ePub3ContentFilterErrorCodeKey"; +NSString * const EPub3ContentFilterErrorMessage = @"ePub3ContentFilterErrorMessageKey"; + @interface RDContainer () { @private std::shared_ptr m_container; @@ -81,6 +87,21 @@ - (instancetype)initWithDelegate:(id )delegate path:(NSStri //return ePub3::DefaultErrorHandler(err); }; ePub3::SetErrorHandler(sdkErrorHandler); + + ePub3::ContentFilterErrorHandlerFn contentFilterErrorHandler = ^(const std::string &filterId, unsigned int errorCode, const std::string &message) { + + NSDictionary *errorDictionary = @{ + EPub3ContentFilterIdentifierKey : [NSString stringWithUTF8String:filterId.c_str()], + EPub3ContentFilterErrorCodeKey : [[NSNumber alloc] initWithUnsignedInt:errorCode], + EPub3ContentFilterErrorMessage : [NSString stringWithUTF8String:message.c_str()] + }; + + NSError *error = [[NSError alloc] initWithDomain:EPub3ContentFilterErrorDomain code:errorCode userInfo:errorDictionary]; + + [m_delegate container:self handleContentFilterError:error]; + }; + + ePub3::ContentFilter::ResetContentFilterErrorHandler(contentFilterErrorHandler); ePub3::InitializeSdk(); ePub3::PopulateFilterManager(); diff --git a/Platform/Apple/ePub3.xcodeproj/project.pbxproj b/Platform/Apple/ePub3.xcodeproj/project.pbxproj index a48169bb1..860c91d68 100644 --- a/Platform/Apple/ePub3.xcodeproj/project.pbxproj +++ b/Platform/Apple/ePub3.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 1EFA3ACB17AB0BEF003A4BC2 /* filter_manager_impl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1EFA3ACA17AB0BEF003A4BC2 /* filter_manager_impl.cpp */; }; 1EFA3ACC17AB0C7A003A4BC2 /* filter_manager_impl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1EFA3ACA17AB0BEF003A4BC2 /* filter_manager_impl.cpp */; }; 3418BA7D16C4151E009AA7EF /* ring_buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ABA88FD116C2B4ED00F2014B /* ring_buffer.cpp */; }; + 586C95941B41FCCE00FD3700 /* filter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 586C95931B41FCCE00FD3700 /* filter.cpp */; }; + 586C95951B41FCCE00FD3700 /* filter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 586C95931B41FCCE00FD3700 /* filter.cpp */; }; 588D24201A02EF8F006A92BB /* PassThroughFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 588D241E1A02EF8F006A92BB /* PassThroughFilter.cpp */; }; 588D24211A02EF8F006A92BB /* PassThroughFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 588D241E1A02EF8F006A92BB /* PassThroughFilter.cpp */; }; 588D24221A02EF8F006A92BB /* PassThroughFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 588D241F1A02EF8F006A92BB /* PassThroughFilter.h */; }; @@ -445,6 +447,7 @@ 1ED0084717A9DC6F00819EBD /* initialization.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = initialization.cpp; sourceTree = ""; }; 1EFA3AC917AB0BC0003A4BC2 /* filter_manager_impl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = filter_manager_impl.h; sourceTree = ""; }; 1EFA3ACA17AB0BEF003A4BC2 /* filter_manager_impl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = filter_manager_impl.cpp; sourceTree = ""; }; + 586C95931B41FCCE00FD3700 /* filter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = filter.cpp; sourceTree = ""; }; 588D241E1A02EF8F006A92BB /* PassThroughFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PassThroughFilter.cpp; sourceTree = ""; }; 588D241F1A02EF8F006A92BB /* PassThroughFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PassThroughFilter.h; sourceTree = ""; }; 850B1AE816A75AB000619C3C /* TestData */ = {isa = PBXFileReference; lastKnownFileType = folder; name = TestData; path = ../../TestData; sourceTree = ""; }; @@ -1098,6 +1101,7 @@ A250D24D05706C9BB1AA54ED /* filter_chain_byte_stream_range.h */, A250DFDBD90C7E9C632B1E00 /* filter_chain_byte_stream.cpp */, A250D26DB00356B591AEFC29 /* filter_chain_byte_stream.h */, + 586C95931B41FCCE00FD3700 /* filter.cpp */, ); name = Filters; sourceTree = ""; @@ -2030,6 +2034,7 @@ buildActionMask = 2147483647; files = ( ABA4BAEF16ADF64400161B77 /* string16.cc in Sources */, + 586C95951B41FCCE00FD3700 /* filter.cpp in Sources */, ABA4BAF016ADF64400161B77 /* gurl.cc in Sources */, AB5284D417CBC395003D7BBF /* CPUCacheUtils.c in Sources */, ABA4BAF316ADF64400161B77 /* url_canon_etc.cc in Sources */, @@ -2174,6 +2179,7 @@ buildActionMask = 2147483647; files = ( ABB18FE61656863300CFC651 /* mkstemp.c in Sources */, + 586C95941B41FCCE00FD3700 /* filter.cpp in Sources */, AB718C04184CEEA900F86C6B /* xml_bridge_dtrace_probes.d in Sources */, ABB18FE81656863300CFC651 /* zip_add.c in Sources */, ABB18FE91656863300CFC651 /* zip_add_dir.c in Sources */, diff --git a/ePub3/ePub/PassThroughFilter.cpp b/ePub3/ePub/PassThroughFilter.cpp index 9cdf2ee9d..a777ee2b5 100644 --- a/ePub3/ePub/PassThroughFilter.cpp +++ b/ePub3/ePub/PassThroughFilter.cpp @@ -43,7 +43,7 @@ bool PassThroughFilter::SniffPassThroughContent(ConstManifestItemPtr item) // auto mediaType = item->MediaType(); // return (mediaType == "audio/mp4" || mediaType == "audio/mpeg" || mediaType == "video/mp4" || mediaType == "video/mpeg"); - return false; + return false; } ContentFilterPtr PassThroughFilter::PassThroughFactory(ConstPackagePtr package) @@ -96,6 +96,10 @@ void *PassThroughFilter::FilterData(FilterContext *context, void *data, size_t l if (!byteStream->IsOpen()) { + HandleContentFilterError( + std::string(PASS_THROUGH_FILTER_ID), + ContentFilterError::InputStreamCannotBeOpened, + std::string("Input ByteStream is not opened")); return nullptr; } diff --git a/ePub3/ePub/PassThroughFilter.h b/ePub3/ePub/PassThroughFilter.h index c1c584e87..5cb17e367 100644 --- a/ePub3/ePub/PassThroughFilter.h +++ b/ePub3/ePub/PassThroughFilter.h @@ -28,6 +28,9 @@ #include #import +#define PASS_THROUGH_FILTER_ID "3439DA53-2559-400D-8231-981ABA6A85B4" + + EPUB3_BEGIN_NAMESPACE diff --git a/ePub3/ePub/filter.cpp b/ePub3/ePub/filter.cpp new file mode 100644 index 000000000..70796ae59 --- /dev/null +++ b/ePub3/ePub/filter.cpp @@ -0,0 +1,29 @@ +// +// filter.cpp +// ePub3 +// +// Created by Nelson Leme on 5/28/15. +// Copyright (c) 2015 The Readium Foundation and contributors. All rights reserved. +// +// This program is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// Licensed under Gnu Affero General Public License Version 3 (provided, notwithstanding this notice, +// Readium Foundation reserves the right to license this material under a different separate license, +// and if you have done so, the terms of that separate license control and the following references +// to GPL do not apply). +// +// This program is free software: you can redistribute it and/or modify it under the terms of the GNU +// Affero General Public License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. You should have received a copy of the GNU +// Affero General Public License along with this program. If not, see . + + +#include +#include "filter.h" + +EPUB3_BEGIN_NAMESPACE + +ContentFilterErrorHandlerFn ContentFilter::s_contentFilterErrorHandler = ContentFilter::DefaultContentFilterErrorHandler; + +EPUB3_END_NAMESPACE diff --git a/ePub3/ePub/filter.h b/ePub3/ePub/filter.h index 16f84c255..e2f652561 100644 --- a/ePub3/ePub/filter.h +++ b/ePub3/ePub/filter.h @@ -40,6 +40,19 @@ typedef std::shared_ptr PackagePtr; class ContentFilter; typedef std::shared_ptr ContentFilterPtr; +enum class ContentFilterError : unsigned int +{ + GenericError = 0, + InputStreamCannotBeOpened, + OutputStreamCannotBeSought, + CorruptedResource, + NoLicenseForDecryption, + NotEnoughBytesAvailable + }; + +typedef std::function ContentFilterErrorHandlerFn; + + // ------------------------------------------------------------------------------------------- class ByteRange @@ -287,6 +300,10 @@ class ContentFilter /// No default constructor. ContentFilter() _DELETED_; + static void DefaultContentFilterErrorHandler(const std::string &filterId, unsigned int code, const std::string &message) { }; + + static ContentFilterErrorHandlerFn s_contentFilterErrorHandler; + public: /// /// Copy constructor. @@ -364,6 +381,23 @@ class ContentFilter */ virtual void *FilterData(FilterContext* context, void *data, size_t len, size_t *outputLen) = 0; + /** + Reset the ContentFilteErrorHandler function. + + Any app that is using the Readium SDK (and its associated ContentFilter classes) may + want to set up its own ContentFilterErrorHandler function. This way, the app can be notified + when there is an error in the processing done by the ContentFilter objects. Notice that, + unless this function is called and reset to something meaningful, the default + ContentFilterErrorHandler function is called, which will simply ignore any errors from the + ContentFilter. + + @param contentFilterErrorHandler A new ContentFilterErrorHandler function + */ + static void ResetContentFilterErrorHandler(ContentFilterErrorHandlerFn contentFilterErrorHandler) + { + s_contentFilterErrorHandler = contentFilterErrorHandler; + } + protected: TypeSnifferFn _sniffer; @@ -387,6 +421,47 @@ class ContentFilter @result An object containing per-item data, or nullptr. */ virtual FilterContext *InnerMakeFilterContext(ConstManifestItemPtr item) const { return nullptr; } + + /** + Report an error during the processing done by a ContentFilter. + + When a ContentFilter is processing a sequence of bytes, it may reach an error. In that + case, the ContentFilter will want to report to the underlying app that something went + wrong, so that it can report that to the user. The two overloads of HandleContentFilterError(), + below, do exactly that: they allow an app to receive the error description (as parameters) + whenever an error happens in a given ContentFilter. + + Notice that the first parameter of both overloads is the filterId. The filterId is a UUID (or + GUID) that identifies uniquely a different class of ContentFilter. This way, no matter how many + ContentFilter classes are created all over, there will be a unique way to identify each one of + them, and any app that uses the Readium SDK will not run the risk of mistaking one class of + ContentFilter by another. + + @param filterId A UUID (or GUID) that will uniquely identify a given class of ContentFilter. + @param error One of the possible values of the ContentFilterError enumeration. + @param message An optional text message, containing whatever the ContentFilter wants to say. + */ + static void HandleContentFilterError(const std::string &filterId, ContentFilterError error, const std::string &message) + { + HandleContentFilterError(filterId, (unsigned int)error, message); + } + + /** + Report an error during the processing done by a ContentFilter. + + This overload of HandleContentFilterError does exactly the same as the previous overload, with + one difference: the caller can pass an unsigned integer for error code, instead of the + ContentFilterError enumeration. The reason is that different people writing ContentFilter classes + may decide to use their own error codes for things. + + @param filterId A UUID (or GUID) that will uniquely identify a given class of ContentFilter. + @param error An unsigned integer that corresponds to an error code. + @param message An optional text message, containing whatever the ContentFilter wants to say. + */ + static void HandleContentFilterError(const std::string &filterId, unsigned int errorCode, const std::string &message) + { + s_contentFilterErrorHandler(filterId, errorCode, message); + } };