12 min read

In this article by Ivo Balbaert author of Dart Cookbook, we will cover the following recipes:

  • Sanitizing HTML
  • Using a browser’s local storage
  • Using an application cache to work offline
  • Preventing an onSubmit event from reloading the page

(For more resources related to this topic, see here.)

Sanitizing HTML

We’ve all heard of (or perhaps even experienced) cross-site scripting (XSS) attacks, where evil minded attackers try to inject client-side script or SQL statements into web pages. This could be done to gain access to session cookies or database data, or to get elevated access-privileges to sensitive page content. To verify an HTML document and produce a new HTML document that preserves only whatever tags are designated safe is called sanitizing the HTML.

How to do it…

Look at the web project sanitization. Run the following script and see how the text content and default sanitization works:

  1. See how the default sanitization works using the following code:
    var elem1 = new Element.html('<div class="foo">content</div>');
    document.body.children.add(elem1);
    var elem2 = new Element.html('<script class="foo">evil content</script><p>ok?</p>');
    document.body.children.add(elem2);

    The text content and ok? from elem1 and elem2 are displayed, but the console gives the message Removing disallowed element <SCRIPT>. So a script is removed before it can do harm.

  2. Sanitize using HtmlEscape, which is mainly used with user-generated content:
    import 'dart:convert' show HtmlEscape;

    In main(), use the following code:

    var unsafe = '<script class="foo">evil   content</script><p>ok?</p>';
    var sanitizer = const HtmlEscape();
    print(sanitizer.convert(unsafe));

    This prints the following output to the console:

    &lt;script class=&quot;foo&quot;&gt;evil   content&lt;&#x2F;script&gt;&lt;p&gt;ok?&lt;&#x2F;p&gt;
  3. Sanitize using node validation. The following code forbids the use of a <p> tag in node1; only <a> tags are allowed:
    var html_string = '<p class="note">a note aside</p>';
    var node1 = new Element.html(
           html_string,
           validator: new NodeValidatorBuilder()
             ..allowElement('a', attributes: ['href'])
         );

    The console prints the following output:

    Removing disallowed element <p>
    Breaking on exception: Bad state: No elements
  4. A NullTreeSanitizer for no validation is used as follows:
    final allHtml = const NullTreeSanitizer();
    class NullTreeSanitizer implements NodeTreeSanitizer {
         const NullTreeSanitizer();
         void sanitizeTree(Node node) {}
    }

    It can also be used as follows:

    var elem3 = new Element.html('<p>a text</p>');
    elem3.setInnerHtml(html_string, treeSanitizer: allHtml);

How it works…

First, we have very good news: Dart automatically sanitizes all methods through which HTML elements are constructed, such as new Element.html(), Element.innerHtml(), and a few others. With them, you can build HTML hardcoded, but also through string interpolation, which entails more risks. The default sanitization removes all scriptable elements and attributes.

