diff --git a/Cargo.lock b/Cargo.lock index f9b3a03dc..af7d471ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -489,6 +489,7 @@ dependencies = [ "image-meta 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "imap 1.0.2 (git+https://github.com/jonhoo/rust-imap?rev=281d2eb8ab50dc656ceff2ae749ca5045f334e15)", "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jetscii 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1010,6 +1011,11 @@ name = "itoa" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "jetscii" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "keccak" version = "0.1.0" @@ -2960,6 +2966,7 @@ dependencies = [ "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum jetscii 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5f25cca2463cb19dbb1061eb3bd38a8b5e4ce1cc5a5a9fc0e02de486d92b9b05" "checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" diff --git a/Cargo.toml b/Cargo.toml index 7cb3cd88b..b992f8874 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ image-meta = "0.1.0" quick-xml = "0.15.0" escaper = "0.1.0" bitflags = "1.1.0" +jetscii = "0.4.4" [dev-dependencies] tempfile = "3.0" diff --git a/proptest-regressions/dc_strencode.txt b/proptest-regressions/dc_strencode.txt new file mode 100644 index 000000000..3ef09f145 --- /dev/null +++ b/proptest-regressions/dc_strencode.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 679506fe9ac59df773f8cfa800fdab5f0a32fe49d2ab370394000a1aa5bc2a72 # shrinks to buf = "%0A" diff --git a/src/dc_mimefactory.rs b/src/dc_mimefactory.rs index e15a3a5e9..e5611779e 100644 --- a/src/dc_mimefactory.rs +++ b/src/dc_mimefactory.rs @@ -1218,7 +1218,7 @@ unsafe fn build_body_file( /* create mime part, for Content-Disposition, see RFC 2183. `Content-Disposition: attachment` seems not to make a difference to `Content-Disposition: inline` at least on tested Thunderbird and Gma'l in 2017. But I've heard about problems with inline and outl'k, so we just use the attachment-type until we run into other problems ... */ - needs_ext = dc_needs_ext_header(filename_to_send); + needs_ext = dc_needs_ext_header(as_str(filename_to_send)); mime_fields = mailmime_fields_new_filename( MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int, if needs_ext { @@ -1255,7 +1255,7 @@ unsafe fn build_body_file( strdup( b"filename*\x00" as *const u8 as *const libc::c_char, ), - dc_encode_ext_header(filename_to_send), + dc_encode_ext_header(as_str(filename_to_send)).strdup(), ), ); if !parm.is_null() { diff --git a/src/dc_mimeparser.rs b/src/dc_mimeparser.rs index cf99d24d2..5d68547fc 100644 --- a/src/dc_mimeparser.rs +++ b/src/dc_mimeparser.rs @@ -1204,8 +1204,8 @@ unsafe fn dc_mimeparser_add_single_part_if_known( } if !filename_parts.is_empty() { free(desired_filename as *mut libc::c_void); - let parts_c = CString::yolo(filename_parts); - desired_filename = dc_decode_ext_header(parts_c.as_ptr()); + desired_filename = + dc_decode_ext_header(filename_parts.as_bytes()).strdup(); } if desired_filename.is_null() { let param = mailmime_find_ct_parameter( diff --git a/src/dc_strencode.rs b/src/dc_strencode.rs index 8e0ffcf39..e1f4d1526 100644 --- a/src/dc_strencode.rs +++ b/src/dc_strencode.rs @@ -1,83 +1,17 @@ -use std::ffi::{CStr, CString}; +use std::borrow::Cow; +use std::ffi::CString; use std::ptr; use charset::Charset; use mmime::mailmime_decode::*; use mmime::mmapstring::*; use mmime::other::*; +use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS}; use crate::dc_tools::*; use crate::types::*; use crate::x::*; -#[inline] -fn isalnum(c: libc::c_int) -> libc::c_int { - if c < std::u8::MAX as libc::c_int { - (c as u8 as char).is_ascii_alphanumeric() as libc::c_int - } else { - 0 - } -} - -/* ****************************************************************************** - * URL encoding and decoding, RFC 3986 - ******************************************************************************/ -unsafe fn int_2_uppercase_hex(code: libc::c_char) -> libc::c_char { - static mut HEX: [libc::c_char; 17] = [ - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 0, - ]; - - HEX[(code as libc::c_int & 15i32) as usize] -} - -pub unsafe fn dc_urldecode(to_decode: *const libc::c_char) -> *mut libc::c_char { - let mut pstr: *const libc::c_char = to_decode; - if to_decode.is_null() { - return dc_strdup(b"\x00" as *const u8 as *const libc::c_char); - } - let buf: *mut libc::c_char = malloc(strlen(to_decode).wrapping_add(1)) as *mut libc::c_char; - let mut pbuf: *mut libc::c_char = buf; - assert!(!buf.is_null()); - - while 0 != *pstr { - if *pstr as libc::c_int == '%' as i32 { - if 0 != *pstr.offset(1isize) as libc::c_int && 0 != *pstr.offset(2isize) as libc::c_int - { - let fresh5 = pbuf; - pbuf = pbuf.offset(1); - *fresh5 = ((hex_2_int(*pstr.offset(1isize)) as libc::c_int) << 4i32 - | hex_2_int(*pstr.offset(2isize)) as libc::c_int) - as libc::c_char; - pstr = pstr.offset(2isize) - } - } else if *pstr as libc::c_int == '+' as i32 { - let fresh6 = pbuf; - pbuf = pbuf.offset(1); - *fresh6 = ' ' as i32 as libc::c_char - } else { - let fresh7 = pbuf; - pbuf = pbuf.offset(1); - *fresh7 = *pstr - } - pstr = pstr.offset(1isize) - } - *pbuf = '\u{0}' as i32 as libc::c_char; - - buf -} - -fn hex_2_int(ch: libc::c_char) -> libc::c_char { - let ch = ch as u8 as char; - if !ch.is_ascii_hexdigit() { - return (ch.to_ascii_lowercase() as i32 - 'a' as i32 + 10) as libc::c_char; - } - - match ch.to_digit(16) { - Some(res) => res as libc::c_char, - None => 0, - } -} - pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut libc::c_char { let mut ok_to_continue = true; let mut ret_str: *mut libc::c_char = ptr::null_mut(); @@ -290,374 +224,63 @@ pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_ out } -#[cfg(test)] -unsafe fn dc_encode_modified_utf7( - mut to_encode: *const libc::c_char, - change_spaces: libc::c_int, -) -> *mut libc::c_char { - let mut utf8pos: libc::c_uint; - let mut utf8total: libc::c_uint; - let mut c: libc::c_uint; - let mut utf7mode: libc::c_uint; - let mut bitstogo: libc::c_uint; - let mut utf16flag: libc::c_uint; - let mut ucs4: libc::c_ulong = 0; - let mut bitbuf: libc::c_ulong = 0; - let mut dst: *mut libc::c_char; - let res: *mut libc::c_char; - if to_encode.is_null() { - return dc_strdup(b"\x00" as *const u8 as *const libc::c_char); - } - res = malloc(2usize.wrapping_mul(strlen(to_encode)).wrapping_add(1)) as *mut libc::c_char; - dst = res; - assert!(!dst.is_null()); +pub fn dc_needs_ext_header(to_check: impl AsRef) -> bool { + let to_check = to_check.as_ref(); - utf7mode = 0i32 as libc::c_uint; - utf8total = 0i32 as libc::c_uint; - bitstogo = 0i32 as libc::c_uint; - utf8pos = 0i32 as libc::c_uint; - loop { - c = *to_encode as libc::c_uchar as libc::c_uint; - if !(c != '\u{0}' as i32 as libc::c_uint) { - break; - } - to_encode = to_encode.offset(1isize); - // normal character? - if c >= ' ' as i32 as libc::c_uint - && c <= '~' as i32 as libc::c_uint - && (c != '_' as i32 as libc::c_uint || 0 == change_spaces) - { - if 0 != utf7mode { - if 0 != bitstogo { - let fresh8 = dst; - dst = dst.offset(1); - *fresh8 = BASE64CHARS - [(bitbuf << (6i32 as libc::c_uint).wrapping_sub(bitstogo) & 0x3f) as usize] - } - let fresh9 = dst; - dst = dst.offset(1); - *fresh9 = '-' as i32 as libc::c_char; - utf7mode = 0i32 as libc::c_uint; - utf8pos = 0i32 as libc::c_uint; - bitstogo = 0i32 as libc::c_uint; - utf8total = 0i32 as libc::c_uint - } - if 0 != change_spaces && c == ' ' as i32 as libc::c_uint { - let fresh10 = dst; - dst = dst.offset(1); - *fresh10 = '_' as i32 as libc::c_char - } else { - let fresh11 = dst; - dst = dst.offset(1); - *fresh11 = c as libc::c_char - } - if c == '&' as i32 as libc::c_uint { - let fresh12 = dst; - dst = dst.offset(1); - *fresh12 = '-' as i32 as libc::c_char - } - } else { - if 0 == utf7mode { - let fresh13 = dst; - dst = dst.offset(1); - *fresh13 = '&' as i32 as libc::c_char; - utf7mode = 1i32 as libc::c_uint - } - // encode ascii characters as themselves - if c < 0x80i32 as libc::c_uint { - ucs4 = c as libc::c_ulong - } else if 0 != utf8total { - ucs4 = ucs4 << 6i32 | c as libc::c_ulong & 0x3f; - utf8pos = utf8pos.wrapping_add(1); - if utf8pos < utf8total { - continue; - } - } else { - utf8pos = 1i32 as libc::c_uint; - if c < 0xe0i32 as libc::c_uint { - utf8total = 2i32 as libc::c_uint; - ucs4 = (c & 0x1fi32 as libc::c_uint) as libc::c_ulong - } else if c < 0xf0i32 as libc::c_uint { - utf8total = 3i32 as libc::c_uint; - ucs4 = (c & 0xfi32 as libc::c_uint) as libc::c_ulong - } else { - utf8total = 4i32 as libc::c_uint; - ucs4 = (c & 0x3i32 as libc::c_uint) as libc::c_ulong - } - continue; - } - utf8total = 0i32 as libc::c_uint; - loop { - if ucs4 >= 0x10000 { - ucs4 = ucs4.wrapping_sub(0x10000); - bitbuf = bitbuf << 16 | (ucs4 >> 10).wrapping_add(0xd800); - ucs4 = (ucs4 & 0x3ff).wrapping_add(0xdc00); - utf16flag = 1i32 as libc::c_uint - } else { - bitbuf = bitbuf << 16 | ucs4; - utf16flag = 0i32 as libc::c_uint - } - bitstogo = bitstogo.wrapping_add(16i32 as libc::c_uint); - while bitstogo >= 6i32 as libc::c_uint { - bitstogo = bitstogo.wrapping_sub(6i32 as libc::c_uint); - let fresh14 = dst; - dst = dst.offset(1); - *fresh14 = BASE64CHARS[(if 0 != bitstogo { - bitbuf >> bitstogo - } else { - bitbuf - } & 0x3f) as usize] - } - if !(0 != utf16flag) { - break; - } - } - } + if to_check.is_empty() { + return false; } - if 0 != utf7mode { - if 0 != bitstogo { - let fresh15 = dst; - dst = dst.offset(1); - *fresh15 = BASE64CHARS - [(bitbuf << (6i32 as libc::c_uint).wrapping_sub(bitstogo) & 0x3f) as usize] - } - let fresh16 = dst; - dst = dst.offset(1); - *fresh16 = '-' as i32 as libc::c_char - } - *dst = '\u{0}' as i32 as libc::c_char; - res + to_check.chars().any(|c| { + !(c.is_ascii_alphanumeric() + || c == '-' + || c == '_' + || c == '_' + || c == '.' + || c == '~' + || c == '%') + }) } -/* ****************************************************************************** - * Encode/decode modified UTF-7 as needed for IMAP, see RFC 2192 - ******************************************************************************/ +const EXT_ASCII_ST: &AsciiSet = &CONTROLS + .add(b' ') + .add(b'-') + .add(b'_') + .add(b'.') + .add(b'~') + .add(b'%'); -// UTF7 modified base64 alphabet -#[cfg(test)] -static mut BASE64CHARS: [libc::c_char; 65] = [ - 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, 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, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 44, 0, -]; - -#[cfg(test)] -unsafe fn dc_decode_modified_utf7( - to_decode: *const libc::c_char, - change_spaces: libc::c_int, -) -> *mut libc::c_char { - let mut c: libc::c_uint; - let mut i: libc::c_uint; - let mut bitcount: libc::c_uint; - let mut ucs4: libc::c_ulong; - let mut utf16: libc::c_ulong; - let mut bitbuf: libc::c_ulong; - let mut base64: [libc::c_uchar; 256] = [0; 256]; - let mut src: *const libc::c_char; - let mut dst: *mut libc::c_char; - let res: *mut libc::c_char; - if to_decode.is_null() { - return dc_strdup(b"\x00" as *const u8 as *const libc::c_char); - } - res = malloc(4usize.wrapping_mul(strlen(to_decode)).wrapping_add(1)) as *mut libc::c_char; - dst = res; - src = to_decode; - assert!(!dst.is_null()); - - libc::memset( - base64.as_mut_ptr() as *mut libc::c_void, - 64, - ::std::mem::size_of::<[libc::c_uchar; 256]>(), - ); - i = 0i32 as libc::c_uint; - while (i as libc::c_ulong) < ::std::mem::size_of::<[libc::c_char; 65]>() as libc::c_ulong { - base64[BASE64CHARS[i as usize] as libc::c_uint as usize] = i as libc::c_uchar; - i = i.wrapping_add(1) - } - while *src as libc::c_int != '\u{0}' as i32 { - let fresh17 = src; - src = src.offset(1); - c = *fresh17 as libc::c_uint; - if c != '&' as i32 as libc::c_uint || *src as libc::c_int == '-' as i32 { - if 0 != change_spaces && c == '_' as i32 as libc::c_uint { - let fresh18 = dst; - dst = dst.offset(1); - *fresh18 = ' ' as i32 as libc::c_char - } else { - let fresh19 = dst; - dst = dst.offset(1); - *fresh19 = c as libc::c_char - } - if c == '&' as i32 as libc::c_uint { - src = src.offset(1isize) - } - } else { - bitbuf = 0; - bitcount = 0i32 as libc::c_uint; - ucs4 = 0; - loop { - c = base64[*src as libc::c_uchar as usize] as libc::c_uint; - if !(c != 64i32 as libc::c_uint) { - break; - } - src = src.offset(1isize); - bitbuf = bitbuf << 6i32 | c as libc::c_ulong; - bitcount = bitcount.wrapping_add(6i32 as libc::c_uint); - // enough bits for a UTF-16 character? - if !(bitcount >= 16i32 as libc::c_uint) { - continue; - } - bitcount = bitcount.wrapping_sub(16i32 as libc::c_uint); - utf16 = if 0 != bitcount { - bitbuf >> bitcount - } else { - bitbuf - } & 0xffff; - - // convert UTF16 to UCS4 - if utf16 >= 0xd800 && utf16 <= 0xdbff { - ucs4 = utf16.wrapping_sub(0xd800) << 10i32 - } else { - if utf16 >= 0xdc00 && utf16 <= 0xdfff { - ucs4 = ucs4.wrapping_add(utf16.wrapping_sub(0xdc00).wrapping_add(0x10000)) - } else { - ucs4 = utf16 - } - if ucs4 <= 0x7f { - *dst.offset(0isize) = ucs4 as libc::c_char; - dst = dst.offset(1isize) - } else if ucs4 <= 0x7ff { - *dst.offset(0isize) = (0xc0 | ucs4 >> 6i32) as libc::c_char; - *dst.offset(1isize) = (0x80 | ucs4 & 0x3f) as libc::c_char; - dst = dst.offset(2isize) - } else if ucs4 <= 0xffff { - *dst.offset(0isize) = (0xe0 | ucs4 >> 12i32) as libc::c_char; - *dst.offset(1isize) = (0x80 | ucs4 >> 6i32 & 0x3f) as libc::c_char; - *dst.offset(2isize) = (0x80 | ucs4 & 0x3f) as libc::c_char; - dst = dst.offset(3isize) - } else { - *dst.offset(0isize) = (0xf0 | ucs4 >> 18i32) as libc::c_char; - *dst.offset(1isize) = (0x80 | ucs4 >> 12i32 & 0x3f) as libc::c_char; - *dst.offset(2isize) = (0x80 | ucs4 >> 6i32 & 0x3f) as libc::c_char; - *dst.offset(3isize) = (0x80 | ucs4 & 0x3f) as libc::c_char; - dst = dst.offset(4isize) - } - } - } - if *src as libc::c_int == '-' as i32 { - src = src.offset(1isize) - } - } - } - *dst = '\u{0}' as i32 as libc::c_char; - - res +/// Encode an UTF-8 string to the extended header format. +pub fn dc_encode_ext_header(to_encode: impl AsRef) -> String { + let encoded = utf8_percent_encode(to_encode.as_ref(), &EXT_ASCII_ST); + format!("utf-8''{}", encoded) } -pub unsafe fn dc_needs_ext_header(mut to_check: *const libc::c_char) -> bool { - if !to_check.is_null() { - while 0 != *to_check { - if 0 == isalnum(*to_check as libc::c_int) - && *to_check as libc::c_int != '-' as i32 - && *to_check as libc::c_int != '_' as i32 - && *to_check as libc::c_int != '.' as i32 - && *to_check as libc::c_int != '~' as i32 - { - return true; - } - to_check = to_check.offset(1isize) - } - } - - false -} - -pub unsafe fn dc_encode_ext_header(to_encode: *const libc::c_char) -> *mut libc::c_char { - let mut pstr: *const libc::c_char = to_encode; - if to_encode.is_null() { - return dc_strdup(b"utf-8\'\'\x00" as *const u8 as *const libc::c_char); - } - let buf: *mut libc::c_char = malloc( - strlen(b"utf-8\'\'\x00" as *const u8 as *const libc::c_char) - .wrapping_add(strlen(to_encode).wrapping_mul(3)) - .wrapping_add(1), - ) as *mut libc::c_char; - assert!(!buf.is_null()); - - let mut pbuf: *mut libc::c_char = buf; - strcpy(pbuf, b"utf-8\'\'\x00" as *const u8 as *const libc::c_char); - pbuf = pbuf.offset(strlen(pbuf) as isize); - while 0 != *pstr { - if 0 != isalnum(*pstr as libc::c_int) - || *pstr as libc::c_int == '-' as i32 - || *pstr as libc::c_int == '_' as i32 - || *pstr as libc::c_int == '.' as i32 - || *pstr as libc::c_int == '~' as i32 - { - let fresh20 = pbuf; - pbuf = pbuf.offset(1); - *fresh20 = *pstr - } else { - let fresh21 = pbuf; - pbuf = pbuf.offset(1); - *fresh21 = '%' as i32 as libc::c_char; - let fresh22 = pbuf; - pbuf = pbuf.offset(1); - *fresh22 = int_2_uppercase_hex((*pstr as libc::c_int >> 4i32) as libc::c_char); - let fresh23 = pbuf; - pbuf = pbuf.offset(1); - *fresh23 = int_2_uppercase_hex((*pstr as libc::c_int & 15i32) as libc::c_char) - } - pstr = pstr.offset(1isize) - } - *pbuf = '\u{0}' as i32 as libc::c_char; - - buf -} - -pub unsafe fn dc_decode_ext_header(to_decode: *const libc::c_char) -> *mut libc::c_char { - let mut decoded: *mut libc::c_char = ptr::null_mut(); - let mut charset: *mut libc::c_char = ptr::null_mut(); - let mut p2: *const libc::c_char; - if !to_decode.is_null() { - // get char set - p2 = strchr(to_decode, '\'' as i32); - if !(p2.is_null() || p2 == to_decode) { - /*no empty charset allowed*/ - charset = - dc_null_terminate(to_decode, p2.wrapping_offset_from(to_decode) as libc::c_int); - p2 = p2.offset(1isize); +/// Decode an extended-header-format strings to UTF-8. +pub fn dc_decode_ext_header(to_decode: &[u8]) -> Cow { + if let Some(index) = bytes!(b'\'').find(to_decode) { + let (charset, rest) = to_decode.split_at(index); + if !charset.is_empty() { // skip language - p2 = strchr(p2, '\'' as i32); - if !p2.is_null() { - p2 = p2.offset(1isize); - decoded = dc_urldecode(p2); - if !charset.is_null() - && strcmp(charset, b"utf-8\x00" as *const u8 as *const libc::c_char) != 0i32 - && strcmp(charset, b"UTF-8\x00" as *const u8 as *const libc::c_char) != 0i32 - { - if let Some(encoding) = - Charset::for_label(CStr::from_ptr(charset).to_str().unwrap().as_bytes()) - { - let data = - std::slice::from_raw_parts(decoded as *const u8, strlen(decoded)); + if let Some(index2) = bytes!(b'\'').find(&rest[1..]) { + let decoded = percent_decode(&rest[index2 + 2..]); - let (res, _, _) = encoding.decode(data); - free(decoded as *mut _); - let r = std::ffi::CString::new(res.as_bytes()).unwrap(); - decoded = dc_strdup(r.as_ptr()); + if charset != b"utf-8" && charset != b"UTF-8" { + if let Some(encoding) = Charset::for_label(charset) { + let bytes = decoded.collect::>(); + let (res, _, _) = encoding.decode(&bytes); + return Cow::Owned(res.into_owned()); + } else { + return decoded.decode_utf8_lossy(); } + } else { + return decoded.decode_utf8_lossy(); } } } } - free(charset as *mut libc::c_void); - if !decoded.is_null() { - decoded - } else { - dc_strdup(to_decode) - } + + String::from_utf8_lossy(to_decode) } unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) { @@ -672,16 +295,8 @@ unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) { #[cfg(test)] mod tests { use super::*; - use percent_encoding::{percent_encode, NON_ALPHANUMERIC}; use std::ffi::CStr; - #[test] - fn test_isalnum() { - assert_eq!(isalnum(0), 0); - assert_eq!(isalnum('5' as libc::c_int), 1); - assert_eq!(isalnum('Q' as libc::c_int), 1); - } - #[test] fn test_dc_decode_header_words() { unsafe { @@ -742,99 +357,43 @@ mod tests { #[test] fn test_dc_encode_ext_header() { - unsafe { - let mut buf1 = dc_encode_ext_header( - b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char, - ); - assert_eq!( - CStr::from_ptr(buf1).to_str().unwrap(), - "utf-8\'\'Bj%C3%B6rn%20Petersen" - ); - let buf2 = dc_decode_ext_header(buf1); - assert_eq!( - strcmp( - buf2, - b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char, - ), - 0 - ); - free(buf1 as *mut libc::c_void); - free(buf2 as *mut libc::c_void); + let buf1 = dc_encode_ext_header("Björn Petersen"); + assert_eq!(&buf1, "utf-8\'\'Bj%C3%B6rn%20Petersen"); + let buf2 = dc_decode_ext_header(buf1.as_bytes()); + assert_eq!(&buf2, "Björn Petersen",); - buf1 = dc_decode_ext_header( - b"iso-8859-1\'en\'%A3%20rates\x00" as *const u8 as *const libc::c_char, - ); - assert_eq!( - strcmp( - buf1, - b"\xc2\xa3 rates\x00" as *const u8 as *const libc::c_char, - ), - 0 - ); - free(buf1 as *mut libc::c_void); + let buf1 = dc_decode_ext_header(b"iso-8859-1\'en\'%A3%20rates"); + assert_eq!(buf1, "£ rates",); - buf1 = dc_decode_ext_header(b"wrong\'format\x00" as *const u8 as *const libc::c_char); - assert_eq!( - strcmp( - buf1, - b"wrong\'format\x00" as *const u8 as *const libc::c_char, - ), - 0 - ); - free(buf1 as *mut libc::c_void); + let buf1 = dc_decode_ext_header(b"wrong\'format"); + assert_eq!(buf1, "wrong\'format",); - buf1 = dc_decode_ext_header(b"\'\'\x00" as *const u8 as *const libc::c_char); - assert_eq!( - strcmp(buf1, b"\'\'\x00" as *const u8 as *const libc::c_char), - 0 - ); - free(buf1 as *mut libc::c_void); + let buf1 = dc_decode_ext_header(b"\'\'"); + assert_eq!(buf1, "\'\'"); - buf1 = dc_decode_ext_header(b"x\'\'\x00" as *const u8 as *const libc::c_char); - assert_eq!(strcmp(buf1, b"\x00" as *const u8 as *const libc::c_char), 0); - free(buf1 as *mut libc::c_void); + let buf1 = dc_decode_ext_header(b"x\'\'"); + assert_eq!(buf1, ""); - buf1 = dc_decode_ext_header(b"\'\x00" as *const u8 as *const libc::c_char); - assert_eq!( - strcmp(buf1, b"\'\x00" as *const u8 as *const libc::c_char), - 0 - ); - free(buf1 as *mut libc::c_void); + let buf1 = dc_decode_ext_header(b"\'"); + assert_eq!(buf1, "\'"); - buf1 = dc_decode_ext_header(b"\x00" as *const u8 as *const libc::c_char); - assert_eq!(strcmp(buf1, b"\x00" as *const u8 as *const libc::c_char), 0); - free(buf1 as *mut libc::c_void); - } + let buf1 = dc_decode_ext_header(b""); + assert_eq!(buf1, ""); + + // regressions + assert_eq!( + dc_decode_ext_header(dc_encode_ext_header("%0A").as_bytes()), + "%0A" + ); } #[test] fn test_dc_needs_ext_header() { - unsafe { - assert_eq!( - dc_needs_ext_header(b"Bj\xc3\xb6rn\x00" as *const u8 as *const libc::c_char), - true - ); - assert_eq!( - dc_needs_ext_header(b"Bjoern\x00" as *const u8 as *const libc::c_char), - false - ); - assert_eq!( - dc_needs_ext_header(b"\x00" as *const u8 as *const libc::c_char), - false - ); - assert_eq!( - dc_needs_ext_header(b" \x00" as *const u8 as *const libc::c_char), - true - ); - assert_eq!( - dc_needs_ext_header(b"a b\x00" as *const u8 as *const libc::c_char), - true - ); - assert_eq!( - dc_needs_ext_header(0 as *const u8 as *const libc::c_char), - false - ); - } + assert_eq!(dc_needs_ext_header("Björn"), true); + assert_eq!(dc_needs_ext_header("Bjoern"), false); + assert_eq!(dc_needs_ext_header(""), false); + assert_eq!(dc_needs_ext_header(" "), true); + assert_eq!(dc_needs_ext_header("a b"), true); } #[test] @@ -849,69 +408,20 @@ mod tests { assert_eq!(to_string(hex.as_ptr() as *const _), "=3A"); } - #[test] - fn test_dc_urlencode_urldecode() { - unsafe { - let buf1 = percent_encode(b"Bj\xc3\xb6rn Petersen", NON_ALPHANUMERIC) - .to_string() - .strdup(); + use proptest::prelude::*; - assert_eq!( - CStr::from_ptr(buf1 as *const libc::c_char) - .to_str() - .unwrap(), - "Bj%C3%B6rn%20Petersen" - ); - - let buf2 = dc_urldecode(buf1); - - assert_eq!( - strcmp( - buf2, - b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char - ), - 0 - ); - - free(buf1 as *mut libc::c_void); - free(buf2 as *mut libc::c_void); + proptest! { + #[test] + fn test_ext_header_roundtrip(buf: String) { + let encoded = dc_encode_ext_header(&buf); + let decoded = dc_decode_ext_header(encoded.as_bytes()); + assert_eq!(buf, decoded); } - } - #[test] - fn test_dc_encode_decode_modified_utf7() { - unsafe { - let buf1 = dc_encode_modified_utf7( - b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char, - 1, - ); - - assert_eq!( - CStr::from_ptr(buf1 as *const libc::c_char) - .to_str() - .unwrap(), - "Bj&APY-rn_Petersen" - ); - - let buf2 = dc_decode_modified_utf7(buf1, 1); - - assert_eq!( - strcmp( - buf2, - b"Bj\xc3\xb6rn Petersen\x00" as *const u8 as *const libc::c_char - ), - 0 - ); - - free(buf1 as *mut libc::c_void); - free(buf2 as *mut libc::c_void); + #[test] + fn test_ext_header_decode_anything(buf: Vec) { + // make sure this never panics + let _decoded = dc_decode_ext_header(&buf); } } - #[test] - fn test_hex_to_int() { - assert_eq!(hex_2_int(b'A' as libc::c_char), 10); - assert_eq!(hex_2_int(b'a' as libc::c_char), 10); - assert_eq!(hex_2_int(b'4' as libc::c_char), 4); - assert_eq!(hex_2_int(b'K' as libc::c_char), 20); - } } diff --git a/src/lib.rs b/src/lib.rs index c3a8cb079..4fb0049b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,8 @@ extern crate rusqlite; extern crate strum; #[macro_use] extern crate strum_macros; +#[macro_use] +extern crate jetscii; #[macro_use] mod log;