15d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// Copyright (c) 2013 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/json/string_escape.h"
65d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
75d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "base/strings/string_util.h"
8868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/strings/utf_string_conversions.h"
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "testing/gtest/include/gtest/gtest.h"
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace base {
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)TEST(JSONStringEscapeTest, EscapeUTF8) {
145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  const struct {
155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    const char* to_escape;
165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    const char* escaped;
175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  } cases[] = {
185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    {"\b\001aZ\"\\wee", "\\b\\u0001aZ\\\"\\\\wee"},
195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    {"a\b\f\n\r\t\v\1\\.\"z",
205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        "a\\b\\f\\n\\r\\t\\u000B\\u0001\\\\.\\\"z"},
215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    {"b\x0f\x7f\xf0\xff!",  // \xf0\xff is not a valid UTF-8 unit.
225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        "b\\u000F\x7F\xEF\xBF\xBD\xEF\xBF\xBD!"},
235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    {"c<>d", "c\\u003C>d"},
245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  };
255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    const char* in_ptr = cases[i].to_escape;
2858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)    std::string in_str = in_ptr;
295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    std::string out;
315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EscapeJSONString(in_ptr, false, &out);
325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EXPECT_EQ(std::string(cases[i].escaped), out);
335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EXPECT_TRUE(IsStringUTF8(out));
345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)    out.erase();
365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    bool convert_ok = EscapeJSONString(in_str, false, &out);
375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EXPECT_EQ(std::string(cases[i].escaped), out);
385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EXPECT_TRUE(IsStringUTF8(out));
395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if (convert_ok) {
415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      std::string fooout = GetQuotedJSONString(in_str);
425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      EXPECT_EQ("\"" + std::string(cases[i].escaped) + "\"", fooout);
435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      EXPECT_TRUE(IsStringUTF8(out));
445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    }
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  std::string in = cases[0].to_escape;
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  std::string out;
495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EscapeJSONString(in, false, &out);
505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_TRUE(IsStringUTF8(out));
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // test quoting
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  std::string out_quoted;
545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EscapeJSONString(in, true, &out_quoted);
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_EQ(out.length() + 2, out_quoted.length());
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_EQ(out_quoted.find(out), 1U);
575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_TRUE(IsStringUTF8(out_quoted));
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // now try with a NULL in the string
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  std::string null_prepend = "test";
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  null_prepend.push_back(0);
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  in = null_prepend + in;
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  std::string expected = "test\\u0000";
645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  expected += cases[0].escaped;
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  out.clear();
665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EscapeJSONString(in, false, &out);
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_EQ(expected, out);
685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_TRUE(IsStringUTF8(out));
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)TEST(JSONStringEscapeTest, EscapeUTF16) {
725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  const struct {
735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    const wchar_t* to_escape;
745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    const char* escaped;
755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  } cases[] = {
765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    {L"b\uffb1\u00ff", "b\xEF\xBE\xB1\xC3\xBF"},
775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    {L"\b\001aZ\"\\wee", "\\b\\u0001aZ\\\"\\\\wee"},
785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    {L"a\b\f\n\r\t\v\1\\.\"z",
795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        "a\\b\\f\\n\\r\\t\\u000B\\u0001\\\\.\\\"z"},
805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    {L"b\x0f\x7f\xf0\xff!", "b\\u000F\x7F\xC3\xB0\xC3\xBF!"},
815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    {L"c<>d", "c\\u003C>d"},
825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  };
835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    string16 in = WideToUTF16(cases[i].to_escape);
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    std::string out;
885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EscapeJSONString(in, false, &out);
895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EXPECT_EQ(std::string(cases[i].escaped), out);
905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EXPECT_TRUE(IsStringUTF8(out));
915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    out = GetQuotedJSONString(in);
935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EXPECT_EQ("\"" + std::string(cases[i].escaped) + "\"", out);
945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EXPECT_TRUE(IsStringUTF8(out));
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  string16 in = WideToUTF16(cases[0].to_escape);
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  std::string out;
995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EscapeJSONString(in, false, &out);
1005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_TRUE(IsStringUTF8(out));
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // test quoting
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  std::string out_quoted;
1045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EscapeJSONString(in, true, &out_quoted);
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_EQ(out.length() + 2, out_quoted.length());
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_EQ(out_quoted.find(out), 1U);
1075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_TRUE(IsStringUTF8(out));
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // now try with a NULL in the string
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  string16 null_prepend = WideToUTF16(L"test");
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  null_prepend.push_back(0);
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  in = null_prepend + in;
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  std::string expected = "test\\u0000";
1145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  expected += cases[0].escaped;
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  out.clear();
1165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EscapeJSONString(in, false, &out);
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  EXPECT_EQ(expected, out);
1185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_TRUE(IsStringUTF8(out));
1195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
1205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)TEST(JSONStringEscapeTest, EscapeUTF16OutsideBMP) {
1225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  {
1235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    // {a, U+10300, !}, SMP.
1245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    string16 test;
1255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    test.push_back('a');
1265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    test.push_back(0xD800);
1275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    test.push_back(0xDF00);
1285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    test.push_back('!');
1295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    std::string actual;
1305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EXPECT_TRUE(EscapeJSONString(test, false, &actual));
1315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EXPECT_EQ("a\xF0\x90\x8C\x80!", actual);
1325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
1335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  {
1345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    // {U+20021, U+2002B}, SIP.
1355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    string16 test;
1365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    test.push_back(0xD840);
1375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    test.push_back(0xDC21);
1385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    test.push_back(0xD840);
1395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    test.push_back(0xDC2B);
1405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    std::string actual;
1415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EXPECT_TRUE(EscapeJSONString(test, false, &actual));
1425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EXPECT_EQ("\xF0\xA0\x80\xA1\xF0\xA0\x80\xAB", actual);
1435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
1445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  {
1455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    // {?, U+D800, @}, lone surrogate.
1465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    string16 test;
1475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    test.push_back('?');
1485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    test.push_back(0xD800);
1495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    test.push_back('@');
1505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    std::string actual;
1515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EXPECT_FALSE(EscapeJSONString(test, false, &actual));
1525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EXPECT_EQ("?\xEF\xBF\xBD@", actual);
1535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
1545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
1555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)TEST(JSONStringEscapeTest, EscapeBytes) {
1575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  const struct {
1585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    const char* to_escape;
1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    const char* escaped;
1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  } cases[] = {
1615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    {"b\x0f\x7f\xf0\xff!", "b\\u000F\\u007F\\u00F0\\u00FF!"},
1625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    {"\xe5\xc4\x4f\x05\xb6\xfd\0", "\\u00E5\\u00C4O\\u0005\\u00B6\\u00FD"},
1635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  };
1645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
1665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    std::string in = std::string(cases[i].to_escape);
1675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EXPECT_FALSE(IsStringUTF8(in));
1685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EXPECT_EQ(std::string(cases[i].escaped),
1705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        EscapeBytesAsInvalidJSONString(in, false));
1715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    EXPECT_EQ("\"" + std::string(cases[i].escaped) + "\"",
1725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        EscapeBytesAsInvalidJSONString(in, true));
1735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
1745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  const char kEmbedNull[] = { '\xab', '\x39', '\0', '\x9f', '\xab' };
1765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  std::string in(kEmbedNull, ARRAYSIZE_UNSAFE(kEmbedNull));
1775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_FALSE(IsStringUTF8(in));
1785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  EXPECT_EQ(std::string("\\u00AB9\\u0000\\u009F\\u00AB"),
1795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            EscapeBytesAsInvalidJSONString(in, false));
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace base
183