Skip to main content

nntp_proxy/protocol/
commands.rs

1//! NNTP command construction helpers
2//!
3//! This module provides functions for constructing well-formed NNTP commands
4//! according to RFC 3977 and RFC 4643.
5
6/// QUIT command (RFC 3977 Section 5.4)
7pub const QUIT: &[u8] = b"QUIT\r\n";
8
9/// DATE command (RFC 3977 Section 7.1)
10///
11/// The DATE command returns the server's current date and time in UTC.
12pub const DATE: &[u8] = b"DATE\r\n";
13
14/// Construct AUTHINFO USER command (RFC 4643 Section 2.3)
15///
16/// Returns a properly formatted AUTHINFO USER command with CRLF termination.
17#[inline]
18pub fn authinfo_user(username: &str) -> String {
19    format!("AUTHINFO USER {}\r\n", username)
20}
21
22/// Construct AUTHINFO PASS command (RFC 4643 Section 2.4)
23///
24/// Returns a properly formatted AUTHINFO PASS command with CRLF termination.
25#[inline]
26pub fn authinfo_pass(password: &str) -> String {
27    format!("AUTHINFO PASS {}\r\n", password)
28}
29
30/// Construct ARTICLE command with message-ID (RFC 3977 Section 6.2.1)
31///
32/// Returns a properly formatted ARTICLE command for retrieving an article by message-ID.
33#[inline]
34pub fn article_by_msgid(msgid: &str) -> String {
35    format!("ARTICLE {}\r\n", msgid)
36}
37
38/// Construct BODY command with message-ID (RFC 3977 Section 6.2.3)
39///
40/// Returns a properly formatted BODY command for retrieving article body by message-ID.
41#[inline]
42pub fn body_by_msgid(msgid: &str) -> String {
43    format!("BODY {}\r\n", msgid)
44}
45
46/// Construct HEAD command with message-ID (RFC 3977 Section 6.2.2)
47///
48/// Returns a properly formatted HEAD command for retrieving article headers by message-ID.
49#[inline]
50pub fn head_by_msgid(msgid: &str) -> String {
51    format!("HEAD {}\r\n", msgid)
52}
53
54/// Construct STAT command with message-ID (RFC 3977 Section 6.2.4)
55///
56/// Returns a properly formatted STAT command for checking article existence by message-ID.
57#[inline]
58pub fn stat_by_msgid(msgid: &str) -> String {
59    format!("STAT {}\r\n", msgid)
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn test_quit_command() {
68        assert_eq!(QUIT, b"QUIT\r\n");
69    }
70
71    #[test]
72    fn test_date_command() {
73        assert_eq!(DATE, b"DATE\r\n");
74    }
75
76    #[test]
77    fn test_authinfo_user() {
78        assert_eq!(authinfo_user("testuser"), "AUTHINFO USER testuser\r\n");
79        assert_eq!(authinfo_user(""), "AUTHINFO USER \r\n");
80    }
81
82    #[test]
83    fn test_authinfo_user_special_chars() {
84        assert_eq!(
85            authinfo_user("user@example.com"),
86            "AUTHINFO USER user@example.com\r\n"
87        );
88    }
89
90    #[test]
91    fn test_authinfo_user_spaces() {
92        assert_eq!(
93            authinfo_user("user with spaces"),
94            "AUTHINFO USER user with spaces\r\n"
95        );
96    }
97
98    #[test]
99    fn test_authinfo_pass() {
100        assert_eq!(authinfo_pass("secret"), "AUTHINFO PASS secret\r\n");
101        assert_eq!(authinfo_pass(""), "AUTHINFO PASS \r\n");
102    }
103
104    #[test]
105    fn test_authinfo_pass_special_chars() {
106        assert_eq!(
107            authinfo_pass("p@ssw0rd!#$"),
108            "AUTHINFO PASS p@ssw0rd!#$\r\n"
109        );
110    }
111
112    #[test]
113    fn test_authinfo_pass_spaces() {
114        assert_eq!(authinfo_pass("pass word"), "AUTHINFO PASS pass word\r\n");
115    }
116
117    #[test]
118    fn test_authinfo_crlf_termination() {
119        assert!(authinfo_user("user").ends_with("\r\n"));
120        assert!(authinfo_pass("pass").ends_with("\r\n"));
121    }
122
123    #[test]
124    fn test_article_by_msgid() {
125        assert_eq!(
126            article_by_msgid("<test@example.com>"),
127            "ARTICLE <test@example.com>\r\n"
128        );
129        assert_eq!(
130            article_by_msgid("<msg123@news.server.com>"),
131            "ARTICLE <msg123@news.server.com>\r\n"
132        );
133    }
134
135    #[test]
136    fn test_body_by_msgid() {
137        assert_eq!(
138            body_by_msgid("<test@example.com>"),
139            "BODY <test@example.com>\r\n"
140        );
141    }
142
143    #[test]
144    fn test_head_by_msgid() {
145        assert_eq!(
146            head_by_msgid("<test@example.com>"),
147            "HEAD <test@example.com>\r\n"
148        );
149    }
150
151    #[test]
152    fn test_stat_by_msgid() {
153        assert_eq!(
154            stat_by_msgid("<test@example.com>"),
155            "STAT <test@example.com>\r\n"
156        );
157    }
158
159    #[test]
160    fn test_commands_end_with_crlf() {
161        assert!(authinfo_user("test").ends_with("\r\n"));
162        assert!(authinfo_pass("test").ends_with("\r\n"));
163        assert!(article_by_msgid("<test@example.com>").ends_with("\r\n"));
164        assert!(body_by_msgid("<test@example.com>").ends_with("\r\n"));
165        assert!(head_by_msgid("<test@example.com>").ends_with("\r\n"));
166        assert!(stat_by_msgid("<test@example.com>").ends_with("\r\n"));
167    }
168}