|
1 | 1 | use std::borrow::Cow; |
2 | 2 | use std::collections::hash_map::DefaultHasher; |
3 | 3 | use std::fmt; |
4 | | -use std::fmt::Formatter; |
| 4 | +use std::fmt::{Formatter, Write}; |
5 | 5 | use std::hash::{Hash, Hasher}; |
6 | 6 | use std::sync::OnceLock; |
7 | 7 |
|
8 | 8 | use idna::punycode::decode_to_string; |
9 | 9 | use jiter::{PartialMode, StringCacheMode}; |
10 | | -use percent_encoding::{percent_encode, NON_ALPHANUMERIC}; |
| 10 | +use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; |
11 | 11 | use pyo3::exceptions::PyValueError; |
12 | 12 | use pyo3::pyclass::CompareOp; |
13 | 13 | use pyo3::sync::OnceLockExt; |
@@ -360,53 +360,49 @@ impl PyMultiHostUrl { |
360 | 360 | let host_offset = scheme.len() + 3; |
361 | 361 |
|
362 | 362 | let mut full_url = self.ref_url.unicode_string(py).into_owned(); |
363 | | - full_url.insert(host_offset, ','); |
| 363 | + let mut extra_hosts = String::new(); |
364 | 364 |
|
365 | 365 | // special urls will have had a trailing slash added, non-special urls will not |
366 | 366 | // hence we need to remove the last char if the scheme is special |
367 | 367 | #[allow(clippy::bool_to_int_with_if)] |
368 | 368 | let sub = if scheme_is_special(scheme) { 1 } else { 0 }; |
369 | 369 |
|
370 | | - let hosts = extra_urls |
371 | | - .iter() |
372 | | - .map(|url| { |
373 | | - let str = unicode_url(url.as_str(), url); |
374 | | - str[host_offset..str.len() - sub].to_string() |
375 | | - }) |
376 | | - .collect::<Vec<String>>() |
377 | | - .join(","); |
378 | | - full_url.insert_str(host_offset, &hosts); |
| 370 | + for url in extra_urls { |
| 371 | + let str = unicode_url(url.as_str(), url); |
| 372 | + extra_hosts.push_str(&str[host_offset..str.len() - sub]); |
| 373 | + extra_hosts.push(','); |
| 374 | + } |
| 375 | + |
| 376 | + full_url.insert_str(host_offset, &extra_hosts); |
379 | 377 | Cow::Owned(full_url) |
380 | 378 | } else { |
381 | 379 | self.ref_url.unicode_string(py) |
382 | 380 | } |
383 | 381 | } |
384 | 382 |
|
385 | | - pub fn __str__(&self, py: Python<'_>) -> String { |
| 383 | + pub fn __str__(&self, py: Python<'_>) -> Cow<'_, str> { |
386 | 384 | if let Some(extra_urls) = &self.extra_urls { |
387 | 385 | let scheme = self.ref_url.lib_url.scheme(); |
388 | 386 | let host_offset = scheme.len() + 3; |
389 | 387 |
|
390 | 388 | let mut full_url = self.ref_url.serialized(py).to_string(); |
391 | | - full_url.insert(host_offset, ','); |
| 389 | + let mut extra_hosts = String::new(); |
392 | 390 |
|
393 | 391 | // special urls will have had a trailing slash added, non-special urls will not |
394 | 392 | // hence we need to remove the last char if the scheme is special |
395 | 393 | #[allow(clippy::bool_to_int_with_if)] |
396 | 394 | let sub = if scheme_is_special(scheme) { 1 } else { 0 }; |
397 | 395 |
|
398 | | - let hosts = extra_urls |
399 | | - .iter() |
400 | | - .map(|url| { |
401 | | - let str = url.as_str(); |
402 | | - &str[host_offset..str.len() - sub] |
403 | | - }) |
404 | | - .collect::<Vec<&str>>() |
405 | | - .join(","); |
406 | | - full_url.insert_str(host_offset, &hosts); |
407 | | - full_url |
| 396 | + for url in extra_urls { |
| 397 | + let str = url.as_str(); |
| 398 | + extra_hosts.push_str(&str[host_offset..str.len() - sub]); |
| 399 | + extra_hosts.push(','); |
| 400 | + } |
| 401 | + |
| 402 | + full_url.insert_str(host_offset, &extra_hosts); |
| 403 | + Cow::Owned(full_url) |
408 | 404 | } else { |
409 | | - self.ref_url.__str__(py).to_string() |
| 405 | + Cow::Borrowed(self.ref_url.__str__(py)) |
410 | 406 | } |
411 | 407 | } |
412 | 408 |
|
@@ -439,7 +435,7 @@ impl PyMultiHostUrl { |
439 | 435 | self.clone().into_py_any(py) |
440 | 436 | } |
441 | 437 |
|
442 | | - fn __getnewargs__(&self, py: Python<'_>) -> (String,) { |
| 438 | + fn __getnewargs__(&self, py: Python<'_>) -> (Cow<'_, str>,) { |
443 | 439 | (self.__str__(py),) |
444 | 440 | } |
445 | 441 |
|
@@ -474,7 +470,7 @@ impl PyMultiHostUrl { |
474 | 470 | "expected one of 'host', 'username', 'password' or 'port' to be set", |
475 | 471 | )); |
476 | 472 | } |
477 | | - multi_url.push_str(&single_host.to_string()); |
| 473 | + write!(multi_url, "{single_host}").expect("string formatting never fails"); |
478 | 474 | if index != hosts.len() - 1 { |
479 | 475 | multi_url.push(','); |
480 | 476 | } |
@@ -602,13 +598,8 @@ fn is_punnycode_domain(lib_url: &Url, domain: &str) -> bool { |
602 | 598 | scheme_is_special(lib_url.scheme()) && domain.split('.').any(|part| part.starts_with(PUNYCODE_PREFIX)) |
603 | 599 | } |
604 | 600 |
|
605 | | -fn encode_userinfo_component(value: &str) -> Cow<'_, str> { |
606 | | - let encoded = percent_encode(value.as_bytes(), NON_ALPHANUMERIC).to_string(); |
607 | | - if encoded == value { |
608 | | - Cow::Borrowed(value) |
609 | | - } else { |
610 | | - Cow::Owned(encoded) |
611 | | - } |
| 601 | +fn encode_userinfo_component(value: &str) -> impl fmt::Display + use<'_> { |
| 602 | + utf8_percent_encode(value, NON_ALPHANUMERIC) |
612 | 603 | } |
613 | 604 | // based on https://github.com/servo/rust-url/blob/1c1e406874b3d2aa6f36c5d2f3a5c2ea74af9efb/url/src/parser.rs#L161-L167 |
614 | 605 | pub fn scheme_is_special(scheme: &str) -> bool { |
|
0 commit comments