Skip to content

Commit bda6377

Browse files
committed
CDRIVER-3390 support multi-byte UTF-8 percent escapes
1 parent 6377276 commit bda6377

File tree

3 files changed

+145
-40
lines changed

3 files changed

+145
-40
lines changed

src/libmongoc/src/mongoc/mongoc-uri.c

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2169,6 +2169,7 @@ mongoc_uri_unescape (const char *escaped_string)
21692169
const char *ptr;
21702170
const char *end;
21712171
size_t len;
2172+
bool unescape_occurred = false;
21722173

21732174
BSON_ASSERT (escaped_string);
21742175

@@ -2192,24 +2193,32 @@ mongoc_uri_unescape (const char *escaped_string)
21922193
case '%':
21932194
if (((end - ptr) < 2) || !isxdigit (ptr[1]) || !isxdigit (ptr[2]) ||
21942195
#ifdef _MSC_VER
2195-
(1 != sscanf_s (&ptr[1], "%02x", &hex)) ||
2196+
(1 != sscanf_s (&ptr[1], "%02x", &hex))
21962197
#else
2197-
(1 != sscanf (&ptr[1], "%02x", &hex)) ||
2198+
(1 != sscanf (&ptr[1], "%02x", &hex))
21982199
#endif
2199-
!isprint (hex)) {
2200+
|| 0 == hex) {
22002201
bson_string_free (str, true);
22012202
MONGOC_WARNING ("Invalid %% escape sequence");
22022203
return NULL;
22032204
}
22042205
bson_string_append_c (str, hex);
22052206
ptr += 2;
2207+
unescape_occurred = true;
22062208
break;
22072209
default:
22082210
bson_string_append_unichar (str, c);
22092211
break;
22102212
}
22112213
}
22122214

2215+
/* Check that after unescaping, it is still valid UTF-8 */
2216+
if (unescape_occurred && !bson_utf8_validate (str->str, str->len, false)) {
2217+
MONGOC_WARNING ("Invalid %% escape sequence: unescaped string contains invalid UTF-8");
2218+
bson_string_free (str, true);
2219+
return NULL;
2220+
}
2221+
22132222
return bson_string_free (str, false);
22142223
}
22152224

src/libmongoc/tests/json/connection_uri/additional-nonspec-tests.json

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,24 @@
134134
"CANONICALIZE_HOST_NAME": true
135135
}
136136
}
137-
}
137+
},
138+
{
139+
"description": "Username containing percent encoded multi-byte UTF-8 is valid",
140+
"uri": "mongodb://%E2%98%83",
141+
"valid": true,
142+
"hosts": [
143+
{
144+
"type": "hostname",
145+
"host": "",
146+
"port": null
147+
}
148+
]
149+
},
150+
{
151+
"description": "Username containing percent encoded multi-byte UTF-8 is valid",
152+
"uri": "mongodb://%E2%D8%83",
153+
"valid": false,
154+
"warning": true
155+
}
138156
]
139157
}

src/libmongoc/tests/test-mongoc-scram.c

Lines changed: 114 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ test_iteration_count (int count, bool should_succeed)
6161
/* set up the scram state to immediately test step 2. */
6262
_mongoc_scram_init (&scram, MONGOC_CRYPTO_ALGORITHM_SHA_1);
6363
_mongoc_scram_set_pass (&scram, "password");
64-
bson_strncpy (scram.encoded_nonce, client_nonce, sizeof (scram.encoded_nonce));
64+
bson_strncpy (
65+
scram.encoded_nonce, client_nonce, sizeof (scram.encoded_nonce));
6566
scram.encoded_nonce_len = (int32_t) strlen (client_nonce);
6667
scram.auth_message = bson_malloc0 (4096);
6768
scram.auth_messagemax = 4096;
@@ -291,53 +292,41 @@ _check_error (const bson_error_t *error, test_error_t expected_error)
291292
ASSERT_ERROR_CONTAINS ((*error), domain, code, message);
292293
}
293294

