
package org.apache.cordova.test;

/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 *
 */

import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.CordovaResourceApi.OpenForReadResult;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.PluginEntry;
import org.apache.cordova.test.actions.CordovaWebViewTestActivity;
import org.json.JSONArray;
import org.json.JSONException;

import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Scanner;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.test.ActivityInstrumentationTestCase2;

public class CordovaResourceApiTest extends ActivityInstrumentationTestCase2<CordovaWebViewTestActivity> {

    public CordovaResourceApiTest()
    {
        super(CordovaWebViewTestActivity.class);
    }

    CordovaWebView cordovaWebView;
    CordovaResourceApi resourceApi;

    private CordovaWebViewTestActivity activity;
    String execPayload;
    Integer execStatus;

    protected void setUp() throws Exception {
        super.setUp();
        activity = this.getActivity();
        cordovaWebView = activity.cordovaWebView;
        resourceApi = cordovaWebView.getResourceApi();
        resourceApi.setThreadCheckingEnabled(false);
        cordovaWebView.pluginManager.addService(new PluginEntry("CordovaResourceApiTestPlugin1", new CordovaPlugin() {
            @Override
            public Uri remapUri(Uri uri) {
                if (uri.getQuery() != null && uri.getQuery().contains("pluginRewrite")) {
                    return cordovaWebView.getResourceApi().remapUri(
                            Uri.parse("data:text/plain;charset=utf-8,pass"));
                }
                return null;
            }
            public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
                synchronized (CordovaResourceApiTest.this) {
                    execPayload = args.getString(0);
                    execStatus = args.getInt(1);
                    CordovaResourceApiTest.this.notify();
                }
                return true;
            }
        }));
    }

    private Uri createTestImageContentUri() {
        Bitmap imageBitmap = BitmapFactory.decodeResource(activity.getResources(), R.drawable.icon);
        String stored = MediaStore.Images.Media.insertImage(activity.getContentResolver(),
                imageBitmap, "app-icon", "desc");
        return Uri.parse(stored);
    }

    private void performApiTest(Uri uri, String expectedMimeType, File expectedLocalFile,
            boolean expectRead, boolean expectWrite) throws IOException {
        uri = resourceApi.remapUri(uri);
        assertEquals(expectedLocalFile, resourceApi.mapUriToFile(uri));
        
        try {
            OpenForReadResult readResult = resourceApi.openForRead(uri);
            assertEquals(expectedMimeType, readResult.mimeType);
            readResult.inputStream.read();
            if (!expectRead) {
                fail("Expected getInputStream to throw.");
            }
        } catch (IOException e) {
            if (expectRead) {
                throw e;
            }
        }
        try {
            OutputStream outStream = resourceApi.openOutputStream(uri);
            outStream.write(123);
            if (!expectWrite) {
                fail("Expected getOutputStream to throw.");
            }
            outStream.close();
        } catch (IOException e) {
            if (expectWrite) {
                throw e;
            }
        }
    }

    public void testValidContentUri() throws IOException
    {
        Uri contentUri = createTestImageContentUri();
        File localFile = resourceApi.mapUriToFile(contentUri);
        assertNotNull(localFile);
        performApiTest(contentUri, "image/jpeg", localFile, true, true);
    }

    public void testInvalidContentUri() throws IOException
    {
        Uri contentUri = Uri.parse("content://media/external/images/media/999999999");
        performApiTest(contentUri, null, null, false, false);
    }

    public void testValidAssetUri() throws IOException
    {
        Uri assetUri = Uri.parse("file:///android_asset/www/index.html?foo#bar"); // Also check for stripping off ? and # correctly.
        performApiTest(assetUri, "text/html", null, true, false);
    }

    public void testInvalidAssetUri() throws IOException
    {
        Uri assetUri = Uri.parse("file:///android_asset/www/missing.html");
        performApiTest(assetUri, "text/html", null, false, false);
    }

    public void testFileUriToExistingFile() throws IOException
    {
        File f = File.createTempFile("te s t", ".txt"); // Also check for dealing with spaces.
        try {
            Uri fileUri = Uri.parse(f.toURI().toString() + "?foo#bar"); // Also check for stripping off ? and # correctly.
            performApiTest(fileUri, "text/plain", f, true, true);
        } finally {
            f.delete();
        }
    }

    public void testFileUriToMissingFile() throws IOException
    {
        File f = new File(Environment.getExternalStorageDirectory() + "/somefilethatdoesntexist");
        Uri fileUri = Uri.parse(f.toURI().toString());
        try {
            performApiTest(fileUri, null, f, false, true);
        } finally {
            f.delete();
        }
    }
    
    public void testFileUriToMissingFileWithMissingParent() throws IOException
    {
        File f = new File(Environment.getExternalStorageDirectory() + "/somedirthatismissing" + System.currentTimeMillis() + "/somefilethatdoesntexist");
        Uri fileUri = Uri.parse(f.toURI().toString());
        performApiTest(fileUri, null, f, false, true);
    }

    public void testUnrecognizedUri() throws IOException
    {
        Uri uri = Uri.parse("somescheme://foo");
        performApiTest(uri, null, null, false, false);
    }

    public void testRelativeUri()
    {
        try {
            resourceApi.openForRead(Uri.parse("/foo"));
            fail("Should have thrown for relative URI 1.");
        } catch (Throwable t) {
        }
        try {
            resourceApi.openForRead(Uri.parse("//foo/bar"));
            fail("Should have thrown for relative URI 2.");
        } catch (Throwable t) {
        }
        try {
            resourceApi.openForRead(Uri.parse("foo.png"));
            fail("Should have thrown for relative URI 3.");
        } catch (Throwable t) {
        }
    }
    
    public void testPluginOverride() throws IOException
    {
        Uri uri = Uri.parse("plugin-uri://foohost/android_asset/www/index.html?pluginRewrite=yes");
        performApiTest(uri, "text/plain", null, true, false);
    }

    public void testMainThreadUsage() throws IOException
    {
        Uri assetUri = Uri.parse("file:///android_asset/www/index.html");
        resourceApi.setThreadCheckingEnabled(true);
        try {
            resourceApi.openForRead(assetUri);
            fail("Should have thrown for main thread check.");
        } catch (Throwable t) {
        }
    }
    
    
    public void testDataUriPlain() throws IOException
    {
        Uri uri = Uri.parse("data:text/plain;charset=utf-8,pass");
        OpenForReadResult readResult = resourceApi.openForRead(uri);
        assertEquals("text/plain", readResult.mimeType);
        String data = new Scanner(readResult.inputStream, "UTF-8").useDelimiter("\\A").next();
        assertEquals("pass", data);
    }
    
    public void testDataUriBase64() throws IOException
    {
        Uri uri = Uri.parse("data:text/js;charset=utf-8;base64,cGFzcw==");
        OpenForReadResult readResult = resourceApi.openForRead(uri);
        assertEquals("text/js", readResult.mimeType);
        String data = new Scanner(readResult.inputStream, "UTF-8").useDelimiter("\\A").next();
        assertEquals("pass", data);
    }
    
    public void testWebViewRequestIntercept() throws IOException
    {
        cordovaWebView.sendJavascript(
            "var x = new XMLHttpRequest;\n" +
            "x.open('GET', 'file://foo?pluginRewrite=1', false);\n" + 
            "x.send();\n" + 
            "cordova.require('cordova/exec')(null,null,'CordovaResourceApiTestPlugin1', 'foo', [x.responseText, x.status])");
        execPayload = null;
        execStatus = null;
        try {
            synchronized (this) {
                this.wait(2000);
            }
        } catch (InterruptedException e) {
        }
        assertEquals("pass", execPayload);
        assertEquals(execStatus.intValue(), 200);
    }
    
    public void testWebViewWhiteListRejection() throws IOException
    {
        cordovaWebView.sendJavascript(
            "var x = new XMLHttpRequest;\n" +
            "x.open('GET', 'http://foo/bar', false);\n" + 
            "x.send();\n" + 
            "cordova.require('cordova/exec')(null,null,'CordovaResourceApiTestPlugin1', 'foo', [x.responseText, x.status])");
        execPayload = null;
        execStatus = null;
        try {
            synchronized (this) {
                this.wait(2000);
            }
        } catch (InterruptedException e) {
        }
        assertEquals("", execPayload);
        assertEquals(execStatus.intValue(), 404);
    }    
}
