Android PhoneGap源码分析——白名单

对于单独的Web app应用来说,加载进来的url一般不能保证它的安全性。那么如何来处理url安全性的问题呢。

让我们来看看PhoneGap是如何做的。

PhoneGap采用了白名单的形式,认为在白名单中的url认为是安全的,不在白名单中的url是不安全的。对于安全的url,PhoneGap的Web app会直接打开,对于不安全的url,会通过浏览器打开。

那么怎么增加白名单呢?PhoneGap是需要在配置文件res/xml/config.xml中设置,如下:

 <cordova>
- <!--     access elements control the Android whitelist.
    Domains are assumed blocked unless set otherwise

  -->
  <access origin="http://127.0.0.1*" />
- <!--  allow local pages
  -->
- <!--  <access origin="https://example.com" /> allow any secure requests to example.com
  -->
- <!--  <access origin="https://example.com" subdomains="true" /> such as above, but including subdomains, such as www
  -->
  <access origin=".*" />
  <log level="DEBUG" />
  <preference name="useBrowserHistory" value="false" />
  <preference name="exit-on-suspend" value="false" />
- <plugins>
  <plugin name="App" value="org.apache.cordova.App" />
  <plugin name="Geolocation" value="org.apache.cordova.GeoBroker" />
  <plugin name="Device" value="org.apache.cordova.Device" />
  <plugin name="Accelerometer" value="org.apache.cordova.AccelListener" />
  <plugin name="Compass" value="org.apache.cordova.CompassListener" />
  <plugin name="Media" value="org.apache.cordova.AudioHandler" />
  <plugin name="Camera" value="org.apache.cordova.CameraLauncher" />
  <plugin name="Contacts" value="org.apache.cordova.ContactManager" />
  <plugin name="File" value="org.apache.cordova.FileUtils" />
  <plugin name="NetworkStatus" value="org.apache.cordova.NetworkManager" />
  <plugin name="Notification" value="org.apache.cordova.Notification" />
  <plugin name="Storage" value="org.apache.cordova.Storage" />
  <plugin name="Temperature" value="org.apache.cordova.TempListener" />
  <plugin name="FileTransfer" value="org.apache.cordova.FileTransfer" />
  <plugin name="Capture" value="org.apache.cordova.Capture" />
  <plugin name="Battery" value="org.apache.cordova.BatteryListener" />
  <plugin name="SplashScreen" value="org.apache.cordova.SplashScreen" />
  <plugin name="Echo" value="org.apache.cordova.Echo" />
  <plugin name="Globalization" value="org.apache.cordova.Globalization" />
  </plugins>
  </cordova>

其中,<access origin="http://127.0.0.1*" />就是加的白名单,我们只需要将网址后面加上*,上格式加进配置文件即可。

