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
use crate::ENCODED_LENGTH_OVERFLOWS;

/// Checks if the char needs to be encoded. Alpha-numeric chars and chars in non-specials do not
/// need to be encoded. All other chars need to be encoded.
///
/// ```
/// use enc::percent::needs_encoding;
///
/// let ns: &[u8] = &[b'_'];
///
/// // Encoding Not Needed
/// assert!(!needs_encoding(b'A', ns));
/// assert!(!needs_encoding(b'Z', ns));
/// assert!(!needs_encoding(b'a', ns));
/// assert!(!needs_encoding(b'z', ns));
/// assert!(!needs_encoding(b'0', ns));
/// assert!(!needs_encoding(b'9', ns));
/// assert!(!needs_encoding(b'_', ns));
///
/// // Encoding Needed
/// assert!(needs_encoding(b'A' - 1, ns));
/// assert!(needs_encoding(b'Z' + 1, ns));
/// assert!(needs_encoding(b'a' - 1, ns));
/// assert!(needs_encoding(b'z' + 1, ns));
/// assert!(needs_encoding(b'0' - 1, ns));
/// assert!(needs_encoding(b'9' + 1, ns));
/// assert!(needs_encoding(b'_' - 1, ns));
/// assert!(needs_encoding(b'_' + 1, ns));
/// ```
pub fn needs_encoding(c: u8, non_specials: &[u8]) -> bool {
    !c.is_ascii_alphanumeric() && !non_specials.contains(&c)
}

/// Gets the length of the encoded data given the length of the data and the number of chars that
/// need to be percent encoded.
///
/// # Panics
/// This function will panic if the length of the encoded data overflows usize.
fn encoded_length_usize(len: usize, count: usize) -> usize {
    let extra: usize = count.checked_mul(2).expect(ENCODED_LENGTH_OVERFLOWS);
    len.checked_add(extra).expect(ENCODED_LENGTH_OVERFLOWS)
}

#[cfg(test)]
mod encoded_length_usize_tests {

    #[test]
    fn test_encoded_length_usize() {
        assert_eq!(super::encoded_length_usize(0, 0), 0);
        assert_eq!(super::encoded_length_usize(1, 1), 3);
        assert_eq!(super::encoded_length_usize(3, 2), 7);
    }

    #[test]
    #[should_panic]
    fn test_encoded_length_usize_panics1() {
        super::encoded_length_usize(0, (std::usize::MAX / 2) + 1);
    }

    #[test]
    #[should_panic]
    fn test_encoded_length_usize_panics2() {
        super::encoded_length_usize(std::usize::MAX - 1, 1);
    }
}

/// Gets the length of the encoded data. This function is consistent with the encode function.
///
/// # Panics
/// This function will panic if the length of the encoded data overflows usize.
///
/// ```
/// use enc::percent::encoded_length;
///
/// let ns: &[u8] = &[b'_'];
///
/// assert_eq!(encoded_length(&[b'a'], ns), 1);
/// assert_eq!(encoded_length(&[b'a', b'_'], ns), 2);
/// assert_eq!(encoded_length(&[b'a', b'_', b'_' + 1], ns), 5);
/// assert_eq!(encoded_length(&[b'a', b'_', b'_' + 1, b'_' - 1], ns), 8);
/// ```
pub fn encoded_length(data: &[u8], non_specials: &[u8]) -> usize {
    let count: usize = data.iter()
        .filter(|r| needs_encoding(**r, non_specials))
        .count();
    encoded_length_usize(data.len(), count)
}

/// Encodes the data into the target. Returns the length of the encoded data. This function is
/// consistent with the encoded_length function.
///
/// # Panics
/// This function will panic if the target has insufficient space for the encoded data.
///
/// ```
/// use enc::percent::encode;
///
/// let target: &mut [u8] = &mut [0u8; 6];
/// let ns: &[u8] = &[b'_'];
///
/// assert_eq!(encode(&[0u8; 0], ns, target), 0);
///
/// assert_eq!(encode(&[b'a'], ns, target), 1);
/// assert_eq!(target[..1], [b'a']);
///
/// assert_eq!(encode(&[b'a', b'_', 0u8], ns, target), 5);
/// assert_eq!(target[..5], [b'a', b'_', b'%', b'0', b'0']);
///
/// ```
pub fn encode(data: &[u8], non_specials: &[u8], target: &mut [u8]) -> usize {
    let mut t: usize = 0;
    for r in data.iter() {
        let c: u8 = *r;
        if needs_encoding(c, non_specials) {
            target[t] = b'%';
            t += 1;
            let e: [u8; 2] = crate::hex::encode_byte_upper(c);
            target[t] = e[0];
            t += 1;
            target[t] = e[1];
            t += 1;
        } else {
            target[t] = c;
            t += 1;
        }
    }
    t
}

#[cfg(test)]
mod tests {

    #[test]
    #[should_panic]
    fn test_encode_lower_panics() {
        super::encode(&[b'a', b'a' - 1], &[b'_'], &mut [0u8; 3]);
    }
}