1ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// Copyright (c) 2011 The Chromium Authors. All rights reserved. 24a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch// Use of this source code is governed by a BSD-style license that can be 34a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch// found in the LICENSE file. 44a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 54a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch#include "chrome/common/web_apps.h" 64a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 74a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch#include "base/file_path.h" 84a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch#include "base/file_util.h" 9ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include "base/memory/scoped_ptr.h" 104a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch#include "base/path_service.h" 114a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch#include "base/utf_string_conversions.h" 124a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch#include "base/values.h" 134a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch#include "chrome/common/chrome_paths.h" 144a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch#include "chrome/common/json_schema_validator.h" 15ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen#include "content/common/json_value_serializer.h" 164a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch#include "testing/gtest/include/gtest/gtest.h" 174a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 184a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdochnamespace { 194a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 204a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben MurdochDictionaryValue* LoadDefinitionFile(const std::string& name) { 214a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch FilePath path; 224a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch if (!PathService::Get(chrome::DIR_TEST_DATA, &path)) { 234a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ADD_FAILURE() << "Could not get test data dir."; 244a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch return NULL; 254a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch } 264a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 274a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch path = path.AppendASCII("web_app_info").AppendASCII(name.c_str()); 284a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch if (!file_util::PathExists(path)) { 294a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ADD_FAILURE() << "Path does not exist: " << path.value(); 304a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch return NULL; 314a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch } 324a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 334a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch std::string error; 344a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch JSONFileValueSerializer serializer(path); 354a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch DictionaryValue* result = static_cast<DictionaryValue*>( 364a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch serializer.Deserialize(NULL, &error)); 374a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch if (!result) { 384a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ADD_FAILURE() << "Error parsing " << name << ": " << error; 394a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch return NULL; 404a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch } 414a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 424a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch return result; 434a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch} 444a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 454a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben MurdochWebApplicationInfo* ParseFromDefinitionAndExpectSuccess( 464a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch const std::string& name) { 474a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch scoped_ptr<DictionaryValue> defintion(LoadDefinitionFile(name)); 484a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch if (!defintion.get()) 494a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch return NULL; 504a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 514a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch scoped_ptr<WebApplicationInfo> web_app(new WebApplicationInfo()); 524a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch web_app->manifest_url = GURL("http://example.com/"); 534a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 544a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch string16 error; 55201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch if (!web_apps::ParseWebAppFromDefinitionFile(defintion.get(), web_app.get(), 564a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch &error)) { 574a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ADD_FAILURE() << "Error parsing " << name << ": " << UTF16ToUTF8(error); 584a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch return NULL; 594a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch } 604a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 614a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch return web_app.release(); 624a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch} 634a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 644a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdochvoid ParseFromDefinitionAndExpectFailure(const std::string& name, 654a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch const string16& expected_error) { 664a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch scoped_ptr<DictionaryValue> definition(LoadDefinitionFile(name)); 674a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch if (!definition.get()) 684a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch return; 694a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 704a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch WebApplicationInfo web_app; 714a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch web_app.manifest_url = GURL("http://example.com/"); 724a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 734a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch string16 error; 74201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch if (web_apps::ParseWebAppFromDefinitionFile(definition.get(), &web_app, 75201ade2fbba22bfb27ae029f4d23fca6ded109a0Ben Murdoch &error)) { 764a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ADD_FAILURE() << "Expected error parsing " << name 774a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch << " but parse succeeded."; 784a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch return; 794a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch } 804a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 814a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ(UTF16ToUTF8(expected_error), UTF16ToUTF8(error)) << name; 824a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch} 834a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 844a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch} 854a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 864a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben MurdochTEST(WebAppInfo, ParseFromDefinitionFileErrors) { 874a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch // Test one definition file with a JSON schema error, just to make sure we're 884a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch // correctly propagating those. We don't extensively test all the properties 894a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch // covered by the schema, since we assume JSON schema is working correctly. 904a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ParseFromDefinitionAndExpectFailure( 914a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch "missing_name.json", 924a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch UTF8ToUTF16(std::string("name: ") + 934a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch JSONSchemaValidator::kObjectPropertyIsRequired)); 944a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 954a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ParseFromDefinitionAndExpectFailure( 964a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch "invalid_launch_url.json", 974a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch UTF8ToUTF16(WebApplicationInfo::kInvalidLaunchURL)); 984a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 994a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ParseFromDefinitionAndExpectFailure( 1004a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch "invalid_urls.json", 1014a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch UTF8ToUTF16( 1024a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch JSONSchemaValidator::FormatErrorMessage( 1034a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch WebApplicationInfo::kInvalidURL, "2"))); 1044a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch} 1054a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 1064a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben MurdochTEST(WebAppInfo, Minimal) { 1074a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch scoped_ptr<WebApplicationInfo> web_app( 1084a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ParseFromDefinitionAndExpectSuccess("minimal.json")); 1094a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 1104a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ(UTF8ToUTF16("hello"), web_app->title); 1114a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ(UTF8ToUTF16(""), web_app->description); 1124a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ(GURL("http://example.com/launch_url"), web_app->app_url); 1134a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ(0u, web_app->icons.size()); 1144a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ(0u, web_app->urls.size()); 1154a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ(0u, web_app->permissions.size()); 1164a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ("", web_app->launch_container); 1174a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch} 1184a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 1194a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben MurdochTEST(WebAppInfo, Full) { 1204a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch scoped_ptr<WebApplicationInfo> web_app( 1214a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ParseFromDefinitionAndExpectSuccess("full.json")); 1224a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 1234a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ(UTF8ToUTF16("hello"), web_app->title); 1244a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ(UTF8ToUTF16("This app is super awesome"), web_app->description); 1254a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ(GURL("http://example.com/launch_url"), web_app->app_url); 1264a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ASSERT_EQ(1u, web_app->icons.size()); 1274a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ("http://example.com/16.png", web_app->icons[0].url.spec()); 1284a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ(16, web_app->icons[0].width); 1294a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ(16, web_app->icons[0].height); 1304a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ASSERT_EQ(2u, web_app->urls.size()); 1314a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ("http://example.com/foobar", web_app->urls[0].spec()); 1324a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ("http://example.com/baz", web_app->urls[1].spec()); 1334a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ASSERT_EQ(2u, web_app->permissions.size()); 1344a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ("geolocation", web_app->permissions[0]); 1354a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ("notifications", web_app->permissions[1]); 1364a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch EXPECT_EQ("panel", web_app->launch_container); 1374a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch} 1384a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 1394a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch// Tests ParseIconSizes with various input. 1404a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben MurdochTEST(WebAppInfo, ParseIconSizes) { 1414a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch struct TestData { 1424a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch const char* input; 1434a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch const bool expected_result; 1444a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch const bool is_any; 1454a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch const size_t expected_size_count; 1464a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch const int width1; 1474a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch const int height1; 1484a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch const int width2; 1494a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch const int height2; 1504a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch } data[] = { 1514a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch // Bogus input cases. 1524a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch { "10", false, false, 0, 0, 0, 0, 0 }, 1534a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch { "10 10", false, false, 0, 0, 0, 0, 0 }, 1544a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch { "010", false, false, 0, 0, 0, 0, 0 }, 1554a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch { " 010 ", false, false, 0, 0, 0, 0, 0 }, 1564a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch { " 10x ", false, false, 0, 0, 0, 0, 0 }, 1574a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch { " x10 ", false, false, 0, 0, 0, 0, 0 }, 1584a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch { "any 10x10", false, false, 0, 0, 0, 0, 0 }, 1594a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch { "", false, false, 0, 0, 0, 0, 0 }, 1604a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch { "10ax11", false, false, 0, 0, 0, 0, 0 }, 1614a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 1624a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch // Any. 1634a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch { "any", true, true, 0, 0, 0, 0, 0 }, 1644a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch { " any", true, true, 0, 0, 0, 0, 0 }, 1654a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch { " any ", true, true, 0, 0, 0, 0, 0 }, 1664a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch 1674a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch // Sizes. 1684a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch { "10x11", true, false, 1, 10, 11, 0, 0 }, 1694a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch { " 10x11 ", true, false, 1, 10, 11, 0, 0 }, 1704a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch { " 10x11 1x2", true, false, 2, 10, 11, 1, 2 }, 1714a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch }; 1724a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { 1734a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch bool is_any; 1744a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch std::vector<gfx::Size> sizes; 1754a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch bool result = web_apps::ParseIconSizes(ASCIIToUTF16(data[i].input), &sizes, 1764a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch &is_any); 1774a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ASSERT_EQ(result, data[i].expected_result); 1784a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch if (result) { 1794a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ASSERT_EQ(data[i].is_any, is_any); 1804a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ASSERT_EQ(data[i].expected_size_count, sizes.size()); 181dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen if (!sizes.empty()) { 1824a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ASSERT_EQ(data[i].width1, sizes[0].width()); 1834a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ASSERT_EQ(data[i].height1, sizes[0].height()); 1844a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch } 1854a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch if (sizes.size() > 1) { 1864a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ASSERT_EQ(data[i].width2, sizes[1].width()); 1874a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch ASSERT_EQ(data[i].height2, sizes[1].height()); 1884a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch } 1894a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch } 1904a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch } 1914a5e2dc747d50c653511c68ccb2cfbfb740bd5a7Ben Murdoch} 192