那么PhoneGap又是如何实现白名单的呢?让我们看一下源代码:CordovaWebView.java。CordovaWebView是显示的WebView的基类。它在初始化时,会加载配置文件的配置项,源代码如下:

    /**
     * Load Cordova configuration from res/xml/cordova.xml.
     * Approved list of URLs that can be loaded into DroidGap
     *      <access origin="http://server regexp" subdomains="true" />
     * Log level: ERROR, WARN, INFO, DEBUG, VERBOSE (default=ERROR)
     *      <log level="DEBUG" />
     */
    private void loadConfiguration() {
        int id = getResources().getIdentifier("config", "xml", this.cordova.getActivity().getPackageName());
        if(id == 0)
        {
            id = getResources().getIdentifier("cordova", "xml", this.cordova.getActivity().getPackageName());
            Log.i("CordovaLog", "config.xml missing, reverting to cordova.xml");
        }
        if (id == 0) {
            LOG.i("CordovaLog", "cordova.xml missing. Ignoring...");
            return;
        }
        XmlResourceParser xml = getResources().getXml(id);
        int eventType = -1;
        while (eventType != XmlResourceParser.END_DOCUMENT) {
            if (eventType == XmlResourceParser.START_TAG) {
                String strNode = xml.getName();
                if (strNode.equals("access")) {
                    String origin = xml.getAttributeValue(null, "origin");
                    String subdomains = xml.getAttributeValue(null, "subdomains");
                    if (origin != null) {
                        this.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
                    }
                }
                else if (strNode.equals("log")) {
                    String level = xml.getAttributeValue(null, "level");
                    LOG.i("CordovaLog", "Found log level %s", level);
                    if (level != null) {
                        LOG.setLogLevel(level);
                    }
                }
                else if (strNode.equals("preference")) {
                    String name = xml.getAttributeValue(null, "name");
                    String value = xml.getAttributeValue(null, "value");

                    LOG.i("CordovaLog", "Found preference for %s=%s", name, value);
                    Log.d("CordovaLog", "Found preference for " + name + "=" + value);

                    // Save preferences in Intent
                    this.cordova.getActivity().getIntent().putExtra(name, value);
                }
            }
            try {
                eventType = xml.next();
            } catch (XmlPullParserException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        // Init preferences
        if ("true".equals(this.getProperty("useBrowserHistory", "false"))) {
            this.useBrowserHistory = true;
        }
        else {
            this.useBrowserHistory = false;
        }

        if ("true".equals(this.getProperty("fullscreen", "false"))) {
            this.cordova.getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
            this.cordova.getActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        }
    }

在解析xml文件时,会将origin标签中的内容加入白名单,调用的是addWhiteListEntry方法。下面我们来看看addWhiteListEntry方法的源代码:

    public void addWhiteListEntry(String origin, boolean subdomains) {
        try {
            // Unlimited access to network resources
            if (origin.compareTo("*") == 0) {
                LOG.d(TAG, "Unlimited access to network resources");
                this.whiteList.add(Pattern.compile(".*"));
            } else { // specific access
                // check if subdomains should be included
                // TODO: we should not add more domains if * has already been added
                if (subdomains) {
                    // XXX making it stupid friendly for people who forget to include protocol/SSL
                    if (origin.startsWith("http")) {
                        this.whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://(.*\\.)?")));
                    } else {
                        this.whiteList.add(Pattern.compile("^https?://(.*\\.)?" + origin));
                    }
                    LOG.d(TAG, "Origin to allow with subdomains: %s", origin);
                } else {
                    // XXX making it stupid friendly for people who forget to include protocol/SSL
                    if (origin.startsWith("http")) {
                        this.whiteList.add(Pattern.compile(origin.replaceFirst("https?://", "^https?://")));
                    } else {
                        this.whiteList.add(Pattern.compile("^https?://" + origin));
                    }
                    LOG.d(TAG, "Origin to allow: %s", origin);
                }
            }
        } catch (Exception e) {
            LOG.d(TAG, "Failed to add origin %s", origin);
        }
    }

我们可以看到,它用正则表达式解析后将白名单中的url加入到whiteList这个属性中,而whiteList是个ArrayList类型的属性。

那么PhoneGap的Web app在显示网页时又是如何利用白名单的呢?让我们继续来看下面的源代码,这是加载网页时会调用的方法:

    /**
     * Load the specified URL in the Cordova webview or a new browser instance.
     *
     * NOTE: If openExternal is false, only URLs listed in whitelist can be loaded.
     *
     * @param url           The url to load.
     * @param openExternal  Load url in browser instead of Cordova webview.
     * @param clearHistory  Clear the history stack, so new page becomes top of history
     * @param params        DroidGap parameters for new app
     */
    public void showWebPage(String url, boolean openExternal, boolean clearHistory, HashMap<String, Object> params) {
        LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap", url, openExternal, clearHistory);

        // If clearing history
        if (clearHistory) {
            this.clearHistory();
        }

        // If loading into our webview
        if (!openExternal) {

            // Make sure url is in whitelist
            if (url.startsWith("file://") || url.indexOf(this.baseUrl) == 0 || isUrlWhiteListed(url)) {
                // TODO: What about params?

                // Clear out current url from history, since it will be replacing it
                if (clearHistory) {
                    this.urls.clear();
                }

                // Load new URL
                this.loadUrl(url);
            }
            // Load in default viewer if not
            else {
                LOG.w(TAG, "showWebPage: Cannot load URL into webview since it is not in white list.  Loading into browser instead. (URL=" + url + ")");
                try {
                    Intent intent = new Intent(Intent.ACTION_VIEW);
                    intent.setData(Uri.parse(url));
                    cordova.getActivity().startActivity(intent);
                } catch (android.content.ActivityNotFoundException e) {
                    LOG.e(TAG, "Error loading url " + url, e);
                }
            }
        }

        // Load in default view intent
        else {
            try {
                Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.setData(Uri.parse(url));
                cordova.getActivity().startActivity(intent);
            } catch (android.content.ActivityNotFoundException e) {
                LOG.e(TAG, "Error loading url " + url, e);
            }
        }
    }

我们可以看到,里面会用isUrlWhiteListed等方法判断该url是否在白名单中,或者是否安全,然后对安全的url直接通过loadUrl来 加载进该Web app,对于PhoneGap认为不安全的url会通过发Intent的形式打开浏览器加载该网页。

下面再贴一段isUrlWhiteListed方法的源代码:

    /**
     * Determine if URL is in approved list of URLs to load.
     *
     * @param url
     * @return
     */
    public boolean isUrlWhiteListed(String url) {

        // Check to see if we have matched url previously
        if (this.whiteListCache.get(url) != null) {
            return true;
        }

        // Look for match in white list
        Iterator<Pattern> pit = this.whiteList.iterator();
        while (pit.hasNext()) {
            Pattern p = pit.next();
            Matcher m = p.matcher(url);

            // If match found, then cache it to speed up subsequent comparisons
            if (m.find()) {
                this.whiteListCache.put(url, true);
                return true;
            }
        }
        return false;
    }

Android PhoneGap源码分析——白名单

时间: 2024-11-10 07:47:01

Android PhoneGap源码分析——白名单的相关文章

[Android]Fragment源码分析(一) 构造

Fragment是Android3.0之后提供的api,被大家广泛所熟知的主要原因还是因为随即附带的ViewPager控件.虽然我并不喜欢用它,但是它确实是一个相对不错的控件.还是我的一贯作风,我将从源码上向大家展示什么是Fragment.我们先写一个简单的代码对Fragment有个直观的认识:(为了保证我们方便调试,我们可以直接使用V4提供的源码包) FragmentTransaction t = getSupportFragmentManager().beginTransaction();

[Android]Volley源码分析(四)

上篇中有提到NetworkDispatcher是通过mNetwork(Network类型)来进行网络访问的,现在来看一下关于Network是如何进行网络访问的. Network部分的类图: Network有一个实现类BasicNetwork,它有一个mHttpStack的属性,实际的网络请求是由这个mHttpStack来进行的,看BasicNetwork的performRequest()方法, 1 @Override 2 public NetworkResponse performRequest

android 从源码分析为什么Listview初次显示时没滚动却自动调用onScroll方法的原因

我们做Listview的分批加载时,需要为Listview调用setOnScrollListener(具体代码可见我上一篇博客) 可是,我们会发现,当运行程序时,listview明明没有滚动,那为什么系统会调用onScroll方法呢?(补充:此时onScrollStateChanged并不会调用) 我们先看setOnScrollListener源码: public void setOnScrollListener(OnScrollListener l) { mOnScrollListener =

[Android]Fragment源码分析(三) 事务

Fragment管理中,不得不谈到的就是它的事务管理,它的事务管理写的非常的出彩.我们先引入一个简单常用的Fragment事务管理代码片段: FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction(); ft.add(R.id.fragmentContainer, fragment, "tag"); ft.addToBackStack("<span style="fo

[Android]Volley源码分析(二)Cache

Cache作为Volley最为核心的一部分,Volley花了重彩来实现它.本章我们顺着Volley的源码思路往下,来看下Volley对Cache的处理逻辑. 我们回想一下昨天的简单代码,我们的入口是从构造一个Request队列开始的,而我们并不直接调用new来构造,而是将控制权反转给Volley这个静态工厂来构造. com.android.volley.toolbox.Volley: public static RequestQueue newRequestQueue(Context conte

[Android]Volley源码分析(叁)Network

如果各位看官仔细看过我之前的文章,实际上Network这块的只是点小功能的补充.我们来看下NetworkDispatcher的核心处理逻辑: <span style="font-size:18px;">while (true) { try { // Take a request from the queue. request = mQueue.take(); } catch (InterruptedException e) { // We may have been int

[Android]Volley源码分析(肆)应用

通过前面的讲述,相信你已经对Volley的原理有了一定了解.本章将举一些我们能在应用中直接用到的例子,第一个例子是 NetworkImageView类,其实NetworkImageView顾名思义就是将异步的操作封装在了控件本身,这种设计可以充分保留控件的移植性和维护性.NetworkImageView通过调用setImageUrl来指定具体的url: public void setImageUrl(String url, ImageLoader imageLoader) { mUrl = ur

Android IntentService 源码分析

IntentService简介: IntentService是一个通过Context.startService(Intent)启动可以处理异步请求的Service,使用时你只需要继承IntentService和重写其中的onHandleIntent(Intent)方法接收一个Intent对象,该服务会在异步任务完成时自动停止服务. 所有的请求的处理都在IntentService内部工作线程中完成,它们会顺序执行任务(但不会阻塞主线程的执行),某一时刻只能执行一个异步请求. IntnetServi

[Android] Volley源码分析(一)体系结构

Volley:google出的一个用于异步处理的框架.由于本身的易用性和良好的api,使得它能得以广泛的应用.我还是一如既往从源码的方向上来把控它.我们先通过一段简单的代码来了解Volley RequestQueue queue = Volley.newRequestQueue(this); ImageRequest imagerequest = new ImageRequest(url, new Response.Listener<Bitmap>(){ @Override public vo