I have an application that uses a webview in order to display content and the Javascript calls are the controller of my application.
In order to provide a level of security I obfuscated the code. This is not enough as I would like to encrypt the html and js files and then decrypt them at runtime. I packed the apk file with these resources encrypted with RC4 algorithm. When loading the files, I am decrypting the javascript files, load them and then decrypt the html file and load it. However this doesn't work as the webcontent displays a message in the form of: the web page at data:text/html might be temporarily down or it may have moved permanently, etc, etc.
I overloaded onLoadResource in order to see what content is loaded and I can see it loads the Javascript content, but the content loaded is html escaped also.
My questions are:
1. How to secure the html and javascript files (located in assets folder) in order to not be accessible?
2. In case my approach is correct, has anyone any idea on what I am doing wrong?
Thanks!
Below is the code that decrypts and loads the resources:
protected void loadWebContent() {
checkEncryptionEnabled();
loadJSFiles();
logger.info("Loaded js ... going for html");
loadAssetFile("www/index.html", "text/html");
}
private void loadJSFiles() {
String[] jsFilesArray = { "app.js", "iscroll.js", "iui.js", "json.js" };
for (String js : jsFilesArray) {
loadAssetFile("www/js/" + js, "application/javascript");
}
}
private void loadAssetFile(String filePath, String mimeType) {
AssetManager assetMgr = getAssets();
InputStream is = null;
try {
is = assetMgr.open(filePath);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] temp = new byte[512];
int bytesRead = -1;
while ((bytesRead = is.read(temp)) > 0) {
baos.write(temp, 0, bytesRead);
}
byte[] encrypted = baos.toByteArray();
String content = null;
/**
* true
* */
if (Config.ENCRYPTION_ENABLED) {
byte[] decrypted = new RC4Encrypter("rc4_key").rc4(encrypted);
content = new String(decrypted, "utf-8");
} else {
content = new String(encrypted, "utf-8");
}
/**
* The webview to use
* */
if("application/javascript".equals(mimeType)) {
webContent.loadUrl("javascript:" + content);
} else {
webContent.loadData(content, mimeType, "utf-8");
}
} catch (IOException ex) {
logger.error(null, ex);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
}
found the answer for the second question question instead of: webContent.loadData(content, mimeType, "utf-8"); I used: webContent.loadDataWithBaseURL("file:///android_asset/www/", content, mimeType, "utf-8", null); Content is shown with no problems ... However, the first question kind of stands and not; but considering there was no answer for more than a year, I'll consider encrypting data is OK.
Data encryption is OK as long as you can also keep the decryption key confidential, which is not the case in the above code. The hardcoded decryption key can be easily spotted after decompiling the DEX files embedded inside the APK.
If you want to hide the application logic inside the HTML and JavaScript files and if that application logic doesn't require offline capabilities then you could outsource the code of that application logic on a server.
From here you have two choices:
Load the application code dynamically from the server whenever
you need it (and run the application code on the client).
Implement the application logic on the server side, e.g., as a
web service (and run the application code on the server, the client
knows only how to call the web service)
The short answer to your first question is that there is no methodology or technology to perfectly protect your application. I recommend to you to take a look at How to avoid reverse engineering of an APK file? for an overview of possible protection methods.
Related
I am trying to load SVF file on Autodesk forge viewer locally in Xamarin.Android. I copied the content to my project Assets/html folder. My code to load the content looks like this.
In MyWebViewClient.cs
public WebResourceResponse ShouldInterceptRequest(WebView webView, IWebResourceRequest request)
{
try
{
Android.Net.Uri url = request.Url;
//Uri uri = url;
String path = url.Path;
if (path.StartsWith("/android_asset/"))
{
try
{
AssetManager assetManager = this.context.Assets;
String relPath = path.Replace("/android_asset/", "").Replace("gz", "gz.mp3");
//InputStream stream = assetManager.Open(relPath);
return new WebResourceResponse(null, null, assetManager.Open(relPath));
}
catch (IOException ex)
{
String str = ex.Message;
}
}
}
catch (Exception ex) { }
return null;
}
Then in my Activity.cs
SetContentView(Resource.Layout.webview);
var wbMain = FindViewById<WebView>(Resource.Id.webView1);
wbMain.Settings.DomStorageEnabled = true;
wbMain.Settings.JavaScriptEnabled = true;
wbMain.Settings.AllowFileAccessFromFileURLs = true;
wbMain.Settings.AllowUniversalAccessFromFileURLs = true;
var customWebViewClient = new MyWebViewClient(BaseContext);
customWebViewClient.OnPageLoaded += MyWebViewClient_OnPageLoaded;
wbMain.SetWebViewClient(customWebViewClient);
wbMain.LoadUrl("file:///android_asset/html/index.html");
This only loads the side views not the main viewer.
Whats the reason for this and how can I resolve this?
Please note that the sample is a bit outdated and there have been some changes in our legal terms since then. Currently, the legal T&C state that all viewer assets (JS, CSS, icons, images, etc.) must be coming from the Autodesk domain.
If you need to be able to run your viewer-based app in "temporarily offline" scenarios (for example, on a construction site), I'd suggest that you look at the following blog post: https://forge.autodesk.com/blog/disconnected-workflows. This approach (using Service Workers and Cache API) is consistent with the legal requirements.
My Android app is showing an html5 e-book in a WebView.
I have a zipped file containing an e-book with all its resources: text, images and audio (mp3 files).
In order to unzip the book I use shouldInterceptRequest(), which intercepts the file:///... requests, and returns the data via a WebResourceResponse object. The code works fine for text and images.
When I get to audio resources, I get runtime errors, and the audio file is not played.
Note: I do see the unzipped file is returned with the correct size (about 10MB).
Error messages I get:
cr_MediaResourceGetter File does not exist
cr_MediaResourceGetter Unable to configure metadata extractor
My HTML code for the audio :
<div xmlns="http://www.w3.org/1999/xhtml">
<p style="text-align:center;margin:0px;">
<audio controls="controls" src="../Audio/01-AudioTrack-01.mp3">Your browser does not support the audio tag.</audio>
<br />
</p>
</div>
My Android Code:
setWebViewClient(new WebViewClient()
{
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, final String url)
{
String urlWithoutAnchor = URLUtil.stripAnchor(url);
String fileName = urlWithoutAnchor;
try {
byte [] resource = tbxPool.getResource(fileName);
/* SIMPLE VERSION without calling setResponseHeaders():
return new WebResourceResponse(mimeType, "UTF-8", new ByteArrayInputStream(resource));
*/
WebResourceResponse returnedMediaResource = new WebResourceResponse(mimeType, "UTF-8", new ByteArrayInputStream(resource));
if (mimeType.toLowerCase().startsWith("audio")) {
Map<String, String> responseHeaders = new HashMap<String, String>();
responseHeaders.put("Content-Type", mimeType);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//2CLEAN
returnedMediaResource.setResponseHeaders(responseHeaders);
Logger.v(TAG, "Response Headers added to audio resource");
}
else {
//TODO: Handle else for API<21. Toast?
}
}
return returnedMediaResource;
} catch (IOException e) {
Logger.e(TAG, "failed to load resource "+fileName,e);
return null;
}
}
}
Environment
Android 6.0.1 (Nexus 5)
Android System WebView version 47
Requirement Clarification
The audio is to play in browser like an html5 document should, without laucnhing external player.
Question:
What am I doing wrong?! Many Thanks in advance!
The workaround I found to this problem is not elegant, but it's the only one that worked for me: Write the audio file to sd card :(.
Stage 1): When shouldInterceptRequest() is called with a chapter url.
The chapter is intercepted first (before the other chapter resources (images, audio, fonts, ..) are intercepted.
When the chapter is intercepted we search the html for the <audio> tag. If found, we replace the relative path (e.g. SRC="../Audio/abc.mp3")
with an absolute path (e.g. SRC="/storage/tmp/abc.mp3")
Stage 2): When shouldInterceptRequest() is called with an audio url.
Your attention. Like all workarounds this is a bit tricky (but works!):
After Stage 1) the audio url will be an absolute url (the absolute url is what is now written in the modified html).
We now have to do 2 things:
a) read the audio file from the zipped epub.
To do this we need to "fool" the code, and read the audio file from its original zipped relative url, e.g. "../Audio/abc.mp3" in our example
(although shouldInterceptRequest has been called with "/storage/tmp/abc.mp3").
b) After reading the zipped audio file, write it to the storage (sdcard)
Stage 3) When shouldInterceptRequest() is called with a chapter url,
We delete the temp audio files
Note: If you follow the code, you will see this is Step 0) in shouldInterceptRequest(), executed before stage 1), but I found it clearer explained as above.
if (isChapterFile(mimeType)) {
deleteTempFiles(); // this line is stage 3)
changeAudioPathsInHtml(tzis); // this line is stage 1)
This is the code:
setWebViewClient(new WebViewClient()
{
private String tmpPath = TbxApplication.getAppPath(null) + "/tmp/";
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, final String url)
{
Logger.d(TAG, "in shouldInterceptRequest for " + url);
String urlWithoutAnchor = URLUtil.stripAnchor(url);
String mimeType = StringUtils.getFileMimeType(urlWithoutAnchor);
String urlWithoutBase; //the url stripped from leading 'epubBaseUrl' (base url for example:"file:///storage/.../123456.tbx")
if (isAudioFile(mimeType)) { //write AUDIO file to phone storage. See AUDIO WORKAROUND DOCUMENTATION
String storagePath = StringUtils.truncateFileScheme(url); //WebView calls shoudlInterceptRequest() with "file://..."
try {
String oEBPSAudioPath = storagePathToOEBPSAudioPath(storagePath); //e.g. change"/storage/tmp" to "OEBPS/Audio/abc.mp3"
byte[] audioBytes = tbxPool.getMedia(oEBPSAudioPath);
FileUtils.writeByteArrayToFile(audioBytes, storagePath); //TODO: To be strict, write in separate thread
Logger.d(TAG, String.format("%s written to %s", oEBPSAudioPath, storagePath));
return null;//webView will read resource from file
//Note: return new WebResourceResponse("audio/mpeg", "UTF-8", new ByteArrayInputStream(audioBytes));
//did NOT work,so we had to change html for audio to point to local storage & write to disk
//see AUDIO WORKAROUND DOCUMENTATION in this file
} catch (Exception e) {
Logger.e(TAG,e.getMessage());
return null;
}
}
.....
else {
if (isChapterFile(mimeType)) { //This is a CHAPTER
deleteTempFiles(); //Loading a new chapter. Delete previous chapter audio files. See AUDIO WORKAROUND DOCUMENTATION in this file
InputStream htmlWithChangedAudioPaths = changeAudioPathsInHtml(tzis); //see AUDIO WORKAROUND DOCUMENTATION in this file
WebResourceResponse webResourceResponse = new WebResourceResponse(mimeType, "UTF-8", htmlWithChangedAudioPaths);
return webResourceResponse;
}
//Changes relative paths of audio files, to absolute paths on storage
//see AUDIO WORKAROUND DOCUMENTATION in this file
private InputStream changeAudioPathsInHtml(InputStream inputStream) {
String inputString = StringUtils.inputStreamToString(inputStream);
String outputString = inputString.replaceAll("\"../Audio/", "\"" + tmpPath);// e.g. SRC="../Audio/abc.mp3" ==>SRC="/sdcard/tmp/abc.mp3" //where '*' stands for multiple whitespaces would be more elegant
return StringUtils.stringToInputStream(outputString);
}
/** Example:
* storagePath="/storage/tmp/abc.mp3
* Returns: "OEBPS/Audio/abc.mp3"*/
private String storagePathToOEBPSAudioPath(String storagePath){
String fileName = StringUtils.getFileName(storagePath);
String tbxOEBPSAudioPath = "OEBPS/Audio/" + fileName;
return tbxOEBPSAudioPath;
}
public static void writeByteArrayToFile(byte[] byteArray, String outPath) {
try {
File file = new File(outPath);
FileOutputStream fos = new FileOutputStream(file);
fos.write(byteArray);
fos.close();
} catch (IOException e) {
Logger.e(TAG, String.format("Could not write %s", outPath));
}
}
I need to read a text stream by using StreamReader from file on android platform. File is about 100k lines, so even editor is getting stuck if i try to load it all to TextAsset or if i use WWW.
I simply need to read that file line by line without loading it all to a string. Then i'll do a tree generation from the lines that i got from the file. (But probably that part doesn't matter, i just need help on file reading part.)
I'm giving the code that i wrote down below. It works perfectly on editor, but fails on android.
I would be glad if anyone tell me, what am i missing.
(ps. english is not my native and this is my first question on the site. so sorry for the any mistakes that i may have done.)
private bool Load(string fileName)
{
try
{
string line;
string path = Application.streamingAssetsPath +"/";
StreamReader theReader = new StreamReader(path + fileName +".txt", Encoding.UTF8);
using (theReader)
{
{
line = theReader.ReadLine();
linesRead++;
if (line != null)
{
tree.AddWord(line);
}
}
while (line != null);
theReader.Close();
return true;
}
}
catch (IOException e)
{
Debug.Log("{0}\n" + e.Message);
exception = e.Message;
return false;
}
}
You can't use Application.streamingAssetsPath as a path on Android because streaming assets are stored within the JAR file with the application.
From http://docs.unity3d.com/Manual/StreamingAssets.html:
Note that on Android, the files are contained within a compressed .jar
file (which is essentially the same format as standard zip-compressed
files). This means that if you do not use Unity’s WWW class to
retrieve the file then you will need to use additional software to see
inside the .jar archive and obtain the file.
Use WWW like this in a coroutine:
WWW data = new WWW(Application.streamingAssetsPath + "/" + fileName);
yield return data;
if(string.IsNullOrEmpty(data.error))
{
content = data.text;
}
Or, if you really want to keep it simple (and your file is only a few 100k, stick it in a resource folder:
TextAsset txt = (TextAsset)Resources.Load(fileName, typeof(TextAsset));
string content = txt.text;
When another application is sending a file to my app, I get a Uri via the intent.getExtras().get(EXTRA_STREAM) property. I can then get the bytes of the file using an inputstream : new BufferedInputStream(activity.getContentResolver().openInputStream(uri));
Everything's OK and working so far. Now I'd like to show some kind of progress to my user, but I'm not sure of how to get the total number of bytes of the file without reading the stream completely beforehand (which would defeat the whole purpose of the progress bar) ...
I tried ParcelFileDescriptor fileDesc = activity.getContentResolver().openFileDescriptor(uri, "r"); but this only works with uris of type file://....
For example If I receive a file from Skydrive I get a content://.. Uri, as in : content://com.microsoft.skydrive.content.external/external_property/10C32CC94ECB90C4!155/Sunset.480p.mp4
On such Uri I get (unsurprisingly) a "FileNotFoundException : Not a whole file" exception.
Any sure fire way to get the total size of the stream of data I will get ?
Even though InputStream.available() is (almost) never a recommended way of getting file size, it might be a viable solution in your case.
The content is already available locally. A server is not involved. So, the following should return the exact file size:
try {
InputStream inputStream = getContentResolver().openInputStream(uri);
Log.i("TEST", "File Size: " + inputStream.available());
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
}
I tested this with SkyDrive and Dropbox. The file sizes returned were correct.
There is no general solution for getting the size of a stream, other than reading the entire stream. This is easily proven: One could create a web server that, for some URL, generates a random stream of text that is terminated at a random time. (In fact, I'm sure such URLs exist, whether by design or not :-) In such a case, the size of the stream isn't known until the last byte has been generated, never mind received.
So, the stream size, if it is sent by the server at all, has to be sent in an application-specific manner.
I've never worked with SkyDrive, but a google search for its API turned up this link, which has the following example for Android Java apps:
public void readFile() {
String fileId = "file.a6b2a7e8f2515e5e.A6B2A7E8F2515E5E!141";
client.getAsync(fileId, new LiveOperationListener() {
public void onError(LiveOperationException exception, LiveOperation operation) {
resultTextView.setText("Error reading file: " + exception.getMessage());
}
public void onComplete(LiveOperation operation) {
JSONObject result = operation.getResult();
String text = "File info:" +
"\nID = " + result.optString("id") +
"\nName = " + result.optString("name");
resultTextView.setText(text);
}
});
}
Based on other examples on that page, I would guess that something like result.optString("size") (or maybe result.optInt("size") ?) would give you the size of the file.
I created a simple html file that loads some images from my local hard-drive (ubuntu).
It is enough to put
<img src=/home/user/directory/image.jpg></img>
Now I need to know if it is the same when Html5 is displayed on a tablet like Android or iOS, or Html5 is used in offline app.
I mean, if html5 can load an image from the device's filesystem just like on my computer, without localStorage or sessionStorage.
If you deploy the application as native application it is possible (wrap it with Phonegap).
For saved HTML files it is not possible.
On Android, it can be done even though it looks a bit tricky at first. Say you have defined a WebView in your layout.xml, which you want to fill with an html file shipped with your application, which in turn should import a png also shipped with your application.
The trick is to put the html file into res/raw and the png into assets.
Example.
Say you have hello.html which should include buongiorno.png.
Within your project, say MyProject, place buongiorno.png into MyProject/assets.
The hello.html goes into MyProject/res/raw (because we want to avoid having it 'optimised' by the android resource compiler), and could look like this:
<html>
<head></head>
<body>
<img src="file:///android_asset/buongiorno.png"/>
<p>Hello world.</p>
</body>
</html>
In your java code, you would put this code:
WebView w = (WebView) findViewById(R.id.myWebview);
String html = getResourceAsString(context, R.raw.hello);
if (html != null) {
w.loadDataWithBaseURL(null, html, "text/html", "UTF-8", null);
}
where getResourceAsString() is defined as follows:
public static String getResourceAsString(Context context, int resid) throws NotFoundException {
Resources resources = context.getResources();
InputStream is = resources.openRawResource(resid);
try {
if (is != null && is.available() > 0) {
final byte[] data = new byte[is.available()];
is.read(data);
return new String(data);
}
} catch (IOException ioe) {
throw new RuntimeException(ioe);
} finally {
try {
is.close();
} catch (IOException ioe) {
// ignore
}
}
return null;
}