Merge upstream v0.16.23

Five conflicts, resolved:

- crates/common/src/auth/authentication.rs: upstream's get_directory_for_token
  and JwtClaims replace extract_jwt_domain; the per-domain directory code
  (DIR-1, DIR-5 to DIR-7) is kept, and the token lookup routes through it.
  The release's one new Enterprise snippet was the body of
  get_directory_for_issuer, which stays returning None: a token naming no
  address gets the server default, as DIR-2 specifies and as v0.16.22 did.
- crates/common/src/manager/application.rs: upstream's rewrite of the tests,
  with the temp directory names renamed again, and the 5(a) notice the
  name-purge change should have added.
- crates/common/src/network/mta.rs: both sides' imports.
- crates/main/Cargo.toml: the AGPL-only license kept, version 0.16.23.
- Cargo.lock: upstream's, with the fork's crates added by Cargo.
This commit is contained in:
2026-09-22 16:57:06 -07:00
82 changed files with 1527 additions and 611 deletions
+86 -19
View File
@@ -16,7 +16,7 @@ pub enum Response<'x, T> {
List(Vec<T>),
Message {
bytes: SliceRange<'x>,
lines: u32,
lines: Option<u32>,
},
Capability {
mechanisms: Vec<Mechanism>,
@@ -54,40 +54,65 @@ impl<'x, T: Display> Response<'x, T> {
buf
}
Response::Message { bytes, lines } => {
let mut buf = Vec::with_capacity(bytes.len() + 10);
buf.extend_from_slice(b"+OK ");
buf.extend_from_slice(bytes.len().to_string().as_bytes());
buf.extend_from_slice(b" octets\r\n");
let mut line_count = 0;
let mut last_byte = 0;
let lines = *lines;
let mut message = Vec::with_capacity(bytes.len() + 16);
let mut octets = 0;
let mut last_byte = b'\n';
let mut in_headers = lines.is_some();
let mut is_blank_line = true;
let mut body_lines = 0;
// Transparency procedure
for &byte in bytes.into_iter() {
// POP3 requires that lines end with CRLF, do this check to ensure that
if byte == b'\n' && last_byte != b'\r' {
buf.push(b'\r');
message.push(b'\r');
octets += 1;
}
if byte == b'.' && last_byte == b'\n' {
buf.push(b'.');
message.push(b'.');
}
buf.push(byte);
message.push(byte);
octets += 1;
last_byte = byte;
if *lines > 0 && byte == b'\n' {
line_count += 1;
if line_count == *lines {
break;
match byte {
b'\n' => {
if in_headers {
in_headers = !is_blank_line;
} else {
body_lines += 1;
}
if !in_headers && lines.is_some_and(|lines| body_lines >= lines) {
break;
}
is_blank_line = true;
}
b'\r' => {}
_ => {
is_blank_line = false;
}
}
}
if last_byte != b'\n' {
buf.extend_from_slice(b"\r\n");
message.extend_from_slice(b"\r\n");
octets += 2;
}
buf.extend_from_slice(b".\r\n");
if in_headers {
message.extend_from_slice(b"\r\n");
octets += 2;
}
message.extend_from_slice(b".\r\n");
let mut buf = Vec::with_capacity(message.len() + 24);
buf.extend_from_slice(b"+OK ");
buf.extend_from_slice(octets.to_string().as_bytes());
buf.extend_from_slice(b" octets\r\n");
buf.extend_from_slice(&message);
buf
}
Response::Capability { mechanisms, stls } => {
@@ -208,9 +233,51 @@ mod tests {
(
Response::Message {
bytes: SliceRange::Split(b"Subject: test\r\n\r\n.\r\n", b"test.\r\n.test\r\na"),
lines: 0,
lines: None,
},
"+OK 35 octets\r\nSubject: test\r\n\r\n..\r\ntest.\r\n..test\r\na\r\n.\r\n",
"+OK 37 octets\r\nSubject: test\r\n\r\n..\r\ntest.\r\n..test\r\na\r\n.\r\n",
),
(
Response::Message {
bytes: SliceRange::Split(b"Subject: test\r\n\r\n.\r\n", b"test.\r\n.test\r\na"),
lines: Some(0),
},
"+OK 17 octets\r\nSubject: test\r\n\r\n.\r\n",
),
(
Response::Message {
bytes: SliceRange::Split(b"Subject: test\r\n\r\n.\r\n", b"test.\r\n.test\r\na"),
lines: Some(2),
},
"+OK 27 octets\r\nSubject: test\r\n\r\n..\r\ntest.\r\n.\r\n",
),
(
Response::Message {
bytes: SliceRange::Split(b"Subject: test\r\n\r\n.\r\n", b"test.\r\n.test\r\na"),
lines: Some(100),
},
"+OK 37 octets\r\nSubject: test\r\n\r\n..\r\ntest.\r\n..test\r\na\r\n.\r\n",
),
(
Response::Message {
bytes: SliceRange::Single(b"Subject: test\n\nbody\n"),
lines: None,
},
"+OK 23 octets\r\nSubject: test\r\n\r\nbody\r\n.\r\n",
),
(
Response::Message {
bytes: SliceRange::Single(b"Subject: test\n\n.leading dot\n"),
lines: Some(1),
},
"+OK 31 octets\r\nSubject: test\r\n\r\n..leading dot\r\n.\r\n",
),
(
Response::Message {
bytes: SliceRange::Single(b".dot\r\nSubject: test\r\n"),
lines: Some(3),
},
"+OK 23 octets\r\n..dot\r\nSubject: test\r\n\r\n.\r\n",
),
] {
assert_eq!(expected, String::from_utf8(cmd.serialize()).unwrap());