294-
/* if auth is expected to succeed, expected_error is zero'd out. */
295295
static void
296-
_try_auth (bool pooled,
297-
const char *user,
298-
const char *pwd,
299-
const char *mechanism,
300-
test_error_t expected_error)
296+
_try_auth_from_uri (bool pooled, mongoc_uri_t *uri, test_error_t expected_error)
301297
{
302-
mongoc_uri_t *uri;
303298
mongoc_client_pool_t *client_pool = NULL;
304299
mongoc_client_t *client = NULL;
300+
mongoc_collection_t *coll;
305301
bson_error_t error;
306302
bson_t reply;
307303
bool res;
308304

309-
uri = test_framework_get_uri ();
310-
mongoc_uri_set_username (uri, user);
311-
mongoc_uri_set_password (uri, pwd);
312-
if (mechanism) {
313-
mongoc_uri_set_auth_mechanism (uri, mechanism);
314-
}
315305
if (pooled) {
316306
client_pool = mongoc_client_pool_new (uri);
307+
test_framework_set_pool_ssl_opts (client_pool);
317308
mongoc_client_pool_set_error_api (client_pool, 2);
318309
client = mongoc_client_pool_pop (client_pool);
319310
/* suppress the auth failure logs from pooled clients. */
320311
capture_logs (true);
321312
} else {
322313
client = mongoc_client_new_from_uri (uri);
323314
mongoc_client_set_error_api (client, 2);
315+
test_framework_set_ssl_opts (client);
324316
}
325-
res = mongoc_client_command_simple (client,
326-
"admin",
327-
tmp_bson ("{'dbstats': 1}"),
328-
NULL /* read_prefs. */,
329-
&reply,
330-
&error);
317+
coll = get_test_collection (client, "try_auth");
318+
res = mongoc_collection_insert_one (
319+
coll, tmp_bson ("{'x': 1}"), NULL /* opts */, &reply, &error);
331320

332321
if (expected_error == MONGOC_TEST_NO_ERROR) {
333-
ASSERT (res);
334-
ASSERT_MATCH (&reply, "{'db': 'admin', 'ok': 1}");
322+
ASSERT_OR_PRINT (res, error);
323+
ASSERT_MATCH (&reply, "{'insertedCount': 1 }");
335324
} else {
336325
ASSERT (!res);
337326
_check_error (&error, expected_error);
338327
}
339328
bson_destroy (&reply);
340-
mongoc_uri_destroy (uri);
329+
mongoc_collection_destroy (coll);
341330
if (pooled) {
342331
mongoc_client_pool_push (client_pool, client);
343332
mongoc_client_pool_destroy (client_pool);
@@ -347,6 +336,26 @@ _try_auth (bool pooled,
347336
}
348337
}
349338

339+
/* if auth is expected to succeed, expected_error is zero'd out. */
340+
static void
341+
_try_auth (bool pooled,
342+
const char *user,
343+
const char *pwd,
344+
const char *mechanism,
345+
test_error_t expected_error)
346+
{
347+
mongoc_uri_t *uri;
348+
349+
uri = test_framework_get_uri ();
350+
mongoc_uri_set_username (uri, user);
351+
mongoc_uri_set_password (uri, pwd);
352+
if (mechanism) {
353+
mongoc_uri_set_auth_mechanism (uri, mechanism);
354+
}
355+
_try_auth_from_uri (pooled, uri, expected_error);
356+
mongoc_uri_destroy (uri);
357+
}
358+
350359

351360
static void
352361
_test_mongoc_scram_auth (bool pooled)
@@ -395,8 +404,7 @@ _test_mongoc_scram_auth (bool pooled)
395404
/* Auth spec: "For a non-existent username, verify that not specifying a
396405
* mechanism when connecting fails with the same error type that would occur
397406
* with a correct username but incorrect password or mechanism." */
398-
_try_auth (
399-
pooled, "unknown_user", "bad", NULL, MONGOC_TEST_USER_NOT_FOUND_ERROR);
407+
_try_auth (pooled, "unknown_user", "bad", NULL, MONGOC_TEST_AUTH_ERROR);
400408
}
401409

