For a single Web app, the loaded url generally does not guarantee its security. So how to deal with url security issues.
Let's take a look at how PhoneGap works.
PhoneGap uses a whitelist. The url in the whitelist is considered safe, and the url not in the whitelist is insecure. For secure URLs, the Web app of PhoneGap will open directly. For insecure URLs, It will be opened through a browser.
So how to add a white list? PhoneGap needs to be set in the configuration file res/xml/config. xml, as follows:
<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 *"/> is the added whitelist. You only need to add * to the URL and add the preceding format to the configuration file.
So how does PhoneGap implement a whitelist? Let's take a look at the source code: CordovaWebView. java. CordovaWebView is the base class of the displayed WebView. It loads configuration items in the configuration file during initialization. The source code is as follows:
/** * 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); } }
When parsing an xml file, the content in the origin tag is added to the whitelist and the addWhiteListEntry method is called. Let's take a look at the source code of the addWhiteListEntry method:
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); } }
We can see that the url in the whiteList is added to the whiteList attribute after regular expression parsing, while whiteList is an ArrayList attribute.
So how does the PhoneGap Web app use the whitelist when displaying webpages? Let's continue to look at the following source code. This is the method that will be called when a webpage is loaded:
/** * 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); } } }
We can see that isUrlWhiteListed and other methods are used to determine whether the url is in the White List or secure, and then the secure url is directly loaded into the Web app through loadUrl, for a url that PhoneGap deems unsafe, it will open the browser to load the webpage in the Intent form.
Next I will post the source code of the isUrlWhiteListed method:
/** * 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; }