forked from torrust/torrust-tracker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpercent_encoding.rs
More file actions
146 lines (124 loc) · 4.96 KB
/
Copy pathpercent_encoding.rs
File metadata and controls
146 lines (124 loc) · 4.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! This module contains functions for percent decoding infohashes and peer IDs.
//!
//! Percent encoding is an encoding format used to encode arbitrary data in a
//! format that is safe to use in URLs. It is used by the HTTP tracker protocol
//! to encode infohashes and peer ids in the URLs of requests.
//!
//! `BitTorrent` infohashes and peer ids are percent encoded like any other
//! arbitrary URL parameter. But they are encoded from binary data (byte arrays)
//! which may not be valid UTF-8. That makes hard to use the `percent_encoding`
//! crate to decode them because all of them expect a well-formed UTF-8 string.
//! However, percent encoding is not limited to UTF-8 strings.
//!
//! More information about "Percent Encoding" can be found here:
//!
//! - <https://datatracker.ietf.org/doc/html/rfc3986#section-2.1>
//! - <https://en.wikipedia.org/wiki/URL_encoding>
//! - <https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding>
use torrust_info_hash::InfoHash;
use torrust_peer_id::PeerId;
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
pub enum PeerIdConversionError {
#[error("Peer id too short: expected 20 bytes, got {actual}")]
NotEnoughBytes { actual: usize },
#[error("Peer id too long: expected 20 bytes, got {actual}")]
TooManyBytes { actual: usize },
}
/// Percent decodes a percent encoded infohash. Internally an
/// [`InfoHash`] is a 20-byte array.
///
/// For example, given the infohash `3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0`,
/// it's percent encoded representation is `%3B%24U%04%CF%5F%11%BB%DB%E1%20%1C%EAjk%F4Z%EE%1B%C0`.
///
/// ```rust
/// use std::str::FromStr;
/// use torrust_tracker_http_tracker_protocol::percent_encoding::percent_decode_info_hash;
/// use torrust_info_hash::InfoHash;
///
/// let encoded_infohash = "%3B%24U%04%CF%5F%11%BB%DB%E1%20%1C%EAjk%F4Z%EE%1B%C0";
///
/// let info_hash = percent_decode_info_hash(encoded_infohash).unwrap();
///
/// assert_eq!(
/// info_hash,
/// InfoHash::from_str("3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0").unwrap()
/// );
/// ```
///
/// # Errors
///
/// Will return `Err` if the decoded bytes do not represent a valid
/// [`InfoHash`].
pub fn percent_decode_info_hash(raw_info_hash: &str) -> Result<InfoHash, torrust_info_hash::ConversionError> {
let bytes = percent_encoding::percent_decode_str(raw_info_hash).collect::<Vec<u8>>();
InfoHash::try_from(bytes)
}
/// Percent decodes a percent encoded peer id. Internally a peer [`Id`](PeerId)
/// is a 20-byte array.
///
/// For example, given the peer id `*b"-qB00000000000000000"`,
/// it's percent encoded representation is `%2DqB00000000000000000`.
///
/// ```rust
/// use std::str::FromStr;
///
/// use torrust_tracker_http_tracker_protocol::percent_encoding::percent_decode_peer_id;
/// use torrust_info_hash::InfoHash;
/// use torrust_peer_id::PeerId;
///
/// let encoded_peer_id = "%2DqB00000000000000000";
///
/// let peer_id = percent_decode_peer_id(encoded_peer_id).unwrap();
///
/// assert_eq!(peer_id, PeerId(*b"-qB00000000000000000"));
/// ```
///
/// # Errors
///
/// Will return `Err` if if the decoded bytes do not represent a valid [`PeerId`].
pub fn percent_decode_peer_id(raw_peer_id: &str) -> Result<PeerId, PeerIdConversionError> {
let bytes = percent_encoding::percent_decode_str(raw_peer_id).collect::<Vec<u8>>();
if bytes.len() < 20 {
return Err(PeerIdConversionError::NotEnoughBytes { actual: bytes.len() });
}
if bytes.len() > 20 {
return Err(PeerIdConversionError::TooManyBytes { actual: bytes.len() });
}
let mut peer_id = [0_u8; 20];
peer_id.copy_from_slice(&bytes);
Ok(PeerId(peer_id))
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use torrust_info_hash::InfoHash;
use torrust_peer_id::PeerId;
use crate::percent_encoding::{percent_decode_info_hash, percent_decode_peer_id};
#[test]
fn it_should_decode_a_percent_encoded_info_hash() {
let encoded_infohash = "%3B%24U%04%CF%5F%11%BB%DB%E1%20%1C%EAjk%F4Z%EE%1B%C0";
let info_hash = percent_decode_info_hash(encoded_infohash).unwrap();
assert_eq!(
info_hash,
InfoHash::from_str("3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0").unwrap()
);
}
#[test]
fn it_should_fail_decoding_an_invalid_percent_encoded_info_hash() {
let invalid_encoded_infohash = "invalid percent-encoded infohash";
let info_hash = percent_decode_info_hash(invalid_encoded_infohash);
assert!(info_hash.is_err());
}
#[test]
fn it_should_decode_a_percent_encoded_peer_id() {
let encoded_peer_id = "%2DqB00000000000000000";
let peer_id = percent_decode_peer_id(encoded_peer_id).unwrap();
assert_eq!(peer_id, PeerId(*b"-qB00000000000000000"));
}
#[test]
fn it_should_fail_decoding_an_invalid_percent_encoded_peer_id() {
let invalid_encoded_peer_id = "invalid percent-encoded peer id";
let peer_id = percent_decode_peer_id(invalid_encoded_peer_id);
assert!(peer_id.is_err());
}
}