402410
/* test the auth tests described in the auth spec. */
@@ -414,20 +422,31 @@ test_mongoc_scram_auth (void *ctx)
414422
static int
415423
_skip_if_no_sha256 ()
416424
{
417-
mongoc_uri_t *uri;
418425
mongoc_client_t *client;
419426
bool res;
427+
bson_error_t error;
428+
429+
client = test_framework_client_new ();
430+
431+
/* Check if SCRAM-SHA-256 is a supported auth mechanism by attempting to
432+
* create a new user with it. */
433+
res = mongoc_client_command_simple (
434+
client,
435+
"admin",
436+
tmp_bson ("{'createUser': 'temp', 'pwd': 'sha256', 'roles': ['root'], "
437+
"'mechanisms': ['SCRAM-SHA-256']}"),
438+
NULL /* read_prefs */,
439+
NULL /* reply */,
440+
&error);
441+
442+
if (res) {
443+
mongoc_database_t *db;
444+
445+
db = mongoc_client_get_database (client, "admin");
446+
ASSERT_OR_PRINT (mongoc_database_remove_user (db, "temp", &error), error);
447+
mongoc_database_destroy (db);
448+
}
420449

421-
uri = test_framework_get_uri ();
422-
mongoc_uri_set_auth_mechanism (uri, "SCRAM-SHA-256");
423-
client = mongoc_client_new_from_uri (uri);
424-
res = mongoc_client_command_simple (client,
425-
"admin",
426-
tmp_bson ("{'dbstats': 1}"),
427-
NULL /* read_prefs */,
428-
NULL /* reply */,
429-
NULL /* error */);
430-
mongoc_uri_destroy (uri);
431450
mongoc_client_destroy (client);
432451
return res ? 1 : 0;
433452
}
@@ -497,9 +516,68 @@ _drop_saslprep_users ()
497516
mongoc_client_destroy (client);
498517
}
499518

519+
static void
520+
_make_uri (const char *username, const char *password, mongoc_uri_t **out)
521+
{
522+
char *uri_str;
523+
char *tmp;
524+
525+
tmp = test_framework_get_uri_str_no_auth ("admin");
526+
uri_str = test_framework_add_user_password (tmp, username, password);
527+
mongoc_uri_destroy (*out);
528+
*out = mongoc_uri_new (uri_str);
529+
bson_free (tmp);
530+
bson_free (uri_str);
531+
}
532+
500533
static void
501534
_test_mongoc_scram_saslprep_auth (bool pooled)
502535
{
536+
mongoc_uri_t *uri = NULL;
537+
538+
/* Test URIs of the form in the auth spec test plan for SASLPrep.
539+
- mongodb://IX:[email protected]/admin
540+
- mongodb://IX:I%C2%[email protected]/admin
541+
- mongodb://%E2%85%A8:[email protected]/admin
542+
- mongodb://%E2%85%A8:I%C2%[email protected]/admin
543+
544+
Test in three ways.
545+
1. By embedding the multi-byte UTF-8 characters directly into the
546+
connection string.
547+
2. By percent escaping the multi-byte UTF-8 characters.
548+
3. By using the setters, mongoc_uri_set_username/mongoc_uri_set_password
549+
and embedding the UTF-8 characters (percent unescaping does not occur for
550+
the setters)
551+
*/
552+
553+
/* Way 1: embedding multi-byte UTF-8 characters directly */
554+
_make_uri ("IX", "IX", &uri);
555+
_try_auth_from_uri (pooled, uri, MONGOC_TEST_NO_ERROR);
556+
557+
_make_uri ("IX", ROMAN_NUMERAL_NINE, &uri);
558+
_try_auth_from_uri (pooled, uri, MONGOC_TEST_NO_ERROR);
559+
560+
_make_uri (ROMAN_NUMERAL_NINE, "IV", &uri);
561+
_try_auth_from_uri (pooled, uri, MONGOC_TEST_NO_ERROR);
562+
563+
_make_uri (ROMAN_NUMERAL_NINE, ROMAN_NUMERAL_FOUR, &uri);
564+
_try_auth_from_uri (pooled, uri, MONGOC_TEST_NO_ERROR);
565+
566+
/* Way 2: Percent escaping */
567+
_make_uri ("IX", "IX", &uri);
568+
_try_auth_from_uri (pooled, uri, MONGOC_TEST_NO_ERROR);
569+
570+
_make_uri ("IX", "I%C2%ADX", &uri);
571+
_try_auth_from_uri (pooled, uri, MONGOC_TEST_NO_ERROR);
572+
573+
_make_uri ("%E2%85%A8", "IV", &uri);
574+
_try_auth_from_uri (pooled, uri, MONGOC_TEST_NO_ERROR);
575+
576+
_make_uri ("%E2%85%A8", "I%C2%ADV", &uri);
577+
_try_auth_from_uri (pooled, uri, MONGOC_TEST_NO_ERROR);
578+
mongoc_uri_destroy (uri);
579+
580+
/* Way 3: with username/password setters. */
503581
_try_auth (pooled, "IX", "IX", NULL, MONGOC_TEST_NO_ERROR);
504582
_try_auth (pooled, "IX", ROMAN_NUMERAL_NINE, NULL, MONGOC_TEST_NO_ERROR);
505583
_try_auth (pooled, ROMAN_NUMERAL_NINE, "IV", NULL, MONGOC_TEST_NO_ERROR);

0 commit comments

Comments
 (0)