Skip to content

Commit 5cc591e

Browse files
committed
disallow non-UTF-8 symbols in path
1 parent 3194351 commit 5cc591e

15 files changed

Lines changed: 117 additions & 235 deletions

File tree

grpc/src/byte_str.rs

Lines changed: 10 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -21,111 +21,37 @@
2121
* IN THE SOFTWARE.
2222
*
2323
*/
24+
25+
use core::str;
2426
use std::ops::Deref;
2527

2628
use bytes::Bytes;
2729

2830
/// A cheaply cloneable and sliceable chunk of contiguous memory.
29-
///
30-
/// The bytes held by `ByteStr` are arbitrary and may not be valid UTF-8.
3131
#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
3232
pub struct ByteStr {
33+
// Invariant: bytes contains valid UTF-8
3334
bytes: Bytes,
3435
}
3536

36-
impl ByteStr {
37-
/// Strips a prefix, returning a new zero-copy ByteStr.
38-
#[inline]
39-
pub(crate) fn strip_prefix(&self, prefix: &[u8]) -> Option<ByteStr> {
40-
if self.starts_with(prefix) {
41-
Some(ByteStr {
42-
bytes: self.bytes.slice(prefix.len()..),
43-
})
44-
} else {
45-
None
46-
}
47-
}
48-
}
49-
5037
impl Deref for ByteStr {
51-
type Target = [u8];
38+
type Target = str;
5239

5340
#[inline]
54-
fn deref(&self) -> &[u8] {
55-
&self.bytes
41+
fn deref(&self) -> &str {
42+
let b: &[u8] = self.bytes.as_ref();
43+
// The invariant of `bytes` is that it contains valid UTF-8 allows us
44+
// to unwrap.
45+
str::from_utf8(b).unwrap()
5646
}
5747
}
5848

5949
impl From<String> for ByteStr {
6050
#[inline]
6151
fn from(src: String) -> ByteStr {
6252
ByteStr {
53+
// Invariant: src is a String so contains valid UTF-8.
6354
bytes: Bytes::from(src),
6455
}
6556
}
6657
}
67-
68-
impl<'a> TryFrom<&'a ByteStr> for &'a str {
69-
type Error = std::str::Utf8Error;
70-
71-
#[inline]
72-
fn try_from(value: &'a ByteStr) -> Result<Self, Self::Error> {
73-
std::str::from_utf8(value)
74-
}
75-
}
76-
77-
impl TryFrom<ByteStr> for String {
78-
type Error = std::str::Utf8Error;
79-
80-
#[inline]
81-
fn try_from(value: ByteStr) -> Result<Self, Self::Error> {
82-
let s = std::str::from_utf8(&value)?;
83-
Ok(s.to_owned())
84-
}
85-
}
86-
87-
impl PartialEq<str> for ByteStr {
88-
#[inline]
89-
fn eq(&self, other: &str) -> bool {
90-
self.bytes == other.as_bytes()
91-
}
92-
}
93-
94-
impl<'a> PartialEq<&'a str> for ByteStr {
95-
#[inline]
96-
fn eq(&self, other: &&'a str) -> bool {
97-
self.bytes == other.as_bytes()
98-
}
99-
}
100-
101-
impl PartialEq<ByteStr> for str {
102-
#[inline]
103-
fn eq(&self, other: &ByteStr) -> bool {
104-
self.as_bytes() == other.bytes
105-
}
106-
}
107-
108-
impl PartialEq<ByteStr> for &str {
109-
#[inline]
110-
fn eq(&self, other: &ByteStr) -> bool {
111-
self.as_bytes() == other.bytes
112-
}
113-
}
114-
115-
impl FromIterator<u8> for ByteStr {
116-
#[inline]
117-
fn from_iter<T: IntoIterator<Item = u8>>(iter: T) -> Self {
118-
ByteStr {
119-
bytes: Bytes::from_iter(iter),
120-
}
121-
}
122-
}
123-
124-
impl From<&'static str> for ByteStr {
125-
#[inline]
126-
fn from(src: &'static str) -> ByteStr {
127-
ByteStr {
128-
bytes: Bytes::from_static(src.as_bytes()),
129-
}
130-
}
131-
}

grpc/src/client/channel.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ use std::vec;
3636
use serde_json::json;
3737
use tokio::sync::mpsc;
3838
use tokio::sync::watch;
39-
use url::Url; // NOTE: http::Uri requires non-empty authority portion of URI
4039

4140
use crate::StatusCodeError;
4241
use crate::StatusError;
@@ -248,10 +247,9 @@ impl PersistentChannel {
248247
options: ChannelOptions,
249248
credentials: Arc<dyn DynChannelCredentials>,
250249
) -> Self {
251-
// TODO(arjan-bal): Return errors here instead of panicking.
252-
let target = Url::from_str(&target.into()).unwrap();
250+
// TODO(nathanielford): Return errors here instead of panicking.
251+
let target = Target::from_str(&target.into()).unwrap();
253252
let resolver_builder = global_registry().get(target.scheme()).unwrap();
254-
let target = name_resolution::Target::from(target);
255253
let authority = options
256254
.channel_authority
257255
.clone()

grpc/src/client/load_balancing/graceful_switch.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -452,10 +452,12 @@ mod test {
452452
let PickResult::Pick(pick) = pick else {
453453
panic!("unexpected pick result: {:?}", pick);
454454
};
455-
let received_address = &pick.subchannel.address().address;
455+
let received_address = &pick.subchannel.address().address.to_string();
456+
// It's good practice to create the expected value once.
457+
let expected_address = name.to_string();
456458

457459
// Check for inequality and panic with a detailed message if they don't match.
458-
assert_eq!(received_address, name);
460+
assert_eq!(received_address, &expected_address);
459461
}
460462

461463
fn move_subchannel_to_state(
@@ -654,7 +656,7 @@ mod test {
654656
.resolver_update(update.clone(), Some(&parsed_config2), &mut *tcc)
655657
.unwrap();
656658
let subchannel = verify_subchannel_creation_from_policy(&mut rx_events);
657-
assert_eq!(subchannel.address().address, "127.0.0.1:1234");
659+
assert_eq!(&*subchannel.address().address, "127.0.0.1:1234");
658660
assert_channel_empty(&mut rx_events);
659661
}
660662

grpc/src/client/load_balancing/pick_first.rs

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ impl PickFirstPolicy {
425425
// Partition by family (Basic IPv6 detection via colon).
426426
let (ipv6, ipv4): (Vec<Address>, Vec<Address>) = tcp_addresses
427427
.into_iter()
428-
.partition(|addr| addr.address.contains(&b':'));
428+
.partition(|addr| addr.address.contains(':'));
429429

430430
// Interleave the two lists so ipv6 and ipv4 addresses are alternated.
431431
let mut interleaved = Vec::with_capacity(ipv6.len() + ipv4.len() + unknown.len());
@@ -738,9 +738,9 @@ mod test {
738738
use std::time::Duration;
739739

740740
use super::*;
741-
use crate::client::load_balancing::test_utils::TestChannelController;
742-
use crate::client::load_balancing::test_utils::TestEvent;
743-
use crate::client::load_balancing::test_utils::TestWorkScheduler;
741+
use crate::client::load_balancing::test_utils::{
742+
TestChannelController, TestEvent, TestWorkScheduler,
743+
};
744744

745745
const DEFAULT_TEST_DURATION: Duration = Duration::from_secs(10);
746746

@@ -928,7 +928,7 @@ mod test {
928928
let res = state.picker.pick(&RequestHeaders::default());
929929
match res {
930930
PickResult::Pick(pick) => {
931-
assert_eq!(pick.subchannel.address().address, "addr1")
931+
assert_eq!(pick.subchannel.address().address.to_string(), "addr1")
932932
}
933933
other => panic!("unexpected pick result {:?}", other),
934934
}
@@ -942,7 +942,7 @@ mod test {
942942

943943
// Should connect to addr2.
944944
let addr = expect_connect(&rx);
945-
assert_eq!(addr.address, "addr2");
945+
assert_eq!(addr.address.to_string(), "addr2");
946946

947947
// Simulate addr2 succeeding.
948948
let sc2 = policy.subchannels[1].clone();
@@ -986,16 +986,25 @@ mod test {
986986

987987
// Should create new subchannel for addr2 (was cleared by cleanup).
988988
let sc2 = expect_new_subchannel(&rx);
989-
assert_eq!(sc2.address().address, "addr2");
989+
assert_eq!(sc2.address().address.to_string(), "addr2");
990990
// Should create new subchannel for addr3 (was not in previous list).
991991
let sc3 = expect_new_subchannel(&rx);
992-
assert_eq!(sc3.address().address, "addr3");
992+
assert_eq!(sc3.address().address.to_string(), "addr3");
993993

994994
// Should NOT have any more events (no Connect, no UpdatePicker),
995995
// because it stuck to the original selected subchannel.
996996
assert!(rx.try_recv().is_err(), "unexpected event");
997997

998-
assert_eq!(policy.selected.as_ref().unwrap().address().address, "addr1");
998+
assert_eq!(
999+
policy
1000+
.selected
1001+
.as_ref()
1002+
.unwrap()
1003+
.address()
1004+
.address
1005+
.to_string(),
1006+
"addr1"
1007+
);
9991008
}
10001009

10011010
// If all addresses fail during a connection pass, the LB should update to
@@ -1079,7 +1088,7 @@ mod test {
10791088
let mut resulting_addrs = Vec::with_capacity(NUM_ADDRS);
10801089
for _ in 0..NUM_ADDRS {
10811090
let sc = expect_new_subchannel(&rx);
1082-
resulting_addrs.push(sc.address().address);
1091+
resulting_addrs.push(sc.address().address.to_string());
10831092
}
10841093

10851094
// Mock shuffler reverses endpoints: [EP3, EP2, EP1]
@@ -1148,9 +1157,9 @@ mod test {
11481157

11491158
// Should only create subchannels for addr1 and addr2 (2 unique addrs).
11501159
let sc1 = expect_new_subchannel(&rx);
1151-
assert_eq!(sc1.address().address, "addr1");
1160+
assert_eq!(sc1.address().address.to_string(), "addr1");
11521161
let sc2 = expect_new_subchannel(&rx);
1153-
assert_eq!(sc2.address().address, "addr2");
1162+
assert_eq!(sc2.address().address.to_string(), "addr2");
11541163

11551164
// Verify no 3rd subchannel was created.
11561165
while let Ok(event) = rx.try_recv() {
@@ -1217,7 +1226,7 @@ mod test {
12171226

12181227
// Expect Connect event for addr2 due to timer expiration.
12191228
let addr = expect_connect(&rx);
1220-
assert_eq!(addr.address, "addr2");
1229+
assert_eq!(addr.address.to_string(), "addr2");
12211230
}
12221231

12231232
// If all addresses fail during a connection pass, the LB should enter
@@ -1250,7 +1259,7 @@ mod test {
12501259

12511260
// Should automatically call connect() again.
12521261
let addr = expect_connect(&rx);
1253-
assert_eq!(addr.address, "addr1");
1262+
assert_eq!(addr.address.to_string(), "addr1");
12541263
}
12551264

12561265
// If the LB is in steady state, and a new address becomes ready, it should
@@ -1265,7 +1274,7 @@ mod test {
12651274

12661275
// Should failover to addr2: expect Connect(addr2).
12671276
let addr = expect_connect(&rx);
1268-
assert_eq!(addr.address, "addr2");
1277+
assert_eq!(addr.address.to_string(), "addr2");
12691278

12701279
// While addr2 is connecting, simulate addr1 going IDLE (backoff over).
12711280
policy.subchannel_update(
@@ -1300,7 +1309,7 @@ mod test {
13001309
);
13011310
expect_request_resolution(&rx);
13021311
let addr = expect_connect(&rx);
1303-
assert_eq!(addr.address, "addr1");
1312+
assert_eq!(addr.address.to_string(), "addr1");
13041313

13051314
// Confirm LB is in steady state.
13061315
assert!(policy.steady_state.is_some());
@@ -1317,7 +1326,7 @@ mod test {
13171326

13181327
// Now it should automatically call connect() again.
13191328
let addr = expect_connect(&rx);
1320-
assert_eq!(addr.address, "addr1");
1329+
assert_eq!(addr.address.to_string(), "addr1");
13211330

13221331
// Simulate addr1 successfully connecting and becoming READY.
13231332
policy.subchannel_update(
@@ -1335,7 +1344,7 @@ mod test {
13351344
let res = state.picker.pick(&RequestHeaders::default());
13361345
match res {
13371346
PickResult::Pick(pick) => {
1338-
assert_eq!(pick.subchannel.address().address, "addr1");
1347+
assert_eq!(pick.subchannel.address().address.to_string(), "addr1");
13391348
}
13401349
other => panic!("unexpected pick result {:?}", other),
13411350
}
@@ -1354,7 +1363,7 @@ mod test {
13541363

13551364
// Expect Connect(addr2).
13561365
let addr = expect_connect(&rx);
1357-
assert_eq!(addr.address, "addr2");
1366+
assert_eq!(addr.address.to_string(), "addr2");
13581367

13591368
// Simulate addr1 backing off and transitioning to IDLE early
13601369
// (while addr2 is still connecting).
@@ -1393,7 +1402,7 @@ mod test {
13931402
// Expect an immediate Connect(addr1) event triggered by the exhaustion
13941403
// loop sweeping up the early IDLE subchannel.
13951404
let addr = expect_connect(&rx);
1396-
assert_eq!(addr.address, "addr1");
1405+
assert_eq!(addr.address.to_string(), "addr1");
13971406
}
13981407

13991408
// This test is meant to validate that if a new address with different
@@ -1492,7 +1501,7 @@ mod test {
14921501
policy.work(None, controller.as_mut());
14931502

14941503
let addr = expect_connect(&rx);
1495-
assert_eq!(addr.address, "addr2");
1504+
assert_eq!(addr.address.to_string(), "addr2");
14961505

14971506
// 2. Simulate addr2 failing first while addr1 is still in flight.
14981507
let sc2 = policy.subchannels[1].clone();
@@ -1574,7 +1583,7 @@ mod test {
15741583
let res = state.picker.pick(&RequestHeaders::default());
15751584
let sc1 = match res {
15761585
PickResult::Pick(pick) => {
1577-
assert_eq!(pick.subchannel.address().address, "addr1");
1586+
assert_eq!(pick.subchannel.address().address.to_string(), "addr1");
15781587
pick.subchannel
15791588
}
15801589
other => panic!("unexpected pick result {:?}", other),
@@ -1610,7 +1619,7 @@ mod test {
16101619

16111620
// 7. Verify that the policy initiates a reconnection to addr1.
16121621
let addr = expect_connect(&rx);
1613-
assert_eq!(addr.address, "addr1");
1622+
assert_eq!(addr.address.to_string(), "addr1");
16141623

16151624
// And the picker goes to Connecting.
16161625
let state = expect_picker_update(&rx);

grpc/src/client/load_balancing/round_robin.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,11 +1018,11 @@ mod test {
10181018
let all_subchannels = verify_subchannel_creation(&mut rx_events, 2);
10191019
let subchannel_one = all_subchannels
10201020
.iter()
1021-
.find(|sc| sc.address().address == "subchannel_one")
1021+
.find(|sc| sc.address().address == "subchannel_one".to_string().into())
10221022
.unwrap();
10231023
let subchannel_two = all_subchannels
10241024
.iter()
1025-
.find(|sc| sc.address().address == "subchannel_two")
1025+
.find(|sc| sc.address().address == "subchannel_two".to_string().into())
10261026
.unwrap();
10271027

10281028
move_subchannel_to_state(
@@ -1081,11 +1081,11 @@ mod test {
10811081
let new_subchannels = verify_subchannel_creation(&mut rx_events, 2);
10821082
let new_sc = new_subchannels
10831083
.iter()
1084-
.find(|sc| sc.address().address == "new")
1084+
.find(|sc| sc.address().address == "new".to_string().into())
10851085
.unwrap();
10861086
let old_sc = new_subchannels
10871087
.iter()
1088-
.find(|sc| sc.address().address == "subchannel_two")
1088+
.find(|sc| sc.address().address == "subchannel_two".to_string().into())
10891089
.unwrap();
10901090

10911091
move_subchannel_to_state(

grpc/src/client/name_resolution/dns/mod.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -315,11 +315,8 @@ struct ParseResult {
315315
fn parse_endpoint_and_authority(target: &Target) -> Result<ParseResult, String> {
316316
// Parse the endpoint.
317317
let endpoint = target.path();
318-
let endpoint = endpoint.strip_prefix(b"/").unwrap_or(endpoint);
319-
let host_port = (&endpoint)
320-
.try_into()
321-
.map_err(|err| format!("target hostname contains invalid UTF-8 symbols: {err}"))?;
322-
let parse_result = parse_host_port(host_port, DEFAULT_PORT)
318+
let endpoint = endpoint.strip_prefix("/").unwrap_or(endpoint);
319+
let parse_result = parse_host_port(endpoint, DEFAULT_PORT)
323320
.map_err(|err| format!("Failed to parse target {target}: {err}"))?;
324321
let endpoint = parse_result.ok_or("Received empty endpoint host.".to_string())?;
325322

0 commit comments

Comments
 (0)