• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

royaltm / rust-delharc / 8115191789

01 Mar 2024 05:50PM UTC coverage: 89.295% (+1.4%) from 87.907%
8115191789

Pull #6

github

web-flow
Merge a2b66817c into 8bad87b9f
Pull Request #6: no-std

289 of 343 new or added lines in 16 files covered. (84.26%)

1 existing line in 1 file now uncovered.

1977 of 2214 relevant lines covered (89.3%)

1662119.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.46
/src/header/parser.rs
1
use core::num::Wrapping;
2
use core::slice;
3
use core::fmt::Write;
4
#[cfg(not(feature = "std"))]
5
use alloc::vec::Vec;
6
use crate::error::{LhaError, LhaResult};
7
use crate::stub_io::Read;
8
use crate::crc::Crc16;
9
use super::*;
10

11
/// Raw identifiers of extra headers.
12
pub mod ext {
13
    /// The "Common" header's CRC-16 field will always be reset to 0 in the parsed header data.
14
    /// This is the necessary condition to verify header's checksum.
15
    pub const EXT_HEADER_COMMON:      u8 = 0x00;
16
    pub const EXT_HEADER_FILENAME:    u8 = 0x01;
17
    pub const EXT_HEADER_PATH:        u8 = 0x02;
18
    pub const EXT_HEADER_MULTI_DISC:  u8 = 0x39;
19
    pub const EXT_HEADER_COMMENT:     u8 = 0x3F;
20
    pub const EXT_HEADER_MSDOS_ATTRS: u8 = 0x40;
21
    pub const EXT_HEADER_MSDOS_TIME:  u8 = 0x41;
22
    pub const EXT_HEADER_MSDOS_SIZE:  u8 = 0x42;
23
    pub const EXT_HEADER_UNIX_PERM:   u8 = 0x50;
24
    pub const EXT_HEADER_UNIX_UIDGID: u8 = 0x51;
25
    pub const EXT_HEADER_UNIX_GROUP:  u8 = 0x52;
26
    pub const EXT_HEADER_UNIX_OWNER:  u8 = 0x53;
27
    pub const EXT_HEADER_UNIX_TIME:   u8 = 0x54;
28
    pub const EXT_HEADER_OS9:         u8 = 0xCC;
29
    pub const EXT_HEADER_EXT_ATTRS:   u8 = 0x7F;
30
}
31

32
use ext::*;
33
/// An iterator through extra headers, yielding the headers' raw content excluding
34
/// the next header length field.
35
pub struct ExtraHeaderIter<'a> {
36
    data: &'a [u8],
37
    header_length: u32,
38
    header_len32: bool
39
}
40

41
impl<'a> Iterator for ExtraHeaderIter<'a> {
42
    type Item = &'a [u8];
43

44
    fn next(&mut self) -> Option<Self::Item> {
30,744✔
45
        let header_length = self.header_length as usize;
30,744✔
46
        if header_length == 0 {
30,744✔
47
            return None
10,689✔
48
        }
20,055✔
49
        let counter_size = if self.header_len32 { 4 } else { 2 };
20,055✔
50
        let (res, data) = self.data.split_at(header_length);
20,055✔
51
        let (res, len) = res.split_at(header_length - counter_size);
20,055✔
52
        let len = if self.header_len32 {
20,055✔
53
            read_u32(len).unwrap()
1,323✔
54
        }
55
        else {
56
            read_u16(len).unwrap() as u32
18,732✔
57
        };
58
        self.header_length = len;
20,055✔
59
        self.data = data;
20,055✔
60
        Some(res)
20,055✔
61
    }
30,744✔
62
}
63

64
#[derive(Clone, Copy, Debug, Default)]
65
#[repr(C)]
66
#[repr(packed)]
67
struct LhaRawBaseHeader {
68
    compression: [u8;5],
69
    compressed_size: [u8;4],
70
    original_size: [u8;4],
71
    last_modified: [u8;4],
72
    msdos_attrs: u8,
73
    lha_level: u8
74
}
75

76
struct Parser<'a, R> {
77
    rd: &'a mut R,
78
    crc: Crc16,
79
    csum: Wrapping<u8>,
80
    len: usize
81
}
82

83
impl<R: Read> Parser<'_, R> {
84
    // NOTE: does not update wrapping sum
