[Simple_browser] Improve url sanitization

Change-Id: I37390f68de80ab3a525b2de7b495ee6f4bb6fc96
diff --git a/bin/simple_browser/BUILD.gn b/bin/simple_browser/BUILD.gn
index e689b2a..b9e4163 100644
--- a/bin/simple_browser/BUILD.gn
+++ b/bin/simple_browser/BUILD.gn
@@ -45,7 +45,9 @@
 # fx run-host-tests simple_browser_unittests
 flutter_test("simple_browser_unittests") {
   sources = [
+    "sanitize_url_test.dart",
     "simple_browser_test.dart",
+    "tld_checker_test.dart",
   ]
 
   deps = [
diff --git a/bin/simple_browser/lib/main.dart b/bin/simple_browser/lib/main.dart
index e23d1a5..c27e80c 100644
--- a/bin/simple_browser/lib/main.dart
+++ b/bin/simple_browser/lib/main.dart
@@ -2,15 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:convert';
 import 'package:flutter/material.dart';
 
 import 'package:fidl_fuchsia_intl/fidl_async.dart';
 import 'package:flutter_localizations/flutter_localizations.dart';
 import 'package:fuchsia_internationalization_flutter/internationalization.dart';
 import 'package:fuchsia_logger/logger.dart';
-import 'package:fuchsia_modular/module.dart' as modular;
-import 'package:fuchsia_services/services.dart' show StartupContext;
+import 'package:fuchsia_services/services.dart';
 import 'package:internationalization/localizations_delegate.dart'
     as localizations;
 import 'package:internationalization/supported_locales.dart'
@@ -22,31 +20,12 @@
 import 'src/blocs/tabs_bloc.dart';
 import 'src/blocs/webpage_bloc.dart';
 import 'src/models/tabs_action.dart';
-import 'src/models/webpage_action.dart';
-
-class RootIntentHandler extends modular.IntentHandler {
-  final TabsBloc<WebPageBloc> tabsBloc;
-  RootIntentHandler(this.tabsBloc);
-
-  @override
-  void handleIntent(modular.Intent intent) {
-    /// if there are no tabs, add one
-    /// otherwise add a new one only if the current tabs isn't a "New Tab"
-    if (tabsBloc.tabs.isEmpty || tabsBloc.currentTab.url.isNotEmpty) {
-      tabsBloc.request.add(NewTabAction<WebPageBloc>());
-    }
-    if (intent.action == 'NavigateToUrl') {
-      intent.getEntity(name: 'url', type: 'string').getData().then((bytes) {
-        final url = utf8.decode(bytes);
-        tabsBloc.currentTab.request.add(NavigateToAction(url: url));
-      });
-    }
-  }
-}
+import 'src/utils/tld_checker.dart';
 
 void main() {
   setupLogger(name: 'Browser');
   final _context = createWebContext();
+  TldChecker().prefetchTlds();
 
   // Bind |tabsBloc| here so that it can be referenced in the TabsBloc
   // constructor arguments.
@@ -61,13 +40,12 @@
       tab.dispose();
     },
   );
-  modular.Module().registerIntentHandler(RootIntentHandler(tabsBloc));
-
   final _intl = PropertyProviderProxy();
   StartupContext.fromStartupInfo().incoming.connectToService(_intl);
 
   final locales = LocaleSource(_intl);
 
+  tabsBloc.request.add(NewTabAction());
   runApp(Localized(tabsBloc, locales.stream()));
 }
 
