Commit 505e8e9c authored by Roger McFarlane's avatar Roger McFarlane Committed by Commit Bot

[autofill] Make form_structure_browsertest.cc use embedded_test_server.

The data driven tests in form_structure_browsertes.cc were navigating to
test pages using data URLs. These have no way to generate relative URLs
for form actions, so the output was being generated incorrectly for
forms that would be evaluated as !ShouldBeParsed due to having "search"
actions.

This CL uses the embedded test server to serve the test pages instead.

Bug: 789944
Change-Id: I9bdb6fa376dba070195d125807fb5e4b5443a203
Reviewed-on: https://chromium-review.googlesource.com/804054
Commit-Queue: Roger McFarlane <rogerm@chromium.org>
Reviewed-by: default avatarMathieu Perreault <mathp@chromium.org>
Cr-Commit-Position: refs/heads/master@{#521517}
parent 312031ee
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include <algorithm> #include <algorithm>
#include <vector> #include <vector>
#include "base/bind.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/feature_list.h" #include "base/feature_list.h"
#include "base/files/file_enumerator.h" #include "base/files/file_enumerator.h"
...@@ -28,6 +29,10 @@ ...@@ -28,6 +29,10 @@
#include "components/autofill/core/browser/form_structure.h" #include "components/autofill/core/browser/form_structure.h"
#include "components/autofill/core/common/autofill_features.h" #include "components/autofill/core/common/autofill_features.h"
#include "content/public/common/content_switches.h" #include "content/public/common/content_switches.h"
#include "net/http/http_status_code.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "url/gurl.h" #include "url/gurl.h"
#if defined(OS_MACOSX) #if defined(OS_MACOSX)
...@@ -37,21 +42,15 @@ ...@@ -37,21 +42,15 @@
namespace autofill { namespace autofill {
namespace { namespace {
using net::test_server::BasicHttpResponse;
using net::test_server::HttpRequest;
using net::test_server::HttpResponse;
const base::FilePath::CharType kTestName[] = FILE_PATH_LITERAL("heuristics"); const base::FilePath::CharType kTestName[] = FILE_PATH_LITERAL("heuristics");
// Convert the |html| snippet to a data URI. const std::set<base::FilePath::StringType>& GetFailingTestNames() {
GURL HTMLToDataURI(const std::string& html) { static auto* failing_test_names = new std::set<base::FilePath::StringType>{};
// GURL requires data URLs to be UTF-8 and will fail below if it's not. return *failing_test_names;
CHECK(base::IsStringUTF8(html)) << "Input file is not UTF-8.";
// Strip `\n`, `\t`, `\r` from |html| to match old `data:` URL behavior.
std::string stripped_html;
for (const auto& character : html) {
if (character == '\n' || character == '\t' || character == '\r')
continue;
stripped_html.push_back(character);
}
return GURL(std::string("data:text/html;charset=utf-8,") + stripped_html);
} }
const base::FilePath& GetTestDataDir() { const base::FilePath& GetTestDataDir() {
...@@ -65,16 +64,17 @@ const base::FilePath& GetTestDataDir() { ...@@ -65,16 +64,17 @@ const base::FilePath& GetTestDataDir() {
return dir; return dir;
} }
const std::vector<base::FilePath> GetTestFiles() { const base::FilePath GetInputDir() {
base::FilePath dir; static base::FilePath input_dir = GetTestDataDir()
CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &dir)); .AppendASCII("autofill")
dir = dir.AppendASCII("components") .Append(kTestName)
.AppendASCII("test") .AppendASCII("input");
.AppendASCII("data") return input_dir;
.AppendASCII("autofill") }
.Append(kTestName)
.AppendASCII("input"); std::vector<base::FilePath> GetTestFiles() {
base::FileEnumerator input_files(dir, false, base::FileEnumerator::FILES); base::FileEnumerator input_files(GetInputDir(), false,
base::FileEnumerator::FILES);
std::vector<base::FilePath> files; std::vector<base::FilePath> files;
for (base::FilePath input_file = input_files.Next(); !input_file.empty(); for (base::FilePath input_file = input_files.Next(); !input_file.empty();
input_file = input_files.Next()) { input_file = input_files.Next()) {
...@@ -89,14 +89,20 @@ const std::vector<base::FilePath> GetTestFiles() { ...@@ -89,14 +89,20 @@ const std::vector<base::FilePath> GetTestFiles() {
return files; return files;
} }
const std::set<base::FilePath::StringType>& GetFailingTestNames() { std::string FormStructuresToString(
// TODO(crbug.com/789944): Reenable these tests. const std::vector<std::unique_ptr<FormStructure>>& forms) {
static std::set<base::FilePath::StringType>* failing_test_names = std::string forms_string;
new std::set<base::FilePath::StringType>{ for (const auto& form : forms) {
FILE_PATH_LITERAL("067_register_rei.com.html"), for (const auto& field : *form) {
FILE_PATH_LITERAL("074_register_threadless.com.html"), forms_string += field->Type().ToString();
}; forms_string += " | " + base::UTF16ToUTF8(field->name);
return *failing_test_names; forms_string += " | " + base::UTF16ToUTF8(field->label);
forms_string += " | " + base::UTF16ToUTF8(field->value);
forms_string += " | " + field->section();
forms_string += "\n";
}
}
return forms_string;
} }
} // namespace } // namespace
...@@ -113,21 +119,24 @@ class FormStructureBrowserTest ...@@ -113,21 +119,24 @@ class FormStructureBrowserTest
~FormStructureBrowserTest() override; ~FormStructureBrowserTest() override;
// InProcessBrowserTest // InProcessBrowserTest
void SetUpCommandLine(base::CommandLine* command_line) override { void SetUpCommandLine(base::CommandLine* command_line) override;
// Suppress most output logs because we can't really control the output for
// arbitrary test sites. // BrowserTestBase
command_line->AppendSwitchASCII(switches::kLoggingLevel, "2"); void SetUpOnMainThread() override;
}
// DataDrivenTest: // DataDrivenTest:
void GenerateResults(const std::string& input, std::string* output) override; void GenerateResults(const std::string& input, std::string* output) override;
// Serializes the given |forms| into a string.
std::string FormStructuresToString(
const std::vector<std::unique_ptr<FormStructure>>& forms);
private: private:
std::unique_ptr<HttpResponse> HandleRequest(const HttpRequest& request);
base::test::ScopedFeatureList feature_list_; base::test::ScopedFeatureList feature_list_;
// The response content to be returned by the embedded test server. Note that
// this is populated in the main thread as a part of the setup in the
// GenerateResults method but it is consumed later in the IO thread by the
// embedded test server to generate the response.
std::string html_content_;
DISALLOW_COPY_AND_ASSIGN(FormStructureBrowserTest); DISALLOW_COPY_AND_ASSIGN(FormStructureBrowserTest);
}; };
...@@ -140,11 +149,42 @@ FormStructureBrowserTest::FormStructureBrowserTest() ...@@ -140,11 +149,42 @@ FormStructureBrowserTest::FormStructureBrowserTest()
FormStructureBrowserTest::~FormStructureBrowserTest() { FormStructureBrowserTest::~FormStructureBrowserTest() {
} }
void FormStructureBrowserTest::SetUpCommandLine(
base::CommandLine* command_line) {
InProcessBrowserTest::SetUpCommandLine(command_line);
// Suppress most output logs because we can't really control the output for
// arbitrary test sites.
command_line->AppendSwitchASCII(switches::kLoggingLevel, "2");
}
void FormStructureBrowserTest::SetUpOnMainThread() {
InProcessBrowserTest::SetUpOnMainThread();
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&FormStructureBrowserTest::HandleRequest, base::Unretained(this)));
ASSERT_TRUE(embedded_test_server()->Start());
}
void FormStructureBrowserTest::GenerateResults(const std::string& input, void FormStructureBrowserTest::GenerateResults(const std::string& input,
std::string* output) { std::string* output) {
ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), // Cache the content to be returned by the embedded test server. This data
HTMLToDataURI(input))); // is readonly after this point.
html_content_.clear();
html_content_.reserve(input.length());
for (const char c : input) {
// Strip `\n`, `\t`, `\r` from |html| to match old `data:` URL behavior.
// TODO(crbug/239819): the tests expect weird concatenation behavior based
// legacy data URL behavior. Fix this so the the tests better represent
// the parsing being done in the wild.
if (c != '\r' && c != '\n' && c != '\t')
html_content_.push_back(c);
}
// Navigate to the test html content.
ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/test.html")));
// Dump the form fields (and their inferred field types).
content::WebContents* web_contents = content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents(); browser()->tab_strip_model()->GetActiveWebContents();
ContentAutofillDriver* autofill_driver = ContentAutofillDriver* autofill_driver =
...@@ -158,20 +198,13 @@ void FormStructureBrowserTest::GenerateResults(const std::string& input, ...@@ -158,20 +198,13 @@ void FormStructureBrowserTest::GenerateResults(const std::string& input,
*output = FormStructuresToString(forms); *output = FormStructuresToString(forms);
} }
std::string FormStructureBrowserTest::FormStructuresToString( std::unique_ptr<HttpResponse> FormStructureBrowserTest::HandleRequest(
const std::vector<std::unique_ptr<FormStructure>>& forms) { const HttpRequest& request) {
std::string forms_string; auto response = std::make_unique<BasicHttpResponse>();
for (const auto& form : forms) { response->set_code(net::HTTP_OK);
for (const auto& field : *form) { response->set_content(html_content_);
forms_string += field->Type().ToString(); response->set_content_type("text/html; charset=utf-8");
forms_string += " | " + base::UTF16ToUTF8(field->name); return std::move(response);
forms_string += " | " + base::UTF16ToUTF8(field->label);
forms_string += " | " + base::UTF16ToUTF8(field->value);
forms_string += " | " + field->section();
forms_string += "\n";
}
}
return forms_string;
} }
IN_PROC_BROWSER_TEST_P(FormStructureBrowserTest, DataDrivenHeuristics) { IN_PROC_BROWSER_TEST_P(FormStructureBrowserTest, DataDrivenHeuristics) {
......
...@@ -32,6 +32,8 @@ ...@@ -32,6 +32,8 @@
#include "components/autofill/core/browser/rationalization_util.h" #include "components/autofill/core/browser/rationalization_util.h"
#include "components/autofill/core/browser/validation.h" #include "components/autofill/core/browser/validation.h"
#include "components/autofill/core/common/autofill_constants.h" #include "components/autofill/core/common/autofill_constants.h"
#include "components/autofill/core/common/autofill_regex_constants.h"
#include "components/autofill/core/common/autofill_regexes.h"
#include "components/autofill/core/common/autofill_util.h" #include "components/autofill/core/common/autofill_util.h"
#include "components/autofill/core/common/form_data.h" #include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/form_data_predictions.h" #include "components/autofill/core/common/form_data_predictions.h"
...@@ -645,10 +647,11 @@ bool FormStructure::ShouldBeParsed() const { ...@@ -645,10 +647,11 @@ bool FormStructure::ShouldBeParsed() const {
return false; return false;
} }
// Rule out http(s)://*/search?... // Rule out search forms.
// e.g. http://www.google.com/search?q=... static const base::string16 kUrlSearchActionPattern =
// http://search.yahoo.com/search?p=... base::UTF8ToUTF16(kUrlSearchActionRe);
if (target_url_.path_piece() == "/search") { if (MatchesPattern(base::UTF8ToUTF16(target_url_.path_piece()),
kUrlSearchActionPattern)) {
return false; return false;
} }
......
...@@ -297,6 +297,10 @@ const char kPhoneSuffixRe[] = "suffix"; ...@@ -297,6 +297,10 @@ const char kPhoneSuffixRe[] = "suffix";
const char kPhoneExtensionRe[] = const char kPhoneExtensionRe[] =
"\\bext|ext\\b|extension" "\\bext|ext\\b|extension"
"|ramal"; // pt-BR, pt-PT "|ramal"; // pt-BR, pt-PT
/////////////////////////////////////////////////////////////////////////////
// validation.cc
/////////////////////////////////////////////////////////////////////////////
const char kUPIVirtualPaymentAddressRe[] = const char kUPIVirtualPaymentAddressRe[] =
"^\\w+@(" "^\\w+@("
"allbank|" // Allahabad Bank UPI "allbank|" // Allahabad Bank UPI
...@@ -348,4 +352,9 @@ const char kUPIVirtualPaymentAddressRe[] = ...@@ -348,4 +352,9 @@ const char kUPIVirtualPaymentAddressRe[] =
"yesbank" // NuPay "yesbank" // NuPay
")$"; ")$";
/////////////////////////////////////////////////////////////////////////////
// form_structure.cc
/////////////////////////////////////////////////////////////////////////////
const char kUrlSearchActionRe[] = "/search(/|((\\w*\\.\\w+)?$))";
} // namespace autofill } // namespace autofill
...@@ -62,6 +62,14 @@ extern const char kPhoneExtensionRe[]; ...@@ -62,6 +62,14 @@ extern const char kPhoneExtensionRe[];
// - https://upipayments.co.in/virtual-payment-address-vpa/ // - https://upipayments.co.in/virtual-payment-address-vpa/
extern const char kUPIVirtualPaymentAddressRe[]; extern const char kUPIVirtualPaymentAddressRe[];
// Match the path values for form actions that look like generic search:
// e.g. /search
// /search/
// /search/products...
// /products/search/
// /blah/search_all.jsp
extern const char kUrlSearchActionRe[];
} // namespace autofill } // namespace autofill
#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_REGEX_CONSTANTS_H_ #endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_REGEX_CONSTANTS_H_
...@@ -3,10 +3,6 @@ UNKNOWN_TYPE | logonPassword | password | password | logonUsername_1-default ...@@ -3,10 +3,6 @@ UNKNOWN_TYPE | logonPassword | password | password | logonUsername_1-default
UNKNOWN_TYPE | zip | Find a store | Enter ZIP/Postal | zip_1-default UNKNOWN_TYPE | zip | Find a store | Enter ZIP/Postal | zip_1-default
UNKNOWN_TYPE | username | Access your wish list | Email | username_1-default UNKNOWN_TYPE | username | Access your wish list | Email | username_1-default
UNKNOWN_TYPE | password | Password | Password | username_1-default UNKNOWN_TYPE | password | Password | Password | username_1-default
NAME_FIRST | firstName | Search wish lists by name | First Name | firstName_1-default
NAME_LAST | lastName | Last Name | Last Name | firstName_1-default
EMAIL_ADDRESS | email | Or search wish lists by email | Email | firstName_1-default
UNKNOWN_TYPE | /aeo/commerce/search/formhandlers/AEOQueryFormHandler.searchRequest.question | Keyword or Style # | Keyword or Style # | /aeo/commerce/search/formhandlers/AEOQueryFormHandler.searchRequest.question_1-default
ADDRESS_HOME_COUNTRY | countryType | Country Type | usa | countryType_1-default ADDRESS_HOME_COUNTRY | countryType | Country Type | usa | countryType_1-default
ADDRESS_HOME_COUNTRY | country | Country, APO/FPO | US | countryType_1-default ADDRESS_HOME_COUNTRY | country | Country, APO/FPO | US | countryType_1-default
NAME_FIRST | firstName | First Name | | countryType_1-default NAME_FIRST | firstName | First Name | | countryType_1-default
......
UNKNOWN_TYPE | zip | Your ZIP Code | Your ZIP Code | zip_1-default
UNKNOWN_TYPE | Ntt | Search PETCO.com | Search PETCO.com | Ntt_1-default
UNKNOWN_TYPE | txtEmail | Your Email Address | Your Email Address | txtEmail_1-default UNKNOWN_TYPE | txtEmail | Your Email Address | Your Email Address | txtEmail_1-default
NAME_FIRST | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName | First Name * | | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName_1-default NAME_FIRST | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName | First Name * | | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName_1-default
NAME_LAST | ctl00$ctl00$cphBody$cphBody$txtSA_LastName | Last Name * | | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName_1-default NAME_LAST | ctl00$ctl00$cphBody$cphBody$txtSA_LastName | Last Name * | | ctl00$ctl00$cphBody$cphBody$txtSA_FirstName_1-default
......
UNKNOWN_TYPE | kw | search petsmart.com | search petsmart.com | kw_1-default
ADDRESS_HOME_COUNTRY | billCountry | • Country: | US | billCountry_1-default ADDRESS_HOME_COUNTRY | billCountry | • Country: | US | billCountry_1-default
NAME_FIRST | billFname | • First Name: | | billCountry_1-default NAME_FIRST | billFname | • First Name: | | billCountry_1-default
NAME_LAST | billLname | • Last Name: | | billCountry_1-default NAME_LAST | billLname | • Last Name: | | billCountry_1-default
......
UNKNOWN_TYPE | searchPhrase | | | searchPhrase_1-default
UNKNOWN_TYPE | shipto | Select a Saved Shipping Address | | shipto_1-default UNKNOWN_TYPE | shipto | Select a Saved Shipping Address | | shipto_1-default
NAME_FIRST | shippingFirstName | First Name | First Name | shipto_1-default NAME_FIRST | shippingFirstName | First Name | First Name | shipto_1-default
NAME_LAST | shippingLastName | Last Name | Last Name | shipto_1-default NAME_LAST | shippingLastName | Last Name | Last Name | shipto_1-default
......
UNKNOWN_TYPE | words | Search by keyword, recipe or item # | Enter keyword, recipe or item # | words_1-default
NAME_FULL | shipTos[0].address.fullName | Full Name* | | shipTos[0].address.fullName_1-default NAME_FULL | shipTos[0].address.fullName | Full Name* | | shipTos[0].address.fullName_1-default
ADDRESS_HOME_LINE1 | shipTos[0].address.addrLine1 | Address* | | shipTos[0].address.fullName_1-default ADDRESS_HOME_LINE1 | shipTos[0].address.addrLine1 | Address* | | shipTos[0].address.fullName_1-default
ADDRESS_HOME_LINE2 | shipTos[0].address.addrLine2 | Address Line 2 | | shipTos[0].address.fullName_1-default ADDRESS_HOME_LINE2 | shipTos[0].address.addrLine2 | Address Line 2 | | shipTos[0].address.fullName_1-default
......
UNKNOWN_TYPE | Keyword | | | Keyword_1-default
NAME_FIRST | FirstName | First Name | | FirstName_1-default NAME_FIRST | FirstName | First Name | | FirstName_1-default
NAME_LAST | LastName | Last Name | | FirstName_1-default NAME_LAST | LastName | Last Name | | FirstName_1-default
ADDRESS_HOME_LINE1 | Address1 | Address | | FirstName_1-default ADDRESS_HOME_LINE1 | Address1 | Address | | FirstName_1-default
......
UNKNOWN_TYPE | search_query | Search | | search_query_1-default
EMAIL_ADDRESS | FormField[1][1] | * Email Address: | | FormField[1][1]_1-default EMAIL_ADDRESS | FormField[1][1] | * Email Address: | | FormField[1][1]_1-default
UNKNOWN_TYPE | FormField[1][2] | * Password: | | FormField[1][1]_1-default UNKNOWN_TYPE | FormField[1][2] | * Password: | | FormField[1][1]_1-default
UNKNOWN_TYPE | FormField[1][3] | * Confirm Password: | | FormField[1][1]_1-default UNKNOWN_TYPE | FormField[1][3] | * Confirm Password: | | FormField[1][1]_1-default
......
UNKNOWN_TYPE | q | Search People | | q_1-default
UNKNOWN_TYPE | accountType | Personal | 2 | accountType_1-default UNKNOWN_TYPE | accountType | Personal | 2 | accountType_1-default
UNKNOWN_TYPE | accountType | Musician | 7 | accountType_1-default UNKNOWN_TYPE | accountType | Musician | 7 | accountType_1-default
UNKNOWN_TYPE | accountType | Comedian | 15 | accountType_1-default UNKNOWN_TYPE | accountType | Comedian | 15 | accountType_1-default
......
UNKNOWN_TYPE | zip | | | zip_1-default UNKNOWN_TYPE | zip | | | zip_1-default
UNKNOWN_TYPE | Ntt | Search | | Ntt_1-default
NAME_FIRST | addrsForm[0].firstName | *First Name: | | addrsForm[0].firstName_1-default NAME_FIRST | addrsForm[0].firstName | *First Name: | | addrsForm[0].firstName_1-default
NAME_MIDDLE_INITIAL | addrsForm[0].middleInitial | Middle Initial: | | addrsForm[0].firstName_1-default NAME_MIDDLE_INITIAL | addrsForm[0].middleInitial | Middle Initial: | | addrsForm[0].firstName_1-default
NAME_LAST | addrsForm[0].lastName | *Last Name: | | addrsForm[0].firstName_1-default NAME_LAST | addrsForm[0].lastName | *Last Name: | | addrsForm[0].firstName_1-default
......
...@@ -6,4 +6,3 @@ EMAIL_ADDRESS | /atg/userprofiling/ProfileFormHandler.confirmEmailAddress | * Co ...@@ -6,4 +6,3 @@ EMAIL_ADDRESS | /atg/userprofiling/ProfileFormHandler.confirmEmailAddress | * Co
ADDRESS_HOME_ZIP | /atg/userprofiling/ProfileFormHandler.value.zip | * Zip: | | /atg/userprofiling/ProfileFormHandler.value.maxPerks_1-default ADDRESS_HOME_ZIP | /atg/userprofiling/ProfileFormHandler.value.zip | * Zip: | | /atg/userprofiling/ProfileFormHandler.value.maxPerks_1-default
UNKNOWN_TYPE | /atg/userprofiling/ProfileFormHandler.value.password | * Password: | | /atg/userprofiling/ProfileFormHandler.value.maxPerks_1-default UNKNOWN_TYPE | /atg/userprofiling/ProfileFormHandler.value.password | * Password: | | /atg/userprofiling/ProfileFormHandler.value.maxPerks_1-default
UNKNOWN_TYPE | /atg/userprofiling/ProfileFormHandler.value.confirmPassword | * Confirm Password: | | /atg/userprofiling/ProfileFormHandler.value.maxPerks_1-default UNKNOWN_TYPE | /atg/userprofiling/ProfileFormHandler.value.confirmPassword | * Confirm Password: | | /atg/userprofiling/ProfileFormHandler.value.maxPerks_1-default
UNKNOWN_TYPE | freeText | Search by Keyword or Item # | Search by Keyword or Item # | freeText_1-default
UNKNOWN_TYPE | q | search | enter keyword(s) or item number | q_1-default
EMAIL_ADDRESS | E1 | Email Address * | | E1_1-default EMAIL_ADDRESS | E1 | Email Address * | | E1_1-default
NAME_FIRST | F1 | First Name * | | E1_1-default NAME_FIRST | F1 | First Name * | | E1_1-default
NAME_LAST | L1 | Last Name * | | E1_1-default NAME_LAST | L1 | Last Name * | | E1_1-default
......
UNKNOWN_TYPE | q | Find Open Source Software | | q_1-default
NAME_FULL | X1mRVeMqejLnZpd1etxNGHllat2M | Name: | | X1mRVeMqejLnZpd1etxNGHllat2M_1-default NAME_FULL | X1mRVeMqejLnZpd1etxNGHllat2M | Name: | | X1mRVeMqejLnZpd1etxNGHllat2M_1-default
EMAIL_ADDRESS | X129ZdMbfixhIflk8_zHDFWB72qk | Email: | | X1mRVeMqejLnZpd1etxNGHllat2M_1-default EMAIL_ADDRESS | X129ZdMbfixhIflk8_zHDFWB72qk | Email: | | X1mRVeMqejLnZpd1etxNGHllat2M_1-default
UNKNOWN_TYPE | X2n9HcN3dx1-mSbLywp_L-szMydw | Username: | | X1mRVeMqejLnZpd1etxNGHllat2M_1-default UNKNOWN_TYPE | X2n9HcN3dx1-mSbLywp_L-szMydw | Username: | | X1mRVeMqejLnZpd1etxNGHllat2M_1-default
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment