Skip to content

Commit 6b57dfd

Browse files
committed
refactor(http): [#160] extract functions for percent decoding
1 parent 849633d commit 6b57dfd

File tree

10 files changed

+223
-95
lines changed

10 files changed

+223
-95
lines changed

src/http/filters.rs

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::sync::Arc;
66
use warp::{reject, Filter, Rejection};
77

88
use super::error::Error;
9+
use super::percent_encoding::{percent_decode_info_hash, percent_decode_peer_id};
910
use super::{request, WebResult};
1011
use crate::protocol::common::MAX_SCRAPE_TORRENTS;
1112
use crate::protocol::info_hash::InfoHash;
@@ -77,9 +78,11 @@ fn info_hashes(raw_query: &String) -> WebResult<Vec<InfoHash>> {
7778

7879
for v in split_raw_query {
7980
if v.contains("info_hash") {
81+
// get raw percent encoded infohash
8082
let raw_info_hash = v.split('=').collect::<Vec<&str>>()[1];
81-
let info_hash_bytes = percent_encoding::percent_decode_str(raw_info_hash).collect::<Vec<u8>>();
82-
let info_hash = InfoHash::from_str(&hex::encode(info_hash_bytes));
83+
84+
let info_hash = percent_decode_info_hash(raw_info_hash);
85+
8386
if let Ok(ih) = info_hash {
8487
info_hashes.push(ih);
8588
}
@@ -106,22 +109,15 @@ fn peer_id(raw_query: &String) -> WebResult<peer::Id> {
106109
for v in split_raw_query {
107110
// look for the peer_id param
108111
if v.contains("peer_id") {
109-
// get raw percent_encoded peer_id
112+
// get raw percent encoded peer id
110113
let raw_peer_id = v.split('=').collect::<Vec<&str>>()[1];
111114

112-
// decode peer_id
113-
let peer_id_bytes = percent_encoding::percent_decode_str(raw_peer_id).collect::<Vec<u8>>();
114-
115-
// peer_id must be 20 bytes
116-
if peer_id_bytes.len() != 20 {
115+
if let Ok(id) = percent_decode_peer_id(raw_peer_id) {
116+
peer_id = Some(id);
117+
} else {
117118
return Err(reject::custom(Error::InvalidPeerId));
118119
}
119120

120-
// clone peer_id_bytes into fixed length array
121-
let mut byte_arr: [u8; 20] = Default::default();
122-
byte_arr.clone_from_slice(peer_id_bytes.as_slice());
123-
124-
peer_id = Some(peer::Id(byte_arr));
125121
break;
126122
}
127123
}

src/http/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub mod request;
1919
pub mod response;
2020
pub mod routes;
2121
pub mod server;
22+
pub mod percent_encoding;
2223

2324
pub type Bytes = u64;
2425
pub type WebResult<T> = std::result::Result<T, warp::Rejection>;

src/http/percent_encoding.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use std::str::FromStr;
2+
3+
use crate::protocol::info_hash::InfoHash;
4+
use crate::tracker::peer::{self, IdConversionError};
5+
6+
/// # Errors
7+
///
8+
/// Will return `Err` if if the decoded bytes do not represent a valid `InfoHash`.
9+
pub fn percent_decode_info_hash(raw_info_hash: &str) -> Result<InfoHash, binascii::ConvertError> {
10+
let bytes = percent_encoding::percent_decode_str(raw_info_hash).collect::<Vec<u8>>();
11+
InfoHash::from_str(&hex::encode(bytes))
12+
}
13+
14+
/// # Errors
15+
///
16+
/// Will return `Err` if if the decoded bytes do not represent a valid `peer::Id`.
17+
pub fn percent_decode_peer_id(raw_peer_id: &str) -> Result<peer::Id, IdConversionError> {
18+
let bytes = percent_encoding::percent_decode_str(raw_peer_id).collect::<Vec<u8>>();
19+
peer::Id::try_from(bytes)
20+
}
21+
22+
#[cfg(test)]
23+
mod tests {
24+
use std::str::FromStr;
25+
26+
use crate::http::percent_encoding::{percent_decode_info_hash, percent_decode_peer_id};
27+
use crate::protocol::info_hash::InfoHash;
28+
use crate::tracker::peer;
29+
30+
#[test]
31+
fn it_should_decode_a_percent_encoded_info_hash() {
32+
let encoded_infohash = "%3B%24U%04%CF%5F%11%BB%DB%E1%20%1C%EAjk%F4Z%EE%1B%C0";
33+
34+
let info_hash = percent_decode_info_hash(encoded_infohash).unwrap();
35+
36+
assert_eq!(
37+
info_hash,
38+
InfoHash::from_str("3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0").unwrap()
39+
);
40+
}
41+
42+
#[test]
43+
fn it_should_fail_decoding_an_invalid_percent_encoded_info_hash() {
44+
let invalid_encoded_infohash = "invalid percent-encoded infohash";
45+
46+
let info_hash = percent_decode_info_hash(invalid_encoded_infohash);
47+
48+
assert!(info_hash.is_err());
49+
}
50+
51+
#[test]
52+
fn it_should_decode_a_percent_encoded_peer_id() {
53+
let encoded_peer_id = "%2DqB00000000000000000";
54+
55+
let peer_id = percent_decode_peer_id(encoded_peer_id).unwrap();
56+
57+
assert_eq!(peer_id, peer::Id(*b"-qB00000000000000000"));
58+
}
59+
60+
#[test]
61+
fn it_should_fail_decoding_an_invalid_percent_encoded_peer_id() {
62+
let invalid_encoded_peer_id = "invalid percent-encoded peer id";
63+
64+
let peer_id = percent_decode_peer_id(invalid_encoded_peer_id);
65+
66+
assert!(peer_id.is_err());
67+
}
68+
}

src/tracker/peer.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,45 @@ impl Peer {
9191
#[derive(PartialEq, Eq, Hash, Clone, Debug, PartialOrd, Ord, Copy)]
9292
pub struct Id(pub [u8; 20]);
9393

94+
#[derive(Debug)]
95+
pub enum IdConversionError {
96+
NotEnoughBytes,
97+
TooManyBytes,
98+
}
99+
100+
impl Id {
101+
/// # Panics
102+
///
103+
/// Will panic if byte slice does not contains the exact amount of bytes need for the `Id`.
104+
#[must_use]
105+
pub fn from_bytes(bytes: &[u8]) -> Self {
106+
assert_eq!(bytes.len(), 20);
107+
let mut ret = Id([0u8; 20]);
108+
ret.0.clone_from_slice(bytes);
109+
ret
110+
}
111+
}
112+
113+
impl From<[u8; 20]> for Id {
114+
fn from(bytes: [u8; 20]) -> Self {
115+
Id(bytes)
116+
}
117+
}
118+
119+
impl TryFrom<Vec<u8>> for Id {
120+
type Error = IdConversionError;
121+
122+
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
123+
if bytes.len() < 20 {
124+
return Err(IdConversionError::NotEnoughBytes);
125+
}
126+
if bytes.len() > 20 {
127+
return Err(IdConversionError::TooManyBytes);
128+
}
129+
Ok(Self::from_bytes(&bytes))
130+
}
131+
}
132+
94133
impl std::fmt::Display for Id {
95134
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96135
match self.to_hex_string() {
@@ -239,6 +278,75 @@ mod test {
239278
mod torrent_peer_id {
240279
use crate::tracker::peer;
241280

281+
#[test]
282+
fn should_be_instantiated_from_a_byte_slice() {
283+
let id = peer::Id::from_bytes(&[
284+
0, 159, 146, 150, 0, 159, 146, 150, 0, 159, 146, 150, 0, 159, 146, 150, 0, 159, 146, 150,
285+
]);
286+
287+
let expected_id = peer::Id([
288+
0, 159, 146, 150, 0, 159, 146, 150, 0, 159, 146, 150, 0, 159, 146, 150, 0, 159, 146, 150,
289+
]);
290+
291+
assert_eq!(id, expected_id);
292+
}
293+
294+
#[test]
295+
#[should_panic]
296+
fn should_fail_trying_to_instantiate_from_a_byte_slice_with_less_than_20_bytes() {
297+
let less_than_20_bytes = [0; 19];
298+
let _ = peer::Id::from_bytes(&less_than_20_bytes);
299+
}
300+
301+
#[test]
302+
#[should_panic]
303+
fn should_fail_trying_to_instantiate_from_a_byte_slice_with_more_than_20_bytes() {
304+
let more_than_20_bytes = [0; 21];
305+
let _ = peer::Id::from_bytes(&more_than_20_bytes);
306+
}
307+
308+
#[test]
309+
fn should_be_converted_from_a_20_byte_array() {
310+
let id = peer::Id::from([
311+
0, 159, 146, 150, 0, 159, 146, 150, 0, 159, 146, 150, 0, 159, 146, 150, 0, 159, 146, 150,
312+
]);
313+
314+
let expected_id = peer::Id([
315+
0, 159, 146, 150, 0, 159, 146, 150, 0, 159, 146, 150, 0, 159, 146, 150, 0, 159, 146, 150,
316+
]);
317+
318+
assert_eq!(id, expected_id);
319+
}
320+
321+
#[test]
322+
fn should_be_converted_from_a_byte_vector() {
323+
let id = peer::Id::try_from(
324+
[
325+
0, 159, 146, 150, 0, 159, 146, 150, 0, 159, 146, 150, 0, 159, 146, 150, 0, 159, 146, 150,
326+
]
327+
.to_vec(),
328+
)
329+
.unwrap();
330+
331+
let expected_id = peer::Id([
332+
0, 159, 146, 150, 0, 159, 146, 150, 0, 159, 146, 150, 0, 159, 146, 150, 0, 159, 146, 150,
333+
]);
334+
335+
assert_eq!(id, expected_id);
336+
}
337+
338+
#[test]
339+
#[should_panic]
340+
fn should_fail_trying_to_convert_from_a_byte_vector_with_less_than_20_bytes() {
341+
let _ = peer::Id::try_from([0; 19].to_vec()).unwrap();
342+
}
343+
344+
#[test]
345+
#[should_panic]
346+
fn should_fail_trying_to_convert_from_a_byte_vector_with_more_than_20_bytes() {
347+
let _ = peer::Id::try_from([0; 21].to_vec()).unwrap();
348+
}
349+
242350
#[test]
243351
fn should_be_converted_to_hex_string() {
244352
let id = peer::Id(*b"-qB00000000000000000");

tests/http/bencode.rs

Lines changed: 0 additions & 15 deletions
This file was deleted.

tests/http/mod.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,28 @@
11
pub mod asserts;
2-
pub mod bencode;
32
pub mod client;
43
pub mod connection_info;
54
pub mod requests;
65
pub mod responses;
76
pub mod server;
7+
8+
use percent_encoding::NON_ALPHANUMERIC;
9+
10+
pub type ByteArray20 = [u8; 20];
11+
12+
pub fn percent_encode_byte_array(bytes: &ByteArray20) -> String {
13+
percent_encoding::percent_encode(bytes, NON_ALPHANUMERIC).to_string()
14+
}
15+
16+
pub struct InfoHash(ByteArray20);
17+
18+
impl InfoHash {
19+
pub fn new(vec: &[u8]) -> Self {
20+
let mut byte_array_20: ByteArray20 = Default::default();
21+
byte_array_20.clone_from_slice(vec);
22+
Self(byte_array_20)
23+
}
24+
25+
pub fn bytes(&self) -> ByteArray20 {
26+
self.0
27+
}
28+
}

tests/http/requests/announce.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ use std::fmt;
22
use std::net::{IpAddr, Ipv4Addr};
33
use std::str::FromStr;
44

5-
use percent_encoding::NON_ALPHANUMERIC;
65
use serde_repr::Serialize_repr;
76
use torrust_tracker::protocol::info_hash::InfoHash;
87
use torrust_tracker::tracker::peer::Id;
98

10-
use crate::http::bencode::ByteArray20;
9+
use crate::http::{percent_encode_byte_array, ByteArray20};
1110

1211
pub struct Query {
1312
pub info_hash: ByteArray20,
@@ -211,11 +210,11 @@ impl QueryParams {
211210
let compact = announce_query.compact.as_ref().map(std::string::ToString::to_string);
212211

213212
Self {
214-
info_hash: Some(percent_encoding::percent_encode(&announce_query.info_hash, NON_ALPHANUMERIC).to_string()),
213+
info_hash: Some(percent_encode_byte_array(&announce_query.info_hash)),
215214
peer_addr: Some(announce_query.peer_addr.to_string()),
216215
downloaded: Some(announce_query.downloaded.to_string()),
217216
uploaded: Some(announce_query.uploaded.to_string()),
218-
peer_id: Some(percent_encoding::percent_encode(&announce_query.peer_id, NON_ALPHANUMERIC).to_string()),
217+
peer_id: Some(percent_encode_byte_array(&announce_query.peer_id)),
219218
port: Some(announce_query.port.to_string()),
220219
left: Some(announce_query.left.to_string()),
221220
event,

tests/http/requests/scrape.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
use std::fmt;
22
use std::str::FromStr;
33

4-
use percent_encoding::NON_ALPHANUMERIC;
54
use torrust_tracker::protocol::info_hash::InfoHash;
65

7-
use crate::http::bencode::ByteArray20;
6+
use crate::http::{percent_encode_byte_array, ByteArray20};
87

98
pub struct Query {
109
pub info_hash: Vec<ByteArray20>,
@@ -111,7 +110,7 @@ impl QueryParams {
111110
let info_hashes = scrape_query
112111
.info_hash
113112
.iter()
114-
.map(|info_hash_bytes| percent_encoding::percent_encode(info_hash_bytes, NON_ALPHANUMERIC).to_string())
113+
.map(percent_encode_byte_array)
115114
.collect::<Vec<String>>();
116115

117116
Self { info_hash: info_hashes }

tests/http/responses/scrape.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::str;
44
use serde::{self, Deserialize, Serialize};
55
use serde_bencode::value::Value;
66

7-
use crate::http::bencode::{ByteArray20, InfoHash};
7+
use crate::http::{ByteArray20, InfoHash};
88

99
#[derive(Debug, PartialEq, Default)]
1010
pub struct Response {

0 commit comments

Comments
 (0)