@@ -76,12 +54,9 @@
 class Localized extends StatelessWidget {
   // The tabs bloc to use for the underlying widget.
   final TabsBloc _tabsBloc;
-
   // The stream of locale updates.
   final Stream<Locale> _localeStream;
-
   const Localized(this._tabsBloc, this._localeStream);
-
   @override
   Widget build(BuildContext context) {
     return StreamBuilder<Locale>(
diff --git a/bin/simple_browser/lib/src/blocs/webpage_bloc.dart b/bin/simple_browser/lib/src/blocs/webpage_bloc.dart
index 498479f..212ab65 100644
--- a/bin/simple_browser/lib/src/blocs/webpage_bloc.dart
+++ b/bin/simple_browser/lib/src/blocs/webpage_bloc.dart
@@ -13,6 +13,7 @@
 import 'package:zircon/zircon.dart';
 
 import '../models/webpage_action.dart';
+import '../utils/sanitize_url.dart';
 
 enum PageType { empty, normal, error }
 PageType pageTypeForWebPageType(web.PageType pageType) {
@@ -158,7 +159,7 @@
       case WebPageActionType.navigateTo:
         final NavigateToAction navigate = action;
         await _navigationController.loadUrl(
-          _sanitizeUrl(navigate.url),
+          sanitizeUrl(navigate.url),
           web.LoadUrlParams(type: web.LoadUrlReason.typed),
         );
         break;
@@ -172,16 +173,6 @@
         await _navigationController.reload(web.ReloadType.partialCache);
     }
   }
-
-  String _sanitizeUrl(String url) {
-    if (url.startsWith('http')) {
-      return url;
-    } else if (url.endsWith('.com')) {
-      return 'https://$url';
-    } else {
-      return 'https://www.google.com/search?q=${Uri.encodeQueryComponent(url)}';
-    }
-  }
 }
 
 class _PopupListener extends web.PopupFrameCreationListener {
diff --git a/bin/simple_browser/lib/src/utils/sanitize_url.dart b/bin/simple_browser/lib/src/utils/sanitize_url.dart
new file mode 100644
index 0000000..364a038
--- /dev/null
+++ b/bin/simple_browser/lib/src/utils/sanitize_url.dart
@@ -0,0 +1,56 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'tld_checker.dart';
+
+String sanitizeUrl(String url) {
+  // Checks if the input starts with a scheme.
+  String scheme;
+  try {
+    scheme = Uri.parse(url).scheme;
+  } on FormatException {
+    return googleKeyword(url);
+  }
+
+  // Checks if the scheme is valid.
+  // We currently only supports http, https and chrome.
+  // localhost is included in the validSchemePattern since it is recognized
+  // as a scheme by the dart Uri.parse method whene there is no other scheme.
+  final validSchemePattern = RegExp(r'^(https?|chrome|localhost)$');
+  if (scheme.isNotEmpty) {
+    if (validSchemePattern.hasMatch(scheme)) {
+      return url;
+    }
+    return googleKeyword(url);
+  }
+
+  // Adds a scheme to get a more accurate output from Uri.parse().host
+  String schemedUrl = 'https://$url';
+  String hostUrl = Uri.parse(schemedUrl).host;
+
+  // Checks if the host url has a valid pattern.
+  // Uri.parse().host does not check the validity.
+  final validHostPattern = RegExp(r'([a-zA-Z0-9@_-]{1,256}[\.]{1,1})+[\w]+');
+  if (validHostPattern.stringMatch(hostUrl) != hostUrl) {
+    return googleKeyword(url);
+  }
+
+  // Checks if the URL has a valid TLD.
+  String tld = hostUrl.split('.').last;
+  if (TldChecker().isValid(tld)) {
+    return schemedUrl;
+  }
+
+  // Checks if the URL is IPv4 address.
+  final validIpv4Pattern = RegExp(
+      r'\b((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3,3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$');
+  if (validIpv4Pattern.stringMatch(hostUrl) == hostUrl) {
+    return schemedUrl;
+  }
+
+  return googleKeyword(url);
+}
+
+String googleKeyword(String keyword) =>
+    'https://www.google.com/search?q=${Uri.encodeQueryComponent(keyword)}';
diff --git a/bin/simple_browser/lib/src/utils/tld_checker.dart b/bin/simple_browser/lib/src/utils/tld_checker.dart
new file mode 100644
index 0000000..9e7c4cf
--- /dev/null
+++ b/bin/simple_browser/lib/src/utils/tld_checker.dart
@@ -0,0 +1,54 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:fuchsia_logger/logger.dart';
+import 'package:meta/meta.dart';
+import 'tlds_provider.dart';
+import 'valid_tlds.dart';
+
+class TldChecker {
+  List<String> _validTlds;
+
+  /// A flag that indicates if the valid TLD list is loaded or not.
+  ///
+  /// Its default value on the initilization is 'false'. and is set to 'true'
+  /// once the [prefetchTlds()] is called, and never changes unless the browser
+  /// is relaunched and this [TldChecker] is newly initiated.
+  bool _isIanaTldsLoaded;
+
+  static final TldChecker _tldCheckerInstance = TldChecker._create();
+  factory TldChecker() {
+    return _tldCheckerInstance;
+  }
+
+  TldChecker._create() {
+    _validTlds = kValidTlds;
+    _isIanaTldsLoaded = false;
+    log.info('A singleton TldChecker instance has been created.');
+  }
+
+  /// Fetches a valid TLD list from the IANA if it has not loaded yet.
+  ///
+  /// If a List<String> type parameter is given, it does not fetch the TLD list
+  /// from the web and instead, just uses the parameter list as the valid TLD
+  /// list. Therefore, this parameter should be given only for testing purposes.
+  void prefetchTlds({List<String> testTlds}) async {
+    if (testTlds != null) {
+      _validTlds = testTlds;
+    } else {
+      if (!_isIanaTldsLoaded) {
+        _validTlds = await TldsProvider().fetchTldsList() ?? kValidTlds;
+        _isIanaTldsLoaded = true;
+      } else {
+        log.warning(
+            'TLD List is already loaded. You do not need to fetch it again.');
+      }
+    }
+  }
+
+  bool isValid(String tld) => _validTlds.contains(tld.toUpperCase());
+
+  @visibleForTesting
+  List<String> get validTlds => _validTlds;
+}
diff --git a/bin/simple_browser/lib/src/utils/tlds_provider.dart b/bin/simple_browser/lib/src/utils/tlds_provider.dart
new file mode 100644
index 0000000..6556cae
--- /dev/null
+++ b/bin/simple_browser/lib/src/utils/tlds_provider.dart
@@ -0,0 +1,49 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'package:fuchsia_logger/logger.dart';
+import 'package:http/http.dart' as http;
+
+class TldsProvider {
+  String _data;
+
+  // Creates the TldsModel Once when the simple browser is initiated.
+  Future<String> loadIanaTldsList() async {
+    final response =
+        await http.get('http://data.iana.org/TLD/tlds-alpha-by-domain.txt');
+
+    if (response.statusCode == 200) {
+      log.info('Successfully loaded a TLD list from iana.org');
+      return response.body;
+    } else {
+      log.warning('Failed to load a TLD list from iana.org.');
+      return null;
+    }
+  }
+
+  Future<List<String>> fetchTldsList() async {
+    String data;
+
+    data = _data ?? await loadIanaTldsList();
+
+    if (data == null) {
+      return null;
+    }
+
+    List<String> tldsList = data.split('\n');
+
+    // Removes all white spaces.
+    for (int i = 0; i < tldsList.length; i++) {
+      tldsList[i] = tldsList[i].replaceAll(RegExp(r'\s+'), '');
+    }
+
+    // Removes all comments and empty elements.
+    tldsList.removeWhere((item) => item.isEmpty || item.startsWith('#'));
+
+    return tldsList;
+  }
+
+  set data(String testData) => _data = testData;
+}
diff --git a/bin/simple_browser/lib/src/utils/valid_tlds.dart b/bin/simple_browser/lib/src/utils/valid_tlds.dart
new file mode 100644
index 0000000..0605b4a
--- /dev/null
+++ b/bin/simple_browser/lib/src/utils/valid_tlds.dart
@@ -0,0 +1,1529 @@
+const kValidTlds = <String>[
+  'AAA',
+  'AARP',
+  'ABARTH',
+  'ABB',
+  'ABBOTT',
+  'ABBVIE',
+  'ABC',
+  'ABLE',
+  'ABOGADO',
+  'ABUDHABI',
+  'AC',
+  'ACADEMY',
+  'ACCENTURE',
+  'ACCOUNTANT',
+  'ACCOUNTANTS',
+  'ACO',
+  'ACTOR',
+  'AD',
+  'ADAC',
+  'ADS',
+  'ADULT',
+  'AE',
+  'AEG',
+  'AERO',
+  'AETNA',
+  'AF',
+  'AFAMILYCOMPANY',
+  'AFL',
+  'AFRICA',
+  'AG',
+  'AGAKHAN',
+  'AGENCY',
+  'AI',
+  'AIG',
+  'AIGO',
+  'AIRBUS',
+  'AIRFORCE',
+  'AIRTEL',
+  'AKDN',
+  'AL',
+  'ALFAROMEO',
+  'ALIBABA',
+  'ALIPAY',
+  'ALLFINANZ',
+  'ALLSTATE',
+  'ALLY',
+  'ALSACE',
+  'ALSTOM',
+  'AM',
+  'AMERICANEXPRESS',
+  'AMERICANFAMILY',
+  'AMEX',
+  'AMFAM',
+  'AMICA',
+  'AMSTERDAM',
+  'ANALYTICS',
+  'ANDROID',
+  'ANQUAN',
+  'ANZ',
+  'AO',
+  'AOL',
+  'APARTMENTS',
+  'APP',
+  'APPLE',
+  'AQ',
+  'AQUARELLE',
+  'AR',
+  'ARAB',
+  'ARAMCO',
+  'ARCHI',
+  'ARMY',
+  'ARPA',
+  'ART',
+  'ARTE',
+  'AS',
+  'ASDA',
+  'ASIA',
+  'ASSOCIATES',
+  'AT',
+  'ATHLETA',
+  'ATTORNEY',
+  'AU',
+  'AUCTION',
+  'AUDI',
+  'AUDIBLE',
+  'AUDIO',
+  'AUSPOST',
+  'AUTHOR',
+  'AUTO',
+  'AUTOS',
+  'AVIANCA',
+  'AW',
+  'AWS',
+  'AX',
+  'AXA',
+  'AZ',
+  'AZURE',
+  'BA',
+  'BABY',
+  'BAIDU',
+  'BANAMEX',
+  'BANANAREPUBLIC',
+  'BAND',
+  'BANK',
+  'BAR',
+  'BARCELONA',
+  'BARCLAYCARD',
+  'BARCLAYS',
+  'BAREFOOT',
+  'BARGAINS',
+  'BASEBALL',
+  'BASKETBALL',
+  'BAUHAUS',
+  'BAYERN',
+  'BB',
+  'BBC',
+  'BBT',
+  'BBVA',
+  'BCG',
+  'BCN',
+  'BD',
+  'BE',
+  'BEATS',
+  'BEAUTY',
+  'BEER',
+  'BENTLEY',
+  'BERLIN',
+  'BEST',
+  'BESTBUY',
+  'BET',
+  'BF',
+  'BG',
+  'BH',
+  'BHARTI',
+  'BI',
+  'BIBLE',
+  'BID',
+  'BIKE',
+  'BING',
+  'BINGO',
+  'BIO',
+  'BIZ',
+  'BJ',
+  'BLACK',
+  'BLACKFRIDAY',
+  'BLOCKBUSTER',
+  'BLOG',
+  'BLOOMBERG',
+  'BLUE',
+  'BM',
+  'BMS',
+  'BMW',
+  'BN',
+  'BNPPARIBAS',
+  'BO',
+  'BOATS',
+  'BOEHRINGER',
+  'BOFA',
+  'BOM',
+  'BOND',
+  'BOO',
+  'BOOK',
+  'BOOKING',
+  'BOSCH',
+  'BOSTIK',
+  'BOSTON',
+  'BOT',
+  'BOUTIQUE',
+  'BOX',
+  'BR',
+  'BRADESCO',
+  'BRIDGESTONE',
+  'BROADWAY',
+  'BROKER',
+  'BROTHER',
+  'BRUSSELS',
+  'BS',
+  'BT',
+  'BUDAPEST',
+  'BUGATTI',
+  'BUILD',
+  'BUILDERS',
+  'BUSINESS',
+  'BUY',
+  'BUZZ',
+  'BV',
+  'BW',
+  'BY',
+  'BZ',
+  'BZH',
+  'CA',
+  'CAB',
+  'CAFE',
+  'CAL',
+  'CALL',
+  'CALVINKLEIN',
+  'CAM',
+  'CAMERA',
+  'CAMP',
+  'CANCERRESEARCH',
+  'CANON',
+  'CAPETOWN',
+  'CAPITAL',
+  'CAPITALONE',
+  'CAR',
+  'CARAVAN',
+  'CARDS',
+  'CARE',
+  'CAREER',
+  'CAREERS',
+  'CARS',
+  'CARTIER',
+  'CASA',
+  'CASE',
+  'CASEIH',
+  'CASH',
+  'CASINO',
+  'CAT',
+  'CATERING',
+  'CATHOLIC',
+  'CBA',
+  'CBN',
+  'CBRE',
+  'CBS',
+  'CC',
+  'CD',
+  'CEB',
+  'CENTER',
+  'CEO',
+  'CERN',
+  'CF',
+  'CFA',
+  'CFD',
+  'CG',
+  'CH',
+  'CHANEL',
+  'CHANNEL',
+  'CHARITY',
+  'CHASE',
+  'CHAT',
+  'CHEAP',
+  'CHINTAI',
+  'CHRISTMAS',
+  'CHROME',
+  'CHRYSLER',
+  'CHURCH',
+  'CI',
+  'CIPRIANI',
+  'CIRCLE',
+  'CISCO',
+  'CITADEL',
+  'CITI',
+  'CITIC',
+  'CITY',
+  'CITYEATS',
+  'CK',
+  'CL',
+  'CLAIMS',
+  'CLEANING',
+  'CLICK',
+  'CLINIC',
+  'CLINIQUE',
+  'CLOTHING',
+  'CLOUD',
+  'CLUB',
+  'CLUBMED',
+  'CM',
+  'CN',
+  'CO',
+  'COACH',
+  'CODES',
+  'COFFEE',
+  'COLLEGE',
+  'COLOGNE',
+  'COM',
+  'COMCAST',
+  'COMMBANK',
+  'COMMUNITY',
+  'COMPANY',
+  'COMPARE',
+  'COMPUTER',
+  'COMSEC',
+  'CONDOS',
+  'CONSTRUCTION',
+  'CONSULTING',
+  'CONTACT',
+  'CONTRACTORS',
+  'COOKING',
+  'COOKINGCHANNEL',
+  'COOL',
+  'COOP',
+  'CORSICA',
+  'COUNTRY',
+  'COUPON',
+  'COUPONS',
+  'COURSES',
+  'CPA',
+  'CR',
+  'CREDIT',
+  'CREDITCARD',
+  'CREDITUNION',
+  'CRICKET',
+  'CROWN',
+  'CRS',
+  'CRUISE',
+  'CRUISES',
+  'CSC',
+  'CU',
+  'CUISINELLA',
+  'CV',
+  'CW',
+  'CX',
+  'CY',
+  'CYMRU',
+  'CYOU',
+  'CZ',
+  'DABUR',
+  'DAD',
+  'DANCE',
+  'DATA',
+  'DATE',
+  'DATING',
+  'DATSUN',
+  'DAY',
+  'DCLK',
+  'DDS',
+  'DE',
+  'DEAL',
+  'DEALER',
+  'DEALS',
+  'DEGREE',
+  'DELIVERY',
+  'DELL',
+  'DELOITTE',
+  'DELTA',
+  'DEMOCRAT',
+  'DENTAL',
+  'DENTIST',
+  'DESI',
+  'DESIGN',
+  'DEV',
+  'DHL',
+  'DIAMONDS',
+  'DIET',
+  'DIGITAL',
+  'DIRECT',
+  'DIRECTORY',
+  'DISCOUNT',
+  'DISCOVER',
+  'DISH',
+  'DIY',
+  'DJ',
+  'DK',
+  'DM',
+  'DNP',
+  'DO',
+  'DOCS',
+  'DOCTOR',
+  'DODGE',
+  'DOG',
+  'DOMAINS',
+  'DOT',
+  'DOWNLOAD',
+  'DRIVE',
+  'DTV',
+  'DUBAI',
+  'DUCK',
+  'DUNLOP',
+  'DUPONT',
+  'DURBAN',
+  'DVAG',
+  'DVR',
+  'DZ',
+  'EARTH',
+  'EAT',
+  'EC',
+  'ECO',
+  'EDEKA',
+  'EDU',
+  'EDUCATION',
+  'EE',
+  'EG',
+  'EMAIL',
+  'EMERCK',
+  'ENERGY',
+  'ENGINEER',
+  'ENGINEERING',
+  'ENTERPRISES',
+  'EPSON',
+  'EQUIPMENT',
+  'ER',
+  'ERICSSON',
+  'ERNI',
+  'ES',
+  'ESQ',
+  'ESTATE',
+  'ESURANCE',
+  'ET',
+  'ETISALAT',
+  'EU',
+  'EUROVISION',
+  'EUS',
+  'EVENTS',
+  'EVERBANK',
+  'EXCHANGE',
+  'EXPERT',
+  'EXPOSED',
+  'EXPRESS',
+  'EXTRASPACE',
+  'FAGE',
+  'FAIL',
+  'FAIRWINDS',
+  'FAITH',
+  'FAMILY',
+  'FAN',
+  'FANS',
+  'FARM',
+  'FARMERS',
+  'FASHION',
+  'FAST',
+  'FEDEX',
+  'FEEDBACK',
+  'FERRARI',
+  'FERRERO',
+  'FI',
+  'FIAT',
+  'FIDELITY',
+  'FIDO',
+  'FILM',
+  'FINAL',
+  'FINANCE',
+  'FINANCIAL',
+  'FIRE',
+  'FIRESTONE',
+  'FIRMDALE',
+  'FISH',
+  'FISHING',
+  'FIT',
+  'FITNESS',
+  'FJ',
+  'FK',
+  'FLICKR',
+  'FLIGHTS',
+  'FLIR',
+  'FLORIST',
+  'FLOWERS',
+  'FLY',
+  'FM',
+  'FO',
+  'FOO',
+  'FOOD',
+  'FOODNETWORK',
+  'FOOTBALL',
+  'FORD',
+  'FOREX',
+  'FORSALE',
+  'FORUM',
+  'FOUNDATION',
+  'FOX',
+  'FR',
+  'FREE',
+  'FRESENIUS',
+  'FRL',
+  'FROGANS',
+  'FRONTDOOR',
+  'FRONTIER',
+  'FTR',
+  'FUJITSU',
+  'FUJIXEROX',
+  'FUN',
+  'FUND',
+  'FURNITURE',
+  'FUTBOL',
+  'FYI',
+  'GA',
+  'GAL',
+  'GALLERY',
+  'GALLO',
+  'GALLUP',
+  'GAME',
+  'GAMES',
+  'GAP',
+  'GARDEN',
+  'GAY',
+  'GB',
+  'GBIZ',
+  'GD',
+  'GDN',
+  'GE',
+  'GEA',
+  'GENT',
+  'GENTING',
+  'GEORGE',
+  'GF',
+  'GG',
+  'GGEE',
+  'GH',
+  'GI',
+  'GIFT',
+  'GIFTS',
+  'GIVES',
+  'GIVING',
+  'GL',
+  'GLADE',
+  'GLASS',
+  'GLE',
+  'GLOBAL',
+  'GLOBO',
+  'GM',
+  'GMAIL',
+  'GMBH',
+  'GMO',
+  'GMX',
+  'GN',
+  'GODADDY',
+  'GOLD',
+  'GOLDPOINT',
+  'GOLF',
+  'GOO',
+  'GOODYEAR',
+  'GOOG',
+  'GOOGLE',
+  'GOP',
+  'GOT',
+  'GOV',
+  'GP',
+  'GQ',
+  'GR',
+  'GRAINGER',
+  'GRAPHICS',
+  'GRATIS',
+  'GREEN',
+  'GRIPE',
+  'GROCERY',
+  'GROUP',
+  'GS',
+  'GT',
+  'GU',
+  'GUARDIAN',
+  'GUCCI',
+  'GUGE',
+  'GUIDE',
+  'GUITARS',
+  'GURU',
+  'GW',
+  'GY',
+  'HAIR',
+  'HAMBURG',
+  'HANGOUT',
+  'HAUS',
+  'HBO',
+  'HDFC',
+  'HDFCBANK',
+  'HEALTH',
+  'HEALTHCARE',
+  'HELP',
+  'HELSINKI',
+  'HERE',
+  'HERMES',
+  'HGTV',
+  'HIPHOP',
+  'HISAMITSU',
+  'HITACHI',
+  'HIV',
+  'HK',
+  'HKT',
+  'HM',
+  'HN',
+  'HOCKEY',
+  'HOLDINGS',
+  'HOLIDAY',
+  'HOMEDEPOT',
+  'HOMEGOODS',
+  'HOMES',
+  'HOMESENSE',
+  'HONDA',
+  'HORSE',
+  'HOSPITAL',
+  'HOST',
+  'HOSTING',
+  'HOT',
+  'HOTELES',
+  'HOTELS',
+  'HOTMAIL',
+  'HOUSE',
+  'HOW',
+  'HR',
+  'HSBC',
+  'HT',
+  'HU',
+  'HUGHES',
+  'HYATT',
+  'HYUNDAI',
+  'IBM',
+  'ICBC',
+  'ICE',
+  'ICU',
+  'ID',
+  'IE',
+  'IEEE',
+  'IFM',
+  'IKANO',
+  'IL',
+  'IM',
+  'IMAMAT',
+  'IMDB',
+  'IMMO',
+  'IMMOBILIEN',
+  'IN',
+  'INC',
+  'INDUSTRIES',
+  'INFINITI',
+  'INFO',
+  'ING',
+  'INK',
+  'INSTITUTE',
+  'INSURANCE',
+  'INSURE',
+  'INT',
+  'INTEL',
+  'INTERNATIONAL',
+  'INTUIT',
+  'INVESTMENTS',
+  'IO',
+  'IPIRANGA',
+  'IQ',
+  'IR',
+  'IRISH',
+  'IS',
+  'ISMAILI',
+  'IST',
+  'ISTANBUL',
+  'IT',
+  'ITAU',
+  'ITV',
+  'IVECO',
+  'JAGUAR',
+  'JAVA',
+  'JCB',
+  'JCP',
+  'JE',
+  'JEEP',
+  'JETZT',
+  'JEWELRY',
+  'JIO',
+  'JLL',
+  'JM',
+  'JMP',
+  'JNJ',
+  'JO',
+  'JOBS',
+  'JOBURG',
+  'JOT',
+  'JOY',
+  'JP',
+  'JPMORGAN',
+  'JPRS',
+  'JUEGOS',
+  'JUNIPER',
+  'KAUFEN',
+  'KDDI',
+  'KE',
+  'KERRYHOTELS',
+  'KERRYLOGISTICS',
+  'KERRYPROPERTIES',
+  'KFH',
+  'KG',
+  'KH',
+  'KI',
+  'KIA',
+  'KIM',
+  'KINDER',
+  'KINDLE',
+  'KITCHEN',
+  'KIWI',
+  'KM',
+  'KN',
+  'KOELN',
+  'KOMATSU',
+  'KOSHER',
+  'KP',
+  'KPMG',
+  'KPN',
+  'KR',
+  'KRD',
+  'KRED',
+  'KUOKGROUP',
+  'KW',
+  'KY',
+  'KYOTO',
+  'KZ',
+  'LA',
+  'LACAIXA',
+  'LADBROKES',
+  'LAMBORGHINI',
+  'LAMER',
+  'LANCASTER',
+  'LANCIA',
+  'LANCOME',
+  'LAND',
+  'LANDROVER',
+  'LANXESS',
+  'LASALLE',
+  'LAT',
+  'LATINO',
+  'LATROBE',
+  'LAW',
+  'LAWYER',
+  'LB',
+  'LC',
+  'LDS',
+  'LEASE',
+  'LECLERC',
+  'LEFRAK',
+  'LEGAL',
+  'LEGO',
+  'LEXUS',
+  'LGBT',
+  'LI',
+  'LIAISON',
+  'LIDL',
+  'LIFE',
+  'LIFEINSURANCE',
+  'LIFESTYLE',
+  'LIGHTING',
+  'LIKE',
+  'LILLY',
+  'LIMITED',
+  'LIMO',
+  'LINCOLN',
+  'LINDE',
+  'LINK',
+  'LIPSY',
+  'LIVE',
+  'LIVING',
+  'LIXIL',
+  'LK',
+  'LLC',
+  'LOAN',
+  'LOANS',
+  'LOCKER',
+  'LOCUS',
+  'LOFT',
+  'LOL',
+  'LONDON',
+  'LOTTE',
+  'LOTTO',
+  'LOVE',
+  'LPL',
+  'LPLFINANCIAL',
+  'LR',
+  'LS',
+  'LT',
+  'LTD',
+  'LTDA',
+  'LU',
+  'LUNDBECK',
+  'LUPIN',
+  'LUXE',
+  'LUXURY',
+  'LV',
+  'LY',
+  'MA',
+  'MACYS',
+  'MADRID',
+  'MAIF',
+  'MAISON',
+  'MAKEUP',
+  'MAN',
+  'MANAGEMENT',
+  'MANGO',
+  'MAP',
+  'MARKET',
+  'MARKETING',
+  'MARKETS',
+  'MARRIOTT',
+  'MARSHALLS',
+  'MASERATI',
+  'MATTEL',
+  'MBA',
+  'MC',
+  'MCKINSEY',
+  'MD',
+  'ME',
+  'MED',
+  'MEDIA',
+  'MEET',
+  'MELBOURNE',
+  'MEME',
+  'MEMORIAL',
+  'MEN',
+  'MENU',
+  'MERCKMSD',
+  'METLIFE',
+  'MG',
+  'MH',
+  'MIAMI',
+  'MICROSOFT',
+  'MIL',
+  'MINI',
+  'MINT',
+  'MIT',
+  'MITSUBISHI',
+  'MK',
+  'ML',
+  'MLB',
+  'MLS',
+  'MM',
+  'MMA',
+  'MN',
+  'MO',
+  'MOBI',
+  'MOBILE',
+  'MODA',
+  'MOE',
+  'MOI',
+  'MOM',
+  'MONASH',
+  'MONEY',
+  'MONSTER',
+  'MOPAR',
+  'MORMON',
+  'MORTGAGE',
+  'MOSCOW',
+  'MOTO',
+  'MOTORCYCLES',
+  'MOV',
+  'MOVIE',
+  'MOVISTAR',
+  'MP',
+  'MQ',
+  'MR',
+  'MS',
+  'MSD',
+  'MT',
+  'MTN',
+  'MTR',
+  'MU',
+  'MUSEUM',
+  'MUTUAL',
+  'MV',
+  'MW',
+  'MX',
+  'MY',
+  'MZ',
+  'NA',
+  'NAB',
+  'NADEX',
+  'NAGOYA',
+  'NAME',
+  'NATIONWIDE',
+  'NATURA',
+  'NAVY',
+  'NBA',
+  'NC',
+  'NE',
+  'NEC',
+  'NET',
+  'NETBANK',
+  'NETFLIX',
+  'NETWORK',
+  'NEUSTAR',
+  'NEW',
+  'NEWHOLLAND',
+  'NEWS',
+  'NEXT',
+  'NEXTDIRECT',
+  'NEXUS',
+  'NF',
+  'NFL',
+  'NG',
+  'NGO',
+  'NHK',
+  'NI',
+  'NICO',
+  'NIKE',
+  'NIKON',
+  'NINJA',
+  'NISSAN',
+  'NISSAY',
+  'NL',
+  'NO',
+  'NOKIA',
+  'NORTHWESTERNMUTUAL',
+  'NORTON',
+  'NOW',
+  'NOWRUZ',
+  'NOWTV',
+  'NP',
+  'NR',
+  'NRA',
+  'NRW',
+  'NTT',
+  'NU',
+  'NYC',
+  'NZ',
+  'OBI',
+  'OBSERVER',
+  'OFF',
+  'OFFICE',
+  'OKINAWA',
+  'OLAYAN',
+  'OLAYANGROUP',
+  'OLDNAVY',
+  'OLLO',
+  'OM',
+  'OMEGA',
+  'ONE',
+  'ONG',
+  'ONL',
+  'ONLINE',
+  'ONYOURSIDE',
+  'OOO',
+  'OPEN',
+  'ORACLE',
+  'ORANGE',
+  'ORG',
+  'ORGANIC',
+  'ORIGINS',
+  'OSAKA',
+  'OTSUKA',
+  'OTT',
+  'OVH',
+  'PA',
+  'PAGE',
+  'PANASONIC',
+  'PARIS',
+  'PARS',
+  'PARTNERS',
+  'PARTS',
+  'PARTY',
+  'PASSAGENS',
+  'PAY',
+  'PCCW',
+  'PE',
+  'PET',
+  'PF',
+  'PFIZER',
+  'PG',
+  'PH',
+  'PHARMACY',
+  'PHD',
+  'PHILIPS',
+  'PHONE',
+  'PHOTO',
+  'PHOTOGRAPHY',
+  'PHOTOS',
+  'PHYSIO',
+  'PIAGET',
+  'PICS',
+  'PICTET',
+  'PICTURES',
+  'PID',
+  'PIN',
+  'PING',
+  'PINK',
+  'PIONEER',
+  'PIZZA',
+  'PK',
+  'PL',
+  'PLACE',
+  'PLAY',
+  'PLAYSTATION',
+  'PLUMBING',
+  'PLUS',
+  'PM',
+  'PN',
+  'PNC',
+  'POHL',
+  'POKER',
+  'POLITIE',
+  'PORN',
+  'POST',
+  'PR',
+  'PRAMERICA',
+  'PRAXI',
+  'PRESS',
+  'PRIME',
+  'PRO',
+  'PROD',
+  'PRODUCTIONS',
+  'PROF',
+  'PROGRESSIVE',
+  'PROMO',
+  'PROPERTIES',
+  'PROPERTY',
+  'PROTECTION',
+  'PRU',
+  'PRUDENTIAL',
+  'PS',
+  'PT',
+  'PUB',
+  'PW',
+  'PWC',
+  'PY',
+  'QA',
+  'QPON',
+  'QUEBEC',
+  'QUEST',
+  'QVC',
+  'RACING',
+  'RADIO',
+  'RAID',
+  'RE',
+  'READ',
+  'REALESTATE',
+  'REALTOR',
+  'REALTY',
+  'RECIPES',
+  'RED',
+  'REDSTONE',
+  'REDUMBRELLA',
+  'REHAB',
+  'REISE',
+  'REISEN',
+  'REIT',
+  'RELIANCE',
+  'REN',
+  'RENT',
+  'RENTALS',
+  'REPAIR',
+  'REPORT',
+  'REPUBLICAN',
+  'REST',
+  'RESTAURANT',
+  'REVIEW',
+  'REVIEWS',
+  'REXROTH',
+  'RICH',
+  'RICHARDLI',
+  'RICOH',
+  'RIGHTATHOME',
+  'RIL',
+  'RIO',
+  'RIP',
+  'RMIT',
+  'RO',
+  'ROCHER',
+  'ROCKS',
+  'RODEO',
+  'ROGERS',
+  'ROOM',
+  'RS',
+  'RSVP',
+  'RU',
+  'RUGBY',
+  'RUHR',
+  'RUN',
+  'RW',
+  'RWE',
+  'RYUKYU',
+  'SA',
+  'SAARLAND',
+  'SAFE',
+  'SAFETY',
+  'SAKURA',
+  'SALE',
+  'SALON',
+  'SAMSCLUB',
+  'SAMSUNG',
+  'SANDVIK',
+  'SANDVIKCOROMANT',
+  'SANOFI',
+  'SAP',
+  'SARL',
+  'SAS',
+  'SAVE',
+  'SAXO',
+  'SB',
+  'SBI',
+  'SBS',
+  'SC',
+  'SCA',
+  'SCB',
+  'SCHAEFFLER',
+  'SCHMIDT',
+  'SCHOLARSHIPS',
+  'SCHOOL',
+  'SCHULE',
+  'SCHWARZ',
+  'SCIENCE',
+  'SCJOHNSON',
+  'SCOR',
+  'SCOT',
+  'SD',
+  'SE',
+  'SEARCH',
+  'SEAT',
+  'SECURE',
+  'SECURITY',
+  'SEEK',
+  'SELECT',
+  'SENER',
+  'SERVICES',
+  'SES',
+  'SEVEN',
+  'SEW',
+  'SEX',
+  'SEXY',
+  'SFR',
+  'SG',
+  'SH',
+  'SHANGRILA',
+  'SHARP',
+  'SHAW',
+  'SHELL',
+  'SHIA',
+  'SHIKSHA',
+  'SHOES',
+  'SHOP',
+  'SHOPPING',
+  'SHOUJI',
+  'SHOW',
+  'SHOWTIME',
+  'SHRIRAM',
+  'SI',
+  'SILK',
+  'SINA',
+  'SINGLES',
+  'SITE',
+  'SJ',
+  'SK',
+  'SKI',
+  'SKIN',
+  'SKY',
+  'SKYPE',
+  'SL',
+  'SLING',
+  'SM',
+  'SMART',
+  'SMILE',
+  'SN',
+  'SNCF',
+  'SO',
+  'SOCCER',
+  'SOCIAL',
+  'SOFTBANK',
+  'SOFTWARE',
+  'SOHU',
+  'SOLAR',
+  'SOLUTIONS',
+  'SONG',
+  'SONY',
+  'SOY',
+  'SPACE',
+  'SPORT',
+  'SPOT',
+  'SPREADBETTING',
+  'SR',
+  'SRL',
+  'SRT',
+  'SS',
+  'ST',
+  'STADA',
+  'STAPLES',
+  'STAR',
+  'STATEBANK',
+  'STATEFARM',
+  'STC',
+  'STCGROUP',
+  'STOCKHOLM',
+  'STORAGE',
+  'STORE',
+  'STREAM',
+  'STUDIO',
+  'STUDY',
+  'STYLE',
+  'SU',
+  'SUCKS',
+  'SUPPLIES',
+  'SUPPLY',
+  'SUPPORT',
+  'SURF',
+  'SURGERY',
+  'SUZUKI',
+  'SV',
+  'SWATCH',
+  'SWIFTCOVER',
+  'SWISS',
+  'SX',
+  'SY',
+  'SYDNEY',
+  'SYMANTEC',
+  'SYSTEMS',
+  'SZ',
+  'TAB',
+  'TAIPEI',
+  'TALK',
+  'TAOBAO',
+  'TARGET',
+  'TATAMOTORS',
+  'TATAR',
+  'TATTOO',
+  'TAX',
+  'TAXI',
+  'TC',
+  'TCI',
+  'TD',
+  'TDK',
+  'TEAM',
+  'TECH',
+  'TECHNOLOGY',
+  'TEL',
+  'TELEFONICA',
+  'TEMASEK',
+  'TENNIS',
+  'TEVA',
+  'TF',
+  'TG',
+  'TH',
+  'THD',
+  'THEATER',
+  'THEATRE',
+  'TIAA',
+  'TICKETS',
+  'TIENDA',
+  'TIFFANY',
+  'TIPS',
+  'TIRES',
+  'TIROL',
+  'TJ',
+  'TJMAXX',
+  'TJX',
+  'TK',
+  'TKMAXX',
+  'TL',
+  'TM',
+  'TMALL',
+  'TN',
+  'TO',
+  'TODAY',
+  'TOKYO',
+  'TOOLS',
+  'TOP',
+  'TORAY',
+  'TOSHIBA',
+  'TOTAL',
+  'TOURS',
+  'TOWN',
+  'TOYOTA',
+  'TOYS',
+  'TR',
+  'TRADE',
+  'TRADING',
+  'TRAINING',
+  'TRAVEL',
+  'TRAVELCHANNEL',
+  'TRAVELERS',
+  'TRAVELERSINSURANCE',
+  'TRUST',
+  'TRV',
+  'TT',
+  'TUBE',
+  'TUI',
+  'TUNES',
+  'TUSHU',
+  'TV',
+  'TVS',
+  'TW',
+  'TZ',
+  'UA',
+  'UBANK',
+  'UBS',
+  'UCONNECT',
+  'UG',
+  'UK',
+  'UNICOM',
+  'UNIVERSITY',
+  'UNO',
+  'UOL',
+  'UPS',
+  'US',
+  'UY',
+  'UZ',
+  'VA',
+  'VACATIONS',
+  'VANA',
+  'VANGUARD',
+  'VC',
+  'VE',
+  'VEGAS',
+  'VENTURES',
+  'VERISIGN',
+  'VERSICHERUNG',
+  'VET',
+  'VG',
+  'VI',
+  'VIAJES',
+  'VIDEO',
+  'VIG',
+  'VIKING',
+  'VILLAS',
+  'VIN',
+  'VIP',
+  'VIRGIN',
+  'VISA',
+  'VISION',
+  'VISTAPRINT',
+  'VIVA',
+  'VIVO',
+  'VLAANDEREN',
+  'VN',
+  'VODKA',
+  'VOLKSWAGEN',
+  'VOLVO',
+  'VOTE',
+  'VOTING',
+  'VOTO',
+  'VOYAGE',
+  'VU',
+  'VUELOS',
+  'WALES',
+  'WALMART',
+  'WALTER',
+  'WANG',
+  'WANGGOU',
+  'WARMAN',
+  'WATCH',
+  'WATCHES',
+  'WEATHER',
+  'WEATHERCHANNEL',
+  'WEBCAM',
+  'WEBER',
+  'WEBSITE',
+  'WED',
+  'WEDDING',
+  'WEIBO',
+  'WEIR',
+  'WF',
+  'WHOSWHO',
+  'WIEN',
+  'WIKI',
+  'WILLIAMHILL',
+  'WIN',
+  'WINDOWS',
+  'WINE',
+  'WINNERS',
+  'WME',
+  'WOLTERSKLUWER',
+  'WOODSIDE',
+  'WORK',
+  'WORKS',
+  'WORLD',
+  'WOW',
+  'WS',
+  'WTC',
+  'WTF',
+  'XBOX',
+  'XEROX',
+  'XFINITY',
+  'XIHUAN',
+  'XIN',
+  'XN--11B4C3D',
+  'XN--1CK2E1B',
+  'XN--1QQW23A',
+  'XN--2SCRJ9C',
+  'XN--30RR7Y',
+  'XN--3BST00M',
+  'XN--3DS443G',
+  'XN--3E0B707E',
+  'XN--3HCRJ9C',
+  'XN--3OQ18VL8PN36A',
+  'XN--3PXU8K',
+  'XN--42C2D9A',
+  'XN--45BR5CYL',
+  'XN--45BRJ9C',
+  'XN--45Q11C',
+  'XN--4GBRIM',
+  'XN--54B7FTA0CC',
+  'XN--55QW42G',
+  'XN--55QX5D',
+  'XN--5SU34J936BGSG',
+  'XN--5TZM5G',
+  'XN--6FRZ82G',
+  'XN--6QQ986B3XL',
+  'XN--80ADXHKS',
+  'XN--80AO21A',
+  'XN--80AQECDR1A',
+  'XN--80ASEHDB',
+  'XN--80ASWG',
+  'XN--8Y0A063A',
+  'XN--90A3AC',
+  'XN--90AE',
+  'XN--90AIS',
+  'XN--9DBQ2A',
+  'XN--9ET52U',
+  'XN--9KRT00A',
+  'XN--B4W605FERD',
+  'XN--BCK1B9A5DRE4C',
+  'XN--C1AVG',
+  'XN--C2BR7G',
+  'XN--CCK2B3B',
+  'XN--CG4BKI',
+  'XN--CLCHC0EA0B2G2A9GCD',
+  'XN--CZR694B',
+  'XN--CZRS0T',
+  'XN--CZRU2D',
+  'XN--D1ACJ3B',
+  'XN--D1ALF',
+  'XN--E1A4C',
+  'XN--ECKVDTC9D',
+  'XN--EFVY88H',
+  'XN--ESTV75G',
+  'XN--FCT429K',
+  'XN--FHBEI',
+  'XN--FIQ228C5HS',
+  'XN--FIQ64B',
+  'XN--FIQS8S',
+  'XN--FIQZ9S',
+  'XN--FJQ720A',
+  'XN--FLW351E',
+  'XN--FPCRJ9C3D',
+  'XN--FZC2C9E2C',
+  'XN--FZYS8D69UVGM',
+  'XN--G2XX48C',
+  'XN--GCKR3F0F',
+  'XN--GECRJ9C',
+  'XN--GK3AT1E',
+  'XN--H2BREG3EVE',
+  'XN--H2BRJ9C',
+  'XN--H2BRJ9C8C',
+  'XN--HXT814E',
+  'XN--I1B6B1A6A2E',
+  'XN--IMR513N',
+  'XN--IO0A7I',
+  'XN--J1AEF',
+  'XN--J1AMH',
+  'XN--J6W193G',
+  'XN--JLQ61U9W7B',
+  'XN--JVR189M',
+  'XN--KCRX77D1X4A',
+  'XN--KPRW13D',
+  'XN--KPRY57D',
+  'XN--KPU716F',
+  'XN--KPUT3I',
+  'XN--L1ACC',
+  'XN--LGBBAT1AD8J',
+  'XN--MGB9AWBF',
+  'XN--MGBA3A3EJT',
+  'XN--MGBA3A4F16A',
+  'XN--MGBA7C0BBN0A',
+  'XN--MGBAAKC7DVF',
+  'XN--MGBAAM7A8H',
+  'XN--MGBAB2BD',
+  'XN--MGBAH1A3HJKRD',
+  'XN--MGBAI9AZGQP6J',
+  'XN--MGBAYH7GPA',
+  'XN--MGBBH1A',
+  'XN--MGBBH1A71E',
+  'XN--MGBC0A9AZCG',
+  'XN--MGBCA7DZDO',
+  'XN--MGBERP4A5D4AR',
+  'XN--MGBGU82A',
+  'XN--MGBI4ECEXP',
+  'XN--MGBPL2FH',
+  'XN--MGBT3DHD',
+  'XN--MGBTX2B',
+  'XN--MGBX4CD0AB',
+  'XN--MIX891F',
+  'XN--MK1BU44C',
+  'XN--MXTQ1M',
+  'XN--NGBC5AZD',
+  'XN--NGBE9E0A',
+  'XN--NGBRX',
+  'XN--NODE',
+  'XN--NQV7F',
+  'XN--NQV7FS00EMA',
+  'XN--NYQY26A',
+  'XN--O3CW4H',
+  'XN--OGBPF8FL',
+  'XN--OTU796D',
+  'XN--P1ACF',
+  'XN--P1AI',
+  'XN--PBT977C',
+  'XN--PGBS0DH',
+  'XN--PSSY2U',
+  'XN--Q9JYB4C',
+  'XN--QCKA1PMC',
+  'XN--QXA6A',
+  'XN--QXAM',
+  'XN--RHQV96G',
+  'XN--ROVU88B',
+  'XN--RVC1E0AM3E',
+  'XN--S9BRJ9C',
+  'XN--SES554G',
+  'XN--T60B56A',
+  'XN--TCKWE',
+  'XN--TIQ49XQYJ',
+  'XN--UNUP4Y',
+  'XN--VERMGENSBERATER-CTB',
+  'XN--VERMGENSBERATUNG-PWB',
+  'XN--VHQUV',
+  'XN--VUQ861B',
+  'XN--W4R85EL8FHU5DNRA',
+  'XN--W4RS40L',
+  'XN--WGBH1C',
+  'XN--WGBL6A',
+  'XN--XHQ521B',
+  'XN--XKC2AL3HYE2A',
+  'XN--XKC2DL3A5EE0H',
+  'XN--Y9A3AQ',
+  'XN--YFRO4I67O',
+  'XN--YGBI2AMMX',
+  'XN--ZFR164B',
+  'XXX',
+  'XYZ',
+  'YACHTS',
+  'YAHOO',
+  'YAMAXUN',
+  'YANDEX',
+  'YE',
+  'YODOBASHI',
+  'YOGA',
+  'YOKOHAMA',
+  'YOU',
+  'YOUTUBE',
+  'YT',
+  'YUN',
+  'ZA',
+  'ZAPPOS',
+  'ZARA',
+  'ZERO',
+  'ZIP',
+  'ZM',
+  'ZONE',
+  'ZUERICH',
+  'ZW',
+];
diff --git a/bin/simple_browser/lib/src/widgets/navigation_field.dart b/bin/simple_browser/lib/src/widgets/navigation_field.dart
index 92497a5..0c3d670 100644
--- a/bin/simple_browser/lib/src/widgets/navigation_field.dart
+++ b/bin/simple_browser/lib/src/widgets/navigation_field.dart
@@ -80,6 +80,7 @@
         cursorWidth: 8,
         cursorRadius: Radius.zero,
         cursorColor: Colors.black,
+        enableInteractiveSelection: true,
         textAlign: TextAlign.center,
         keyboardType: TextInputType.url,
         decoration: InputDecoration(
diff --git a/bin/simple_browser/test/sanitize_url_test.dart b/bin/simple_browser/test/sanitize_url_test.dart
new file mode 100644
index 0000000..90210c6
--- /dev/null
+++ b/bin/simple_browser/test/sanitize_url_test.dart
@@ -0,0 +1,79 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:fuchsia_logger/logger.dart';
+
+// ignore_for_file: implementation_imports
+import 'package:simple_browser/src/utils/sanitize_url.dart';
+
+void main() {
+  setupLogger(name: 'sanitize_url_test');
+
+  void urlSanitizationTest(String testUrl, String expectedResult) {
+    String testResult = sanitizeUrl(testUrl);
+
+    expect(testResult, expectedResult,
+        reason: 'expected "$expectedResult" but actually "$testResult".');
+  }
+
+  test('URL Sanitization: URLs with supported schemes.', () {
+    // With a URL that causes Format Exception to Dart's Uri.parse().
+    urlSanitizationTest('http ://wrongformat',
+        'https://www.google.com/search?q=http+%3A%2F%2Fwrongformat');
+
+    // With a supported scheme(https) and a valid TLD(com).
+    urlSanitizationTest('https://google.com', 'https://google.com');
+
+    // With a supported scheme(http) and a valid TLD(org).
+    urlSanitizationTest('http://wikipedia.org', 'http://wikipedia.org');
+
+    // With a supported scheme(chrome) and a valid URL.
+    urlSanitizationTest('chrome://gpu', 'chrome://gpu');
+
+    // With a supported scheme(https) and an invalid TLD.
+    urlSanitizationTest('https://google.cooom', 'https://google.cooom');
+
+    // localhost with a port number
+    urlSanitizationTest('localhost:8000', 'localhost:8000');
+  });
+
+  test('URL Sanitization: URLs without schemes.', () {
+    const String googleSearchUrl = 'https://www.google.com/search?q=';
+
+    // With a valid host pattern and a valid TLD.
+    urlSanitizationTest('flutter.dev', 'https://flutter.dev');
+
+    // With a valid host pattern and a valid TLD and a path
+    urlSanitizationTest('flutter.dev/clock', 'https://flutter.dev/clock');
+
+    // With an invalid host pattern and a valid TLD.
+    urlSanitizationTest(
+        'flu#%*tter.dev', '${googleSearchUrl}flu%23%25%2Atter.dev');
+
+    // With a valid host pattern and an invalid TLD.
+    urlSanitizationTest('google.cooom', '${googleSearchUrl}google.cooom');
+
+    // With a keyword.
+    urlSanitizationTest('fuchsia', '${googleSearchUrl}fuchsia');
+
+    // With a valid ip address.
+    urlSanitizationTest('255.111.18.1', 'https://255.111.18.1');
+
+    // With a valid ip address and a path
+    urlSanitizationTest('200.102.11.9/hello', 'https://200.102.11.9/hello');
+
+    // With an invalid ip address (Each number must be a value between 0 - 255).
+    urlSanitizationTest('955.111.18.1', '${googleSearchUrl}955.111.18.1');
+
+    // With an invalid ip address (The address must consist of 4 numbers).
+    urlSanitizationTest('255.111.18', '${googleSearchUrl}255.111.18');
+
+    // With an invalid ip address (Spaced not allowed).
+    urlSanitizationTest('255.111.18. 1', '${googleSearchUrl}255.111.18.+1');
+
+    // With an invalid ip address (Non-digit characters except '.' not allowed).
+    urlSanitizationTest('255.111.18.*1', '${googleSearchUrl}255.111.18.%2A1');
+  });
+}
diff --git a/bin/simple_browser/test/tld_checker_test.dart b/bin/simple_browser/test/tld_checker_test.dart
new file mode 100644
index 0000000..4050f72
--- /dev/null
+++ b/bin/simple_browser/test/tld_checker_test.dart
@@ -0,0 +1,55 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:fuchsia_logger/logger.dart';
+import 'package:test/test.dart';
+
+// ignore_for_file: implementation_imports
+import 'package:simple_browser/src/utils/tld_checker.dart';
+import 'package:simple_browser/src/utils/tlds_provider.dart';
+
+void main() {
+  setupLogger(name: 'tld_checker_test');
+  test('TLD Validity check test with local TLD list.', () {
+    expect(TldChecker().isValid('dev'), true, reason: '"dev" should be valid.');
+    expect(TldChecker().isValid('asdf'), false,
+        reason: '"asdf" should be invalid.');
+  });
+
+  test('Fetch test with two valid TLDs and one comment line.', () async {
+    String testData = '''
+    # version 2019111500, Last Updated Fri Nov 15 07:07:01 2019 UTC
+    AAA
+    BBB
+    ''';
+    TldsProvider provider = TldsProvider()..data = testData;
+    List<String> testTlds = await provider.fetchTldsList();
+    TldChecker().prefetchTlds(testTlds: testTlds);
+
+    expect(TldChecker().validTlds.length, 2,
+        reason: 'The length of valid TLD list should be two.');
+    expect(TldChecker().isValid('aaa'), true, reason: '"aaa" should be valid.');
+    expect(TldChecker().isValid('bbb'), true, reason: '"bbb should be valid.');
+  });
+
+  test('Fetch test with one valid TLDs and multiple comment lines.', () async {
+    String testData = '''
+    # version 2019111500, Last Updated Fri Nov 15 07:07:01 2019 UTC
+    # Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+    # Donec sed libero at lacus blandit scelerisque.
+    # Aliquam at felis ac orci facilisis tincidunt.
+    # Nullam eu justo faucibus, pretium urna et, pretium magna.
+    # Duis faucibus elit id felis sollicitudin, quis fermentum neque convallis.
+    QQQ
+    ''';
+
+    TldsProvider provider = TldsProvider()..data = testData;
+    List<String> testTlds = await provider.fetchTldsList();
+    TldChecker().prefetchTlds(testTlds: testTlds);
+
+    expect(TldChecker().validTlds.length, 1,
+        reason: 'The length of valid TLD list should be one.');
+    expect(TldChecker().isValid('qqq'), true, reason: '"qqq" should be valid.');
+  });
+}