1pub const VERSION: &str = env!("CARGO_PKG_VERSION");
3
4pub mod error;
5pub mod types;
6
7pub use error::{Error, Result};
8pub use types::*;
9
10pub fn sanitize_for_proto(s: &str) -> String {
16 s.replace('\0', "")
17}
18
19fn sanitize_author_field(s: &str) -> String {
24 s.chars()
25 .filter(|c| !matches!(c, '\0' | '\n' | '\r' | '<' | '>'))
26 .collect()
27}
28
29pub fn resolve_author(name: &str, email: &str, agent: &str) -> (String, String) {
34 let safe_agent = sanitize_author_field(agent);
35 let sanitized_name = sanitize_author_field(name);
36 let effective_name = if sanitized_name.is_empty() {
37 safe_agent.clone()
38 } else {
39 sanitized_name
40 };
41 let sanitized_email = sanitize_author_field(email);
42 let effective_email = if sanitized_email.is_empty() {
43 format!("{}@dkod.dev", safe_agent)
44 } else {
45 sanitized_email
46 };
47 (effective_name, effective_email)
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn sanitize_for_proto_strips_null_bytes() {
56 assert_eq!(sanitize_for_proto("hello\0world"), "helloworld");
57 assert_eq!(sanitize_for_proto("\0\0"), "");
58 assert_eq!(sanitize_for_proto("clean"), "clean");
59 }
60
61 #[test]
62 fn sanitize_for_proto_preserves_valid_utf8() {
63 assert_eq!(sanitize_for_proto("fn résumé()"), "fn résumé()");
64 assert_eq!(sanitize_for_proto("日本語"), "日本語");
65 assert_eq!(sanitize_for_proto("bad\u{FFFD}char"), "bad\u{FFFD}char");
66 }
67
68 #[test]
69 fn resolve_author_uses_supplied_values() {
70 let (name, email) = resolve_author("Alice", "alice@example.com", "agent-1");
71 assert_eq!(name, "Alice");
72 assert_eq!(email, "alice@example.com");
73 }
74
75 #[test]
76 fn resolve_author_falls_back_to_agent() {
77 let (name, email) = resolve_author("", "", "agent-1");
78 assert_eq!(name, "agent-1");
79 assert_eq!(email, "agent-1@dkod.dev");
80 }
81
82 #[test]
83 fn resolve_author_sanitizes_newlines_and_nulls() {
84 let (name, email) = resolve_author("Al\nice\0", "al\rice@\nex.com", "agent-1");
85 assert_eq!(name, "Alice");
86 assert_eq!(email, "alice@ex.com");
87 }
88
89 #[test]
90 fn resolve_author_falls_back_when_input_is_only_stripped_chars() {
91 let (name, email) = resolve_author("\n", "\r\0", "agent-1");
92 assert_eq!(name, "agent-1");
93 assert_eq!(email, "agent-1@dkod.dev");
94 }
95
96 #[test]
97 fn resolve_author_strips_angle_brackets() {
98 let (name, email) = resolve_author("Alice <hacker>", "a<b>c@ex.com", "agent-1");
99 assert_eq!(name, "Alice hacker");
100 assert_eq!(email, "abc@ex.com");
101 }
102}