blob: 1f4e4921809669c55e2d94cec2896175eb73bb9f [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/sms/sms_parser.h"
#include <string>
#include "base/optional.h"
#include "base/strings/stringprintf.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
url::Origin ParseOrigin(const std::string& message) {
SmsParser::Result result = SmsParser::Parse(message);
if (!result.IsValid())
return url::Origin();
return result.origin;
}
std::string ParseOTP(const std::string& message) {
SmsParser::Result result = SmsParser::Parse(message);
if (!result.IsValid())
return "";
return result.one_time_code;
}
} // namespace
TEST(SmsParserTest, NoToken) {
ASSERT_FALSE(SmsParser::Parse("foo").IsValid());
}
TEST(SmsParserTest, WithTokenInvalidUrl) {
ASSERT_FALSE(SmsParser::Parse("@foo").IsValid());
}
TEST(SmsParserTest, NoSpace) {
ASSERT_FALSE(SmsParser::Parse("@example.com#12345").IsValid());
}
TEST(SmsParserTest, MultipleSpace) {
ASSERT_FALSE(SmsParser::Parse("@example.com #12345").IsValid());
}
TEST(SmsParserTest, WhiteSpaceThatIsNotSpace) {
ASSERT_FALSE(SmsParser::Parse("@example.com\t#12345").IsValid());
}
TEST(SmsParserTest, WordInBetween) {
ASSERT_FALSE(SmsParser::Parse("@example.com random #12345").IsValid());
}
TEST(SmsParserTest, InvalidUrl) {
ASSERT_FALSE(SmsParser::Parse("@//example.com #123").IsValid());
}
TEST(SmsParserTest, FtpScheme) {
ASSERT_FALSE(SmsParser::Parse("@ftp://example.com #123").IsValid());
}
TEST(SmsParserTest, Mailto) {
ASSERT_FALSE(SmsParser::Parse("@mailto:goto@chromium.org #123").IsValid());
}
TEST(SmsParserTest, MissingOneTimeCodeParameter) {
ASSERT_FALSE(SmsParser::Parse("@example.com").IsValid());
}
TEST(SmsParserTest, Basic) {
auto result = SmsParser::Parse("@example.com #12345");
ASSERT_TRUE(result.IsValid());
EXPECT_EQ("12345", result.one_time_code);
EXPECT_EQ(url::Origin::Create(GURL("https://example.com")), result.origin);
}
TEST(SmsParserTest, Realistic) {
EXPECT_EQ(url::Origin::Create(GURL("https://example.com")),
ParseOrigin("<#> Your OTP is 1234ABC.\n@example.com #12345"));
}
TEST(SmsParserTest, OneTimeCode) {
EXPECT_EQ("123", ParseOTP("@example.com #123"));
}
TEST(SmsParserTest, LocalhostForDevelopment) {
EXPECT_EQ(url::Origin::Create(GURL("http://localhost")),
ParseOrigin("@localhost #123"));
ASSERT_FALSE(SmsParser::Parse("@localhost:8080 #123").IsValid());
ASSERT_FALSE(SmsParser::Parse("@localhost").IsValid());
}
TEST(SmsParserTest, Paths) {
ASSERT_FALSE(SmsParser::Parse("@example.com/foobar #123").IsValid());
}
TEST(SmsParserTest, Message) {
EXPECT_EQ(url::Origin::Create(GURL("https://example.com")),
ParseOrigin("hello world\n@example.com #123"));
}
TEST(SmsParserTest, Whitespace) {
EXPECT_EQ(url::Origin::Create(GURL("https://example.com")),
ParseOrigin("hello world\n@example.com #123 "));
}
TEST(SmsParserTest, Dashes) {
EXPECT_EQ(url::Origin::Create(GURL("https://web-otp-example.com")),
ParseOrigin("@web-otp-example.com #123"));
}
TEST(SmsParserTest, CapitalLetters) {
EXPECT_EQ(url::Origin::Create(GURL("https://can-contain-CAPITAL.com")),
ParseOrigin("@can-contain-CAPITAL.com #123"));
}
TEST(SmsParserTest, Numbers) {
EXPECT_EQ(url::Origin::Create(GURL("https://can-contain-number-9870.com")),
ParseOrigin("@can-contain-number-9870.com #123"));
}
TEST(SmsParserTest, ForbiddenCharacters) {
// TODO(majidvp): Domains with unicode characters are valid.
// See: https://url.spec.whatwg.org/#concept-domain-to-ascii
// EXPECT_EQ(url::Origin::Create(GURL("can-contain-unicode-like-חומוס.com")),
// ParseOrigin("@can-contain-unicode-like-חומוס.com #123"));
// Forbidden codepoints https://url.spec.whatwg.org/#forbidden-host-code-point
const char forbidden_chars[] = {'\x00' /* null */,
'\x09' /* TAB */,
'\x0A' /* LF */,
'\x0D' /* CR */,
' ',
'#',
'%',
'/',
':',
'<',
'>',
'?',
'@',
'[',
'\\',
']',
'^'};
for (char c : forbidden_chars) {
ASSERT_FALSE(
SmsParser::Parse(base::StringPrintf("@cannot-contain-%c #123456", c))
.IsValid());
}
}
TEST(SmsParserTest, Newlines) {
EXPECT_EQ(url::Origin::Create(GURL("https://example.com")),
ParseOrigin("hello world\n@example.com #123\n"));
}
TEST(SmsParserTest, TwoTokens) {
EXPECT_EQ(url::Origin::Create(GURL("https://b.com")),
ParseOrigin("@a.com @b.com #123"));
EXPECT_EQ(url::Origin::Create(GURL("https://a.com")),
ParseOrigin("@a.com #123 @b.com"));
}
TEST(SmsParserTest, Ports) {
ASSERT_FALSE(SmsParser::Parse("@a.com:8443 #123").IsValid());
}
TEST(SmsParserTest, Username) {
ASSERT_FALSE(SmsParser::Parse("@username@a.com #123").IsValid());
}
TEST(SmsParserTest, QueryParams) {
ASSERT_FALSE(SmsParser::Parse("@a.com/?foo=123 #123").IsValid());
}
TEST(SmsParserTest, HarmlessOriginsButInvalid) {
ASSERT_FALSE(SmsParser::Parse("@data://123").IsValid());
}
TEST(SmsParserTest, AppHash) {
EXPECT_EQ(
url::Origin::Create(GURL("https://example.com")),
ParseOrigin("<#> Hello World\nApp Hash: s3LhKBB0M33\n@example.com #123"));
}
TEST(SmsParserTest, OneTimeCodeCharRanges) {
EXPECT_EQ("cannot-contain-hashes",
ParseOTP("@example.com #cannot-contain-hashes#yes"));
EXPECT_EQ("can-contain-numbers-like-123",
ParseOTP("@example.com #can-contain-numbers-like-123"));
EXPECT_EQ("can-contain-utf8-like-🤷",
ParseOTP("@example.com #can-contain-utf8-like-🤷"));
EXPECT_EQ("can-contain-chars-like-*^$@",
ParseOTP("@example.com #can-contain-chars-like-*^$@"));
EXPECT_EQ("human-readable-words-like-sillyface",
ParseOTP("@example.com #human-readable-words-like-sillyface"));
EXPECT_EQ("can-it-be-super-lengthy-like-a-lot",
ParseOTP("@example.com #can-it-be-super-lengthy-like-a-lot"));
EXPECT_EQ("1", ParseOTP("@example.com #1 can be short"));
EXPECT_EQ("otp", ParseOTP("@example.com #otp with space"));
EXPECT_EQ("otp", ParseOTP("@example.com #otp\twith with tab"));
}
} // namespace content