Commit message: UriQuery.c: Fix out-of-bounds-write in ComposeQuery and ...Ex

Reported by Google Autofuzz team
CVE: CVE-2018-19198
CWE: CWE-787
Vulnerability description: An issue was discovered in uriparser before 0.9.0. UriQuery.c allows an out-of-bounds write via a uriComposeQuery* or uriComposeQueryEx* function because the '&' character is mishandled in certain contexts.

Project: uriparser
File: test.cpp

int main() {
	Suite suite;
	suite.add(auto_ptr<Suite>(new UriSuite()));
	suite.add(auto_ptr<Suite>(new FourSuite()));
	suite.add(auto_ptr<Suite>(new VersionSuite()));
	TextOutput output(TextOutput::Verbose);
	return suite.run(output, false) ? 0 : 1;
}

Project: uriparser

	bool testAddBaseHelper(const wchar_t * base, const wchar_t * rel, const wchar_t * expectedResult, bool backward_compatibility = false) {
		UriParserStateW stateW;

		UriUriW baseUri;
		stateW.uri = &baseUri;
		int res = uriParseUriW(&stateW, base);
		if (res != 0) {
			uriFreeUriMembersW(&baseUri);
			return false;
		}

		UriUriW relUri;
		stateW.uri = &relUri;
		res = uriParseUriW(&stateW, rel);
		if (res != 0) {
			uriFreeUriMembersW(&baseUri);
			uriFreeUriMembersW(&relUri);
			return false;
		}

		UriUriW expectedUri;
		stateW.uri = &expectedUri;
		res = uriParseUriW(&stateW, expectedResult);
		if (res != 0) {
			uriFreeUriMembersW(&baseUri);
			uriFreeUriMembersW(&relUri);
			uriFreeUriMembersW(&expectedUri);
			return false;
		}

		UriUriW transformedUri;
		if (backward_compatibility) {
			res = uriAddBaseUriExW(&transformedUri, &relUri, &baseUri, URI_RESOLVE_IDENTICAL_SCHEME_COMPAT);
		} else {
			res = uriAddBaseUriW(&transformedUri, &relUri, &baseUri);
		}

		if (res != 0) {
			uriFreeUriMembersW(&baseUri);
			uriFreeUriMembersW(&relUri);
			uriFreeUriMembersW(&expectedUri);
			uriFreeUriMembersW(&transformedUri);
			return false;
		}

		const bool equal = (URI_TRUE == uriEqualsUriW(&transformedUri, &expectedUri));
		if (!equal) {
			wchar_t transformedUriText[1024 * 8];
			wchar_t expectedUriText[1024 * 8];
			uriToStringW(transformedUriText, &transformedUri, 1024 * 8, NULL);
			uriToStringW(expectedUriText, &expectedUri, 1024 * 8, NULL);
#ifdef HAVE_WPRINTF
			wprintf(L"\n\n\nExpected: \"%s\"\nReceived: \"%s\"\n\n\n", expectedUriText, transformedUriText);
#endif
		}

		uriFreeUriMembersW(&baseUri);
		uriFreeUriMembersW(&relUri);
		uriFreeUriMembersW(&expectedUri);
		uriFreeUriMembersW(&transformedUri);
		return equal;
	}

Project: uriparser

	void testCrash_Report2418192() {
		helperTestQueryString("http://svcs.cnn.com/weather/wrapper.jsp?&csiID=csi1", 1);
	}

Project: uriparser

	bool testNormalizeMaskHelper(const wchar_t * uriText, unsigned int expectedMask) {
		UriParserStateW state;
		UriUriW uri;
		state.uri = &uri;
		int res = uriParseUriW(&state, uriText);
		if (res != 0) {
			uriFreeUriMembersW(&uri);
			return false;
		}

		const unsigned int maskBefore = uriNormalizeSyntaxMaskRequiredW(&uri);
		if (maskBefore != expectedMask) {
			uriFreeUriMembersW(&uri);
			return false;
		}

		res = uriNormalizeSyntaxW(&uri);
		if (res != 0) {
			uriFreeUriMembersW(&uri);
			return false;
		}

		const unsigned int maskAfter = uriNormalizeSyntaxMaskRequiredW(&uri);
		uriFreeUriMembersW(&uri);

		uriFreeUriMembersW(&uri);

		return (maskAfter == URI_NORMALIZED);
	}

Project: uriparser

	void testNormalizeSyntax() {
		TEST_ASSERT(testNormalizeSyntaxHelper(
				L"eXAMPLE://a/./b/../b/%63/%7bfoo%7d",
				L"example://a/b/c/%7Bfoo%7D"));

		TEST_ASSERT(testNormalizeSyntaxHelper(
				L"http://examp%4Ce.com/",
				L"http://example.com/"));

		TEST_ASSERT(testNormalizeSyntaxHelper(
				L"http://example.com/a/b/%2E%2E/",
				L"http://example.com/a/"));

		TEST_ASSERT(testNormalizeSyntaxHelper(
				L"http://user:pass@SOMEHOST.COM:123",
				L"http://user:pass@somehost.com:123"));

		TEST_ASSERT(testNormalizeSyntaxHelper(
				L"HTTP://a:b@HOST:123/./1/2/../%41?abc#def",
				L"http://a:b@host:123/1/A?abc#def"));

		TEST_ASSERT(testNormalizeSyntaxHelper(
				L"../../abc",
				L"../../abc"));

		TEST_ASSERT(testNormalizeSyntaxHelper(
				L"../../abc/..",
				L"../../"));

		TEST_ASSERT(testNormalizeSyntaxHelper(
				L"../../abc/../def",
				L"../../def"));

		TEST_ASSERT(testNormalizeSyntaxHelper(
				L"abc/..",
				L""));

		TEST_ASSERT(testNormalizeSyntaxHelper(
				L"abc/../",
				L""));

		TEST_ASSERT(testNormalizeSyntaxHelper(
				L"../../abc/./def",
				L"../../abc/def"));

		TEST_ASSERT(testNormalizeSyntaxHelper(
				L"./def",
				L"def"));

		TEST_ASSERT(testNormalizeSyntaxHelper(
				L"def/.",
				L"def/"));

		TEST_ASSERT(testNormalizeSyntaxHelper(
				L"./abc:def",
				L"./abc:def"));
	}

Project: uriparser
File: test.cpp

	void testQueryListPair() {
		testQueryListPairHelper("one+two+%26+three=%2B", "one two & three", "+");
		testQueryListPairHelper("one=two=three", "one", "two=three", "one=two%3Dthree");
		testQueryListPairHelper("one=two=three=four", "one", "two=three=four", "one=two%3Dthree%3Dfour");
	}

Project: uriparser

	void testRangeComparison() {
		testCompareRangeHelper("", "", 0);
		testCompareRangeHelper("a", "", 1);
		testCompareRangeHelper("", "a", -1);

		testCompareRangeHelper("a", "a", 0);
		testCompareRangeHelper("a", "b", -1);
		testCompareRangeHelper("b", "a", 1);

		testCompareRangeHelper("a", "aa", -1);
		testCompareRangeHelper("aa", "a", 1);

		testCompareRangeHelper(NULL, "a", -1);
		testCompareRangeHelper("a", NULL, 1);
		testCompareRangeHelper(NULL, NULL, 0);

		const bool KEEP_NULL_RANGE = false;
		const bool AVOID_NULL_RANGE = true;
		testCompareRangeHelper(NULL, "", -1, AVOID_NULL_RANGE);
		testCompareRangeHelper(NULL, "", -1, KEEP_NULL_RANGE);
		testCompareRangeHelper("", NULL, 1, AVOID_NULL_RANGE);
		testCompareRangeHelper("", NULL, 1, KEEP_NULL_RANGE);
	}

Project: uriparser

	void testRangeComparison_RemoveBaseUri_Issue19() {
		testRemoveBaseUriHelper("scheme://host/source",
								"scheme://host/source",
								"schemelonger://host/base");
		testRemoveBaseUriHelper("schemelonger://host/source",
								"schemelonger://host/source",
								"scheme://host/base");

		testRemoveBaseUriHelper("//host/source",
								"http://host/source",
								"http://hostlonger/base");
		testRemoveBaseUriHelper("//hostlonger/source",
								"http://hostlonger/source",
								"http://host/base");

		testRemoveBaseUriHelper("//[v7.host]/source",
								"http://[v7.host]/source",
								"http://[v7.hostlonger]/base");
		testRemoveBaseUriHelper("//[v7.hostlonger]/source",
								"http://[v7.hostlonger]/source",
								"http://host/base");

		testRemoveBaseUriHelper("path1",
								"http://host/path1",
								"http://host/path111");
		testRemoveBaseUriHelper("../path1/path2",
								"http://host/path1/path2",
								"http://host/path111/path222");
		testRemoveBaseUriHelper("path111",
								"http://host/path111",
								"http://host/path1");
		testRemoveBaseUriHelper("../path111/path222",
								"http://host/path111/path222",
								"http://host/path1/path2");

		testRemoveBaseUriHelper("//example/x/abc",
								"http://example/x/abc",
								"http://example2/x/y/z");
	}

Project: uriparser

	void testUriHostEmpty() {
		UriParserStateA stateA;
		UriUriA uriA;
		stateA.uri = &uriA;
		const char * const input = "http" "://" ":" "123";
		const int res = uriParseUriA(&stateA, input);
		TEST_ASSERT(URI_SUCCESS == res);
		TEST_ASSERT(uriA.userInfo.first == NULL);
		TEST_ASSERT(uriA.userInfo.afterLast == NULL);
		TEST_ASSERT(uriA.hostText.first != NULL);
		TEST_ASSERT(uriA.hostText.afterLast != NULL);
		TEST_ASSERT(uriA.hostText.afterLast - uriA.hostText.first == 0);
		TEST_ASSERT(uriA.portText.first == input + 4 + 3 + 1);
		TEST_ASSERT(uriA.portText.afterLast == input + 4 + 3 + 1 + 3);
		uriFreeUriMembersA(&uriA);
	}

Project: uriparser

	void testUriUserInfoHostPort23_Bug3510198_related_1() {
		UriParserStateA stateA;
		UriUriA uriA;
		stateA.uri = &uriA;

		int res;
		res = uriParseUriA(&stateA, "http" "://" "@" "host" "/");
		TEST_ASSERT(URI_SUCCESS == res);
		TEST_ASSERT(uriA.userInfo.afterLast != NULL);
		TEST_ASSERT(uriA.userInfo.first != NULL);
		TEST_ASSERT(uriA.userInfo.afterLast - uriA.userInfo.first == 0);
		TEST_ASSERT(!memcmp(uriA.hostText.first, "host", 4 * sizeof(char)));
		TEST_ASSERT(uriA.hostText.afterLast - uriA.hostText.first == 4);
		TEST_ASSERT(uriA.portText.first == NULL);
		TEST_ASSERT(uriA.portText.afterLast == NULL);
		uriFreeUriMembersA(&uriA);
	}