If you want to escape all characters in a string so that they are transformed into HTML special characters (such as ;&#x2F for a /), use the class HTMLEscape from dart:convert as shown in the second step. The default behavior is to escape apostrophes, greater than/less than, quotes, and slashes. If your application is using untrusted HTML to put in variables, it is strongly advised to use a validation scheme, which only covers the syntax you expect users to feed into your app. This is possible because Element.html() has the following optional arguments:

Element.html(String html, {NodeValidator validator, NodeTreeSanitizer treeSanitizer})

In step 3, only <a> was an allowed tag. By adding more allowElement rules in cascade, you can allow more tags. Using allowHtml5() permits all HTML5 tags.

If you want to remove all control in some cases (perhaps you are dealing with known safe HTML and need to bypass sanitization for performance reasons), you can add the class NullTreeSanitizer to your code, which has no control at all and defines an object allHtml, as shown in step 4. Then, use setInnerHtml() with an optional named attribute treeSanitizer set to allHtml.

Using a browser’s local storage

Local storage (also called the Web Storage API) is widely supported in modern browsers. It enables the application’s data to be persisted locally (on the client side) as a map-like structure: a dictionary of key-value string pairs, in fact using JSON strings to store and retrieve data. It provides our application with an offline mode of functioning when the server is not available to store the data in a database. Local storage does not expire, but every application can only access its own data up to a certain limit depending on the browser. In addition, of course, different browsers can’t access each other’s stores.

How to do it…

Look at the following example, the local_storage.dart file:

import 'dart:html';
 Storage local = window.localStorage;
 void main() {
var job1 = new Job(1, "Web Developer", 6500, "Dart Unlimited") ;

Perform the following steps to use the browser’s local storage:

  1. Write to a local storage with the key Job:1 using the following code:
    local["Job:${job1.id}"] = job1.toJson;
    ButtonElement bel = querySelector('#readls');
    bel.onClick.listen(readShowData);
    }
  2. A click on the button checks to see whether the key Job:1 can be found in the local storage, and, if so, reads the data in. This is then shown in the data <div>:
    readShowData(Event e) {
       var key = 'Job:1';
       if(local.containsKey(key)) {
    // read data from local storage:
       String job = local[key];
       querySelector('#data').appendText(job);
    }
    }
     
    class Job {
    int id;
    String type;
    int salary;
    String company;
    Job(this.id, this.type, this.salary, this.company);
    String get toJson => '{ "type": "$type", "salary": "$salary", "company": "$company" } ';
    }

The following screenshot depicts how data is stored in and retrieved from a local storage:

How it works…

You can store data with a certain key in the local storage from the Window class as follows using window.localStorage[key] = data; (both key and data are Strings).

You can retrieve it with var data = window.localStorage[key];.

In our code, we used the abbreviation Storage local = window.localStorage; because local is a map. You can check the existence of this piece of data in the local storage with containsKey(key); in Chrome (also in other browsers via Developer Tools). You can verify this by navigating to Extra | Tools | Resources | Local Storage (as shown in the previous screenshot), window.localStorage also has a length property; you can query whether it contains something with isEmpty, and you can loop through all stored values using the following code:

for(var key in window.localStorage.keys) {
String value = window.localStorage[key];
// more code
}

There’s more…

Local storage can be disabled (by user action, or via an installed plugin or extension), so we must alert the user when this needs to be enabled; we can do this by catching the exception that occurs in this case:

try {
window.localStorage[key] = data;
} on Exception catch (ex) {
window.alert("Data not stored: Local storage is disabled!");
}

Local storage is a simple key-value store and does have good cross-browser coverage. However, it can only store strings and is a blocking (synchronous) API; this means that it can temporarily pause your web page from responding while it is doing its job storing or reading large amounts of data such as images. Moreover, it has a space limit of 5 MB (this varies with browsers); you can’t detect when you are nearing this limit and you can’t ask for more space. When the limit is reached, an error occurs so that the user can be informed.

These properties make local storage only useful as a temporary data storage tool; this means that it is better than cookies, but not suited for a reliable, database kind of storage.

Web storage also has another way of storing data called sessionStorage used in the same way, but this limits the persistence of the data to only the current browser session. So, data is lost when the browser is closed or another application is started in the same browser window.

Using an application cache to work offline

When, for some reason, our users don’t have web access or the website is down for maintenance (or even broken), our web-based applications should also work offline. The browser cache is not robust enough to be able to do this, so HTML5 has given us the mechanism of ApplicationCache. This cache tells the browser which files should be made available offline. The effect is that the application loads and works correctly, even when the user is offline. The files to be held in the cache are specified in a manifest file, which has a .mf or .appcache extension.

How to do it…

Look at the appcache application; it has a manifest file called appcache.mf.

  1. The manifest file can be specified in every web page that has to be cached. This is done with the manifest attribute of the <html> tag:
    <html manifest="appcache.mf">

    If a page has to be cached and doesn’t have the manifest attribute, it must be specified in the CACHE section of the manifest file. The manifest file has the following (minimum) content:

    CACHE MANIFEST
    # 2012-09-28:v3
     CACHE:
    Cached1.html
    appcache.css
    appcache.dart
    http://dart.googlecode.com/svn/branches/bleeding_edge/dart/client/dart.js
     NETWORK:
    *
     FALLBACK:
    / offline.html
  2. Run cached1.html. This displays the This page is cached, and works offline! text. Change the text to This page has been changed! and reload the browser. You don’t see the changed text because the page is created from the application cache.
  3. When the manifest file is changed (change version v1 to v2), the cache becomes invalid and the new version of the page is loaded with the This page has been changed! text.
  4. The Dart script appcache.dart of the page should contain the following minimal code to access the cache:
    main() {
    new AppCache(window.applicationCache);
    }
     class AppCache {
    ApplicationCache appCache;
     AppCache(this.appCache) {
       appCache.onUpdateReady.listen((e) => updateReady());
       appCache.onError.listen(onCacheError);
    }
     void updateReady() {
       if (appCache.status == ApplicationCache.UPDATEREADY) {
         // The browser downloaded a new app cache. Alert the user:
         appCache.swapCache();
         window.alert('A new version of this site is available. Please reload.');
       }
    }
     void onCacheError(Event e) {
         print('Cache error: ${e}');
         // Implement more complete error reporting to developers
    }
    }

How it works…

The CACHE section in the manifest file enumerates all the entries that have to be cached. The NETWORK: and * options mean that to use all other resources the user has to be online. FALLBACK specifies that offline.html will be displayed if the user is offline and a resource is inaccessible. A page is cached when either of the following is true:

  • Its HTML tag has a manifest attribute pointing to the manifest file
  • The page is specified in the CACHE section of the manifest file

The browser is notified when the manifest file is changed, and the user will be forced to refresh its cached resources. Adding a timestamp and/or a version number such as # 2014-05-18:v1 works fine. Changing the date or the version invalidates the cache, and the updated pages are again loaded from the server.

To access the browser’s app cache from your code, use the window.applicationCache object. Make an object of a class AppCache, and alert the user when the application cache has become invalid (the status is UPDATEREADY) by defining an onUpdateReady listener.

There’s more…

The other known states of the application cache are UNCACHED, IDLE, CHECKING, DOWNLOADING, and OBSOLETE. To log all these cache events, you could add the following listeners to the appCache constructor:

appCache.onCached.listen(onCacheEvent);
appCache.onChecking.listen(onCacheEvent);
appCache.onDownloading.listen(onCacheEvent);
appCache.onNoUpdate.listen(onCacheEvent);
appCache.onObsolete.listen(onCacheEvent);
appCache.onProgress.listen(onCacheEvent);

Provide an onCacheEvent handler using the following code:

void onCacheEvent(Event e) {
   print('Cache event: ${e}');
}

Preventing an onSubmit event from reloading the page

The default action for a submit button on a web page that contains an HTML form is to post all the form data to the server on which the application runs. What if we don’t want this to happen?

How to do it…

Experiment with the submit application by performing the following steps:

  1. Our web page submit.html contains the following code:
    <form id="form1" action="http://www.dartlang.org" method="POST">
    <label>Job:<input type="text" name="Job" size="75"></input>
       </label>
       <input type="submit" value="Job Search">
       </form>

    Comment out all the code in submit.dart. Run the app, enter a job name, and click on the Job Search submit button; the Dart site appears.

  2. When the following code is added to submit.dart, clicking on the no button for a longer duration makes the Dart site appear:
    import 'dart:html';
     void main() {
    querySelector('#form1').onSubmit.listen(submit);
    }
     submit(Event e) {
         e.preventDefault();
    // code to be executed when button is clicked
     }

How it works…

In the first step, when the submit button is pressed, the browser sees that the method is POST. This method collects the data and names from the input fields and sends it to the URL specified in action to be executed, which only shows the Dart site in our case.

To prevent the form from posting the data, make an event handler for the onSubmit event of the form. In this handler code, e.preventDefault(); as the first statement will cancel the default submit action. However, the rest of the submit event handler (and even the same handler of a parent control, should there be one) is still executed on the client side.

Summary

In this article we learned how to handle web applications, sanitize a HTML, use a browser’s local storage, use application cache to work offline, and how to prevent an onSubmit event from reloading a page.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here