85
    fn read_u8_or_none(&mut self) -> LhaResult<Option<u8>, R> {
340✔
86
        let mut byte = 0u8;
340✔
87
        if 0 == self.rd.read_all(slice::from_mut(&mut byte)).map_err(LhaError::Io)? {
340✔
88
            return Ok(None)
4✔
89
        }
336✔
90
        // self.rd.by_ref().bytes().next().transpose().map(|mb|
336✔
91
        //     mb.map(|byte| {
336✔
92
                self.update_checksums_no_wrapping_sum(slice::from_ref(&byte));
336✔
93
                // byte
336✔
94
        //     })
336✔
95
        // )
336✔
96
        Ok(Some(byte))
336✔
97
    }
340✔
98

99
    fn read_u8(&mut self) -> LhaResult<u8, R> {
458✔
100
        let mut byte: u8 = 0;
458✔
101
        self.read_exact(slice::from_mut(&mut byte))?;
458✔
102
        Ok(byte)
458✔
103
    }
458✔
104

105
    fn read_u16(&mut self) -> LhaResult<u16, R> {
291✔
106
        let mut buf = [0u8;2];
291✔
107
        self.read_exact(&mut buf)?;
291✔
108
        Ok(u16::from_le_bytes(buf))
291✔
109
    }
291✔
110

111
    fn read_u32(&mut self) -> LhaResult<u32, R> {
12✔
112
        let mut buf = [0u8;4];
12✔
113
        self.read_exact(&mut buf)?;
12✔
114
        Ok(u32::from_le_bytes(buf))
12✔
115
    }
12✔
116

117
    fn read_exact(&mut self, buf: &mut [u8]) -> LhaResult<(), R> {
946✔
118
        self.rd.read_exact(buf).map_err(LhaError::Io)?;
946✔
119
        self.update_checksums(buf);
946✔
120
        Ok(())
946✔
121
    }
946✔
122

123
    fn read_limit(&mut self, limit: usize) -> LhaResult<Box<[u8]>, R> {
158✔
124
        let mut buf = Vec::with_capacity(limit);
158✔
125
        self.read_limit_no_checksums(limit, &mut buf)?;
158✔
126
        self.update_checksums(&buf);
158✔
127
        Ok(buf.into_boxed_slice())
158✔
128
    }
158✔
129

130
    fn update_checksums(&mut self, buf: &[u8]) {
1,104✔
131
        self.update_checksums_no_wrapping_sum(buf);
1,104✔
132
        self.csum = wrapping_csum(self.csum, buf);
1,104✔
133
    }
1,104✔
134

135
    fn update_checksums_no_wrapping_sum(&mut self, buf: &[u8]) {
1,764✔
136
        self.len += buf.len();
1,764✔
137
        self.crc.digest(buf);
1,764✔
138
    }
1,764✔
139

140
    fn read_limit_no_checksums(&mut self, limit: usize, buf: &mut Vec<u8>) -> LhaResult<(), R> {
482✔
141
        buf.try_reserve_exact(limit).map_err(|_| LhaError::HeaderParse("memory allocation failed"))?;
482✔
142
        // TODO: use BorrowedBuf once stabilized
143
        let spare = unsafe { core::mem::transmute::<_, &mut [u8]>(&mut buf.spare_capacity_mut()[..limit]) };
482✔
144
        self.rd.read_exact(spare).map_err(LhaError::Io)?;
482✔
145
        unsafe { buf.set_len(buf.len() + limit); }
482✔
146
        // if self.rd.by_ref().take(limit as u64).read_to_end(buf)? != limit {
482✔
147
        //     return Err(LhaError::HeaderParse("file is too short"))
482✔
148
        // }
482✔
149
        Ok(())
482✔
150
    }
482✔
151
}
152

153
impl LhaHeader {
154
    /// Attempt to parse the LHA header. Return `Ok(Some(LhaHeader))` on success. Return `Ok(None)`
155
    /// if the end of archive marker (a `0` byte) was encountered.
156
    ///
157
    /// The method validates all length and checksum fields of the header, but does not parse extra
158
    /// headers except:
159
    /// * The ["Common"][EXT_HEADER_COMMON] header for validating the header's CRC-16 checksum.
160
    /// * The ["MS-DOS Attributes"][EXT_HEADER_MSDOS_ATTRS] header for reading MS-DOS attributes.
161
    /// * The ["MS-DOS Size"][EXT_HEADER_MSDOS_SIZE] header for reading 64-bit file size.
162
    ///
163
    /// All extra data is available as raw bytes and extra headers can be iterated with [LhaHeader::iter_extra].
164
    ///
165
    /// Instance methods can be further called on the parsed `LhaHeader` struct to attempt to parse the
166
    /// name and path of the file or other file's meta-data.
167
    ///
168
    /// # Errors
169
    /// Returns an error from the underlying reading operations or because a malformed header was encountered.
170
    pub fn read<R: Read>(rd: &mut R) -> LhaResult<Option<LhaHeader>, R> {
340✔
171
        let mut parser = Parser {
340✔
172
            rd, 
340✔
173
            crc: Crc16::default(),
340✔
174
            csum: Wrapping(0),
340✔
175
            len: 0
340✔
176
        };
340✔
177
        let header_len = match parser.read_u8_or_none()? {
340✔
178
            Some(0)|None => return Ok(None),
155✔
179
            Some(len) => len
185✔
180
        };
181
        let csum = parser.read_u8()?;
185✔
182
        // reset wrapping checksum which should not include the first 2 bytes
183
        parser.csum = Wrapping(0);
185✔
184

185✔
185
        let mut raw_header = LhaRawBaseHeader::default();
185✔
186
        parser.read_exact(unsafe {
185✔
187
            // safe because LhaRawBaseHeader is packed and contains only byte type members
185✔
188
            struct_slice_mut(&mut raw_header)
185✔
189
        })?;
185✔
190
        if raw_header.lha_level > 3 {
185✔
NEW
191
            return Err(LhaError::HeaderParse("unknown header level"))
×
192
        }
185✔
193

194
        // read filename if level 0 or 1
195
        let filename = if raw_header.lha_level < 2 {
185✔
196
            let filename_len = parser.read_u8()? as usize;
141✔
197
            if (header_len as usize) < parser.len + filename_len {
141✔
NEW
198
                return Err(LhaError::HeaderParse("wrong header size"))
×
199
            }
141✔
200
            parser.read_limit(filename_len)?
141✔
201
        }
202
        else {
203
            Box::new([])
44✔
204
        };
205

206
        // file CRC-16
207
        let file_crc = parser.read_u16()?;
185✔
208

209
        // OS-TYPE
210
        let mut os_type = 0;
185✔
211
        if raw_header.lha_level > 0 {
185✔
212
            os_type = parser.read_u8()?;
112✔
213
        }
73✔
214

215
        // extended area, only 0 and 1 level
216
        let mut extended_area: Box<[u8]> = Box::new([]);
185✔
217
        if raw_header.lha_level < 2 {
185✔
218
            let mut min_len = parser.len;
141✔
219
            if raw_header.lha_level == 0 {
141✔
220
                min_len -= 2; // no extra headers
73✔
221
            }
105✔
222
            if (header_len as usize) < min_len {
141✔
NEW
223
                return Err(LhaError::HeaderParse("wrong header size"))
×
224
            }
141✔
225
            let mut extended_len = (header_len as usize) - min_len;
141✔
226
            if extended_len != 0 && raw_header.lha_level == 0  {
141✔
227
                // get os_type from level 0 extended area
228
                extended_len -= 1;
17✔
229
                os_type = parser.read_u8()?;
17✔
230
            }
124✔
231
            if extended_len != 0 {
141✔
232
                extended_area = parser.read_limit(extended_len)?;
17✔
233
            }
124✔
234
        };
44✔
235

236
        // extra headers
237
        let mut long_header_len: u32 = 0; // a long header length found in level >= 2
185✔
238
        let mut first_header_len: u32 = 0;
185✔
239
        let mut extra_headers = Vec::new();
185✔
240
        // establish the first extra header length and the long header length
185✔
241
        match raw_header.lha_level {
185✔
242
            1 => {
243
                first_header_len = parser.read_u16()? as u32;
68✔
244
            }
245
            2 => {
246
                long_header_len = u16::from_le_bytes([header_len, csum]) as u32;
38✔
247
                first_header_len = parser.read_u16()? as u32;
38✔
248
            }
249
            3 => {
250
                long_header_len = parser.read_u32()?;
6✔
251
                first_header_len = parser.read_u32()?;
6✔
252
                if header_len != 4 || csum != 0 {
6✔
NEW
253
                    return Err(LhaError::HeaderParse("invalid header"))
×
254
                }
6✔
255
            }
256
            _ => {}
73✔
257
        }
258

259
        // validate level 0 and 1 header checksum
260
        if raw_header.lha_level < 2 {
185✔
261
            if csum != parser.csum.0 {
141✔
NEW
262
                return Err(LhaError::HeaderParse("invalid header level checksum"))
×
263
            }
141✔
264
        }
265
        else if long_header_len < parser.len as u32 + first_header_len {
44✔
NEW
266
            return Err(LhaError::HeaderParse("wrong header size"))
×
267
        }
44✔
268

269
        let mut msdos_attrs = MsDosAttrs::from_bits_retain(raw_header.msdos_attrs as u16);
185✔
270
        let mut original_size = u32::from_le_bytes(raw_header.original_size) as u64;
185✔
271
        let mut compressed_size = u32::from_le_bytes(raw_header.compressed_size) as u64;
185✔
272
        let mut header_crc: Option<u16> = None;
185✔
273
        // read extra headers
274
        let min_header_len = if raw_header.lha_level == 3 { 5 } else { 3 };
185✔
275
        let mut extra_header_len = first_header_len as usize;
185✔
276
        while extra_header_len != 0 {
509✔
277
            if extra_header_len < min_header_len {
324✔
NEW
278
                return Err(LhaError::HeaderParse("wrong extra header size"))
×
279
            }
324✔
280
            // check long header length (level 2, 3)
324✔
281
            if long_header_len != 0 {
324✔
282
                if (long_header_len as usize) < parser.len + extra_header_len - 2 {
164✔
NEW
283
                    return Err(LhaError::HeaderParse("wrong header size"))
×
284
                }
164✔
285
            }
286
            else if compressed_size < (extra_headers.len() + extra_header_len) as u64 {
160✔
287
                // otherwise check skip size (level 1)
NEW
288
                return Err(LhaError::HeaderParse("wrong header size"))
×
289
            }
160✔
290
            parser.read_limit_no_checksums(extra_header_len, &mut extra_headers)?;
324✔
291
            let start = extra_headers.len() - extra_header_len;
324✔
292
            let header = &mut extra_headers[start..];
324✔
293
            match header {
10✔
294
                // we need to extract the CRC-16 from header and clear it in order to calculate checksum
295
                [EXT_HEADER_COMMON, data @ ..] => {
53✔
296
                    if header_crc.is_some() {
53✔
NEW
297
                        return Err(LhaError::HeaderParse("double common CRC-16 header"))
×
298
                    }
53✔
299
                    if let Some(crc) = data.get_mut(0..2) {
53✔
300
                        header_crc = read_u16(crc);
53✔
301
                        for p in crc.iter_mut() {
106✔
302
                            *p = 0;
106✔
303
                        }
106✔
304
                    }
×
305
                }
306
                [EXT_HEADER_MSDOS_ATTRS, data @ ..]|
10✔
307
                [EXT_HEADER_EXT_ATTRS,   data @ ..] if data.len() >= 2 => {
14✔
308
                    if let Some(attrs) = read_u16(&data[0..2]) {
16✔
309
                        msdos_attrs = MsDosAttrs::from_bits_retain(attrs);
16✔
310
                    }
16✔
311
                }
312
                [EXT_HEADER_MSDOS_SIZE, data @ ..] if raw_header.lha_level >= 2 && data.len() >= 16 => {
×
NEW
313
                    if let (Some(compr), Some(orig)) = (read_u64(&data[0..8]), read_u64(&data[8..16])) {
×
NEW
314
                        compressed_size = compr;
×
NEW
315
                        original_size = orig;
×
UNCOV
316
                    }
×
317
                }
318
                _ => {}
255✔
319
            }
320
            parser.update_checksums_no_wrapping_sum(header);
324✔
321
            extra_header_len = if raw_header.lha_level == 3 {
324✔
322
                read_u32(&header[header.len() - 4..]).unwrap() as usize
21✔
323
            }
324
            else {
325
                read_u16(&header[header.len() - 2..]).unwrap() as usize
303✔
326
            }
327
        }
328

329
        // validate long header length
330
        if long_header_len != 0 &&
185✔
331
           long_header_len != parser.len as u32
44✔
332
        {
333
            if raw_header.lha_level == 2 && long_header_len == parser.len as u32 + 1
9✔
334
            {
335
                // read padding byte
336
                parser.read_u8()?;
3✔
337
            }
338
            else if raw_header.lha_level == 2 && long_header_len + 2 != parser.len as u32 {
6✔
339
                // some packers (Osk) don't include self in the header length
NEW
340
                return Err(LhaError::HeaderParse("wrong length of headers"))
×
341
            }
6✔
342
        }
176✔
343

344
        // validate headers CRC
345
        if let Some(crc) = header_crc {
185✔
346
            if crc != parser.crc.sum16() {
53✔
NEW
347
                return Err(LhaError::HeaderParse("wrong header CRC-16 checksum"))
×
348
            }
53✔
349
        }
132✔
350

351
        // adjust compressed size for level 1
352
        if raw_header.lha_level == 1 {
185✔
353
            if extra_headers.len() as u64 > compressed_size {
68✔
NEW
354
                return Err(LhaError::HeaderParse("wrong length of skip size"))
×
355
            }
68✔
356
            compressed_size -= extra_headers.len() as u64;
68✔
357
        }
117✔
358

359
        let compression = raw_header.compression;
185✔
360
        let last_modified = u32::from_le_bytes(raw_header.last_modified);
185✔
361
        let extra_headers = extra_headers.into_boxed_slice();
185✔
362

185✔
363
        Ok(Some(LhaHeader {
185✔
364
            level: raw_header.lha_level,
185✔
365
            compression,
185✔
366
            compressed_size,
185✔
367
            original_size,
185✔
368
            filename,
185✔
369
            os_type,
185✔
370
            msdos_attrs,
185✔
371
            last_modified,
185✔
372
            file_crc,
185✔
373
            extended_area,
185✔
374
            first_header_len,
185✔
375
            extra_headers
185✔
376
        }))
185✔
377
    }
340✔
378

379
    /// Return an iterator that will iterate through extra headers, yielding the headers' raw
380
    /// data, excluding the next header length field.
381
    ///
382
    /// # Note
383
    /// Each iterated raw header will have at least the size of 1 byte containing the header identifier.
384
    pub fn iter_extra(&self) -> ExtraHeaderIter<'_> {
11,613✔
385
        ExtraHeaderIter {
11,613✔
386
            data: &self.extra_headers,
11,613✔
387
            header_length: self.first_header_len,
11,613✔
388
            header_len32: self.level == 3
11,613✔
389
        }
11,613✔
390
    }
11,613✔
391
}
392

393
fn read_u16(slice: &[u8]) -> Option<u16> {
26,544✔
394
    match slice {
26,544✔
395
        &[lo, hi] => Some(u16::from_le_bytes([lo, hi])),
26,544✔
396
        _ => None
×
397
    }
398
}
26,544✔
399

400
pub(super) fn read_u32(slice: &[u8]) -> Option<u32> {
2,793✔
401
    match slice {
2,793✔
402
        &[b0, b1, b2, b3] => Some(u32::from_le_bytes([b0, b1, b2, b3])),
2,793✔
403
        _ => None
×
404
    }
405
}
2,793✔
406

407
pub(super) fn read_u64(slice: &[u8]) -> Option<u64> {
189✔
408
    match slice {
189✔
409
        &[b0, b1, b2, b3, b4, b5, b6, b7] => Some(u64::from_le_bytes([b0, b1, b2, b3, b4, b5, b6, b7])),
189✔
410
        _ => None
×
411
    }
412
}
189✔
413

414
fn wrapping_csum(init: Wrapping<u8>, data: &[u8]) -> Wrapping<u8> {
23,184✔
415
    let sum: Wrapping<u8> = data.iter().copied().map(Wrapping).sum();
23,184✔
416
    sum + init
23,184✔
417
}
23,184✔
418

419
pub(super) fn split_data_at_nil_or_end(data: &[u8]) -> (&[u8], Option<&[u8]>) {
129✔
420
    match memchr::memchr(0, data) {
129✔
421
        Some(index) => (&data[0..index], Some(&data[index + 1..data.len()])),
44✔
422
        None => (data, None)
85✔
423
    }
424
}
129✔
425

426
#[cfg(feature = "std")]
427
pub(super) fn parse_pathname(data: &[u8], path: &mut PathBuf) {
4,214✔
428
    path.reserve(data.len());
4,214✔
429
    // split by all possible path separators
430
    for part in data.split(|&c| c == 0xFF || c == b'/' || c == b'\\') {
36,249✔
431
        match part {
6,718✔
432
            b"."|b".."|[] => {} // ignore malicious and empty paths
6,718✔
433
            name => path.push(parse_str_nilterm(name, false, false).as_ref())
4,929✔
434
        }
435
    }
436
}
4,214✔
437

438
pub(super) fn parse_pathname_to_str(data: &[u8], path: &mut String) {
3,626✔
439
    path.reserve(data.len());
3,626✔
440
    // split by all possible path separators
441
    for part in data.split(|&c| c == 0xFF || c == b'/' || c == b'\\') {
31,482✔
442
        match part {
5,794✔
443
            b"."|b".."|[] => {} // ignore malicious and empty paths
5,794✔
444
            name => {
4,341✔
445
                if !path.is_empty() {
4,341✔
446
                    path.push('/');
1,438✔
447
                }
2,908✔
448
                path.push_str(parse_str_nilterm(name, false, false).as_ref())
4,341✔
449
            }
450
        }
451
    }
452
}
3,626✔
453

454
#[cfg(feature = "std")]
455
#[inline(always)]
456
fn is_separator(c: char) -> bool {
73,051✔
457
    std::path::is_separator(c)
73,051✔
458
}
73,051✔
459

460
#[cfg(not(feature = "std"))]
461
fn is_separator(c: char) -> bool {
462
    c == '/' || c == '\\'
463
}
464

465
pub(super) fn parse_str_nilterm(
11,107✔
466
        data: &[u8], nilterm: bool, ignore_sep: bool
11,107✔
467
    ) -> Cow<str>
11,107✔
468
{
11,107✔
469
    if let Some(index) = data.iter().position(|&c|
11,107✔
470
            !(0x20..0x7f).contains(&c) ||
73,259✔
471
            (!ignore_sep && is_separator(c as char))
73,259✔
472
        )
11,107✔
473
    {
474
        let mut out = String::with_capacity(data.len()*3);
31✔
475
        let (head, rest) = data.split_at(index);
31✔
476
        out.push_str(unsafe { // safe because head was validated
31✔
477
            core::str::from_utf8_unchecked(head)
31✔
478
        });
31✔
479
        for byte in rest.iter() {
375✔
480
            match byte {
5✔
481
                0 if nilterm => break,
3✔
482
                0x00..=0x1f|
372✔
483
                0x7f..=0xff => {
73✔
484
                    write!(out, "%{:02x}", byte).unwrap();
73✔
485
                }
73✔
486
                &ch => {
299✔
487
                    let c = ch as char;
299✔
488
                    if !ignore_sep && is_separator(c) {
299✔
489
                        out.push('_');
5✔
490
                    }
5✔
491
                    else {
294✔
492
                        out.push(c);
294✔
493
                    }
294✔
494
                }
495
            }
496
        }
497
        Cow::Owned(out)
31✔
498
    }
499
    else {
500
        unsafe { // safe because data was validated
501
            Cow::Borrowed(core::str::from_utf8_unchecked(data))
11,076✔
502
        }
503
    }
504
}
11,107✔
505

506
/// # Safety
507
/// This function can be used safely only with packed structs that solely consist of
508
/// `u8` or array of `u8` primitives.
509
unsafe fn struct_slice_mut<T: Copy>(obj: &mut T) -> &mut [u8] {
185✔
510
    let len = core::mem::size_of::<T>() / core::mem::size_of::<u8>();
185✔
511
    core::slice::from_raw_parts_mut(obj as *mut T as *mut u8, len)
185✔
512
}
185✔
513

514
#[cfg(feature = "std")]
515
#[cfg(test)]
516
mod tests {
517
    use super::*;
518
    use std::path::MAIN_SEPARATOR;
519

520
    fn parse_filename(data: &[u8]) -> Cow<str> {
6✔
521
        parse_str_nilterm(data, false, false)
6✔
522
    }
6✔
523

524
   #[test]
525
    fn split_data_at_nil_or_end_works() {
1✔
526
        assert_eq!((&b"Foo"[..], None), split_data_at_nil_or_end(b"Foo"));
1✔
527
        assert_eq!((&b"Foo"[..], Some(&b"Bar"[..])), split_data_at_nil_or_end(b"Foo\x00Bar"));
1✔
528
        assert_eq!((&[][..], Some(&b"Bar"[..])), split_data_at_nil_or_end(b"\x00Bar"));
1✔
529
    }
1✔
530

531
   #[test]
532
    fn path_parser_works() {
1✔
533
        assert_eq!("", parse_filename(b""));
1✔
534
        assert_eq!("Hello World!", parse_filename(b"Hello World!"));
1✔
535
        if std::path::is_separator('/') {
1✔
536
            assert_eq!("_Hello_World_", parse_filename(b"/Hello/World/"));
1✔
537
        }
×
538
        if std::path::is_separator('\\') {
1✔
539
            assert_eq!("_Hello_World_", parse_filename(br"\Hello\World\"));
×
540
        }
1✔
541
        assert_eq!("Hello%00World%7f", parse_filename(b"Hello\x00World\x7f"));
1✔
542
        assert_eq!("Hello%01World%ff", parse_filename(b"Hello\x01World\xff"));
1✔
543
        assert_eq!("Hello", parse_str_nilterm(b"Hello\x00World\xff", true, false));
1✔
544
        if std::path::is_separator('/') {
1✔
545
            assert_eq!("He_llo", parse_str_nilterm(b"He/llo\x00World\xff", true, false));
1✔
546
            assert_eq!("He/llo", parse_str_nilterm(b"He/llo\x00World\xff", true, true));
1✔
547
            assert_eq!("He/llo%00World%ff", parse_str_nilterm(b"He/llo\x00World\xff", false, true));
1✔
548
            assert_eq!("_Hello%1fWorld%80", parse_filename(b"/Hello\x1fWorld\x80"));
1✔
549
        }
×
550
        let mut path = PathBuf::new();
1✔
551
        parse_pathname(b"", &mut path);
1✔
552
        assert!(path.is_relative());
1✔
553
        assert_eq!("", path.to_str().unwrap());
1✔
554
        parse_pathname(b"/", &mut path);
1✔
555
        assert!(path.is_relative());
1✔
556
        assert_eq!("", path.to_str().unwrap());
1✔
557
        parse_pathname(br"\", &mut path);
1✔
558
        assert!(path.is_relative());
1✔
559
        assert_eq!("", path.to_str().unwrap());
1✔
560
        parse_pathname(br".", &mut path);
1✔
561
        assert!(path.is_relative());
1✔
562
        assert_eq!("", path.to_str().unwrap());
1✔
563
        parse_pathname(br"..", &mut path);
1✔
564
        assert!(path.is_relative());
1✔
565
        assert_eq!("", path.to_str().unwrap());
1✔
566
        parse_pathname(br"./..", &mut path);
1✔
567
        assert!(path.is_relative());
1✔
568
        assert_eq!("", path.to_str().unwrap());
1✔
569
        parse_pathname(br".\..", &mut path);
1✔
570
        assert!(path.is_relative());
1✔
571
        assert_eq!("", path.to_str().unwrap());
1✔
572
        parse_pathname(br"/..\./", &mut path);
1✔
573
        assert!(path.is_relative());
1✔
574
        assert_eq!("", path.to_str().unwrap());
1✔
575
        parse_pathname(br"\../.\", &mut path);
1✔
576
        assert!(path.is_relative());
1✔
577
        assert_eq!("", path.to_str().unwrap());
1✔
578
        parse_pathname(br"foo/bar\baz", &mut path);
1✔
579
        assert!(path.is_relative());
1✔
580
        let expect = format!("foo{}bar{}baz", MAIN_SEPARATOR, MAIN_SEPARATOR);
1✔
581
        assert_eq!(expect, path.to_str().unwrap());
1✔
582
        path.clear();
1✔
583
        parse_pathname(br"\foo/bar\baz/", &mut path);
1✔
584
        assert!(path.is_relative());
1✔
585
        let expect = format!("foo{}bar{}baz", MAIN_SEPARATOR, MAIN_SEPARATOR);
1✔
586
        assert_eq!(expect, path.to_str().unwrap());
1✔
587
        path.clear();
1✔
588
        parse_pathname(br"/foo\bar/baz\", &mut path);
1✔
589
        assert!(path.is_relative());
1✔
590
        let expect = format!("foo{}bar{}baz", MAIN_SEPARATOR, MAIN_SEPARATOR);
1✔
591
        assert_eq!(expect, path.to_str().unwrap());
1✔
592
        path.clear();
1✔
593
        parse_pathname(b"foo\xffbar\xffbaz", &mut path);
1✔
594
        assert!(path.is_relative());
1✔
595
        let expect = format!("foo{}bar{}baz", MAIN_SEPARATOR, MAIN_SEPARATOR);
1✔
596
        assert_eq!(expect, path.to_str().unwrap());
1✔
597
        path.clear();
1✔
598
        parse_pathname(b"\xfffoo\xffb\x91ar\xffbaz\xff", &mut path);
1✔
599
        assert!(path.is_relative());
1✔
600
        let expect = format!("foo{}b%91ar{}baz", MAIN_SEPARATOR, MAIN_SEPARATOR);
1✔
601
        assert_eq!(expect, path.to_str().unwrap());
1✔
602
        path.clear();
1✔
603
    }
1✔
604

605
    #[test]
606
    fn path_parser_to_str_works() {
1✔
607
        let mut path = String::new();
1✔
608
        parse_pathname_to_str(b"", &mut path);
1✔
609
        assert!(!path.starts_with('/'));
1✔
610
        assert_eq!("", &path);
1✔
611
        parse_pathname_to_str(b"/", &mut path);
1✔
612
        assert!(!path.starts_with('/'));
1✔
613
        assert_eq!("", &path);
1✔
614
        parse_pathname_to_str(br"\", &mut path);
1✔
615
        assert!(!path.starts_with('/'));
1✔
616
        assert_eq!("", &path);
1✔
617
        parse_pathname_to_str(br".", &mut path);
1✔
618
        assert!(!path.starts_with('/'));
1✔
619
        assert_eq!("", &path);
1✔
620
        parse_pathname_to_str(br"..", &mut path);
1✔
621
        assert!(!path.starts_with('/'));
1✔
622
        assert_eq!("", &path);
1✔
623
        parse_pathname_to_str(br"./..", &mut path);
1✔
624
        assert!(!path.starts_with('/'));
1✔
625
        assert_eq!("", &path);
1✔
626
        parse_pathname_to_str(br".\..", &mut path);
1✔
627
        assert!(!path.starts_with('/'));
1✔
628
        assert_eq!("", &path);
1✔
629
        parse_pathname_to_str(br"/..\./", &mut path);
1✔
630
        assert!(!path.starts_with('/'));
1✔
631
        assert_eq!("", &path);
1✔
632
        parse_pathname_to_str(br"\../.\", &mut path);
1✔
633
        assert!(!path.starts_with('/'));
1✔
634
        assert_eq!("", &path);
1✔
635
        parse_pathname_to_str(br"foo/bar\baz", &mut path);
1✔
636
        assert!(!path.starts_with('/'));
1✔
637
        let expect = "foo/bar/baz";
1✔
638
        assert_eq!(expect, &path);
1✔
639
        path.clear();
1✔
640
        parse_pathname_to_str(br"\foo/bar\baz/", &mut path);
1✔
641
        assert!(!path.starts_with('/'));
1✔
642
        let expect = "foo/bar/baz";
1✔
643
        assert_eq!(expect, &path);
1✔
644
        path.clear();
1✔
645
        parse_pathname_to_str(br"/foo\bar/baz\", &mut path);
1✔
646
        assert!(!path.starts_with('/'));
1✔
647
        let expect = "foo/bar/baz";
1✔
648
        assert_eq!(expect, &path);
1✔
649
        path.clear();
1✔
650
        parse_pathname_to_str(b"foo\xffbar\xffbaz", &mut path);
1✔
651
        assert!(!path.starts_with('/'));
1✔
652
        let expect = "foo/bar/baz";
1✔
653
        assert_eq!(expect, &path);
1✔
654
        path.clear();
1✔
655
        parse_pathname_to_str(b"\xfffoo\xffb\x91ar\xffbaz\xff", &mut path);
1✔
656
        assert!(!path.starts_with('/'));
1✔
657
        let expect = "foo/b%91ar/baz";
1✔
658
        assert_eq!(expect, &path);
1✔
659
        path.clear();
1✔
660
    }
1✔
661
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc