1#![warn(missing_docs, rustdoc::missing_crate_level_docs)]
33#![deny(unsafe_code)]
34
35pub mod error;
36pub mod message;
37pub mod command;
38pub mod capabilities;
39pub mod sasl;
40pub mod validation;
41pub mod replies;
42pub mod iron;
43pub mod admin;
44
45#[cfg(feature = "bleeding-edge")]
46pub mod bleeding_edge;
47
48pub use error::{IronError, Result};
50pub use message::IrcMessage;
51pub use command::Command;
52pub use capabilities::{Capability, CapabilitySet, CapabilityHandler};
53pub use replies::Reply;
54pub use utils::ChannelType;
55pub use iron::{IronSession, IronVersion, IronNegotiationResult, IronChannelHandler, ChannelJoinResult, IronChannelError};
56pub use admin::{AdminOperation, MemberOperation, BanOperation, KeyOperation, MemberRole, ChannelMode,
57 ChannelSettings, AdminResult, ChannelAdmin, Permission};
58
59#[cfg(feature = "bleeding-edge")]
60pub use bleeding_edge::{MessageReply, MessageReaction, ReactionAction};
61
62pub mod constants {
64 pub const MAX_MESSAGE_LENGTH: usize = 512;
66
67 pub const MAX_TAG_LENGTH: usize = 8191;
69
70 pub const MAX_PARAMS: usize = 15;
72
73 pub const MAX_CAPABILITY_NAME_LENGTH: usize = 64;
75
76 pub const MAX_NICK_LENGTH: usize = 32;
78
79 pub const MAX_CHANNEL_LENGTH: usize = 50;
81
82 pub const DEFAULT_IRC_PORT: u16 = 6667;
84
85 pub const DEFAULT_IRCS_PORT: u16 = 6697;
87}
88
89pub mod utils {
91 use crate::constants::*;
92
93 #[derive(Debug, Clone, PartialEq, Eq)]
95 pub enum ChannelType {
96 IrcGlobal,
98 IrcLocal,
100 LegionEncrypted,
102 Invalid,
104 }
105
106 pub fn get_channel_type(channel: &str) -> ChannelType {
108 if channel.is_empty() {
109 return ChannelType::Invalid;
110 }
111
112 match channel.chars().next().unwrap() {
113 '#' => ChannelType::IrcGlobal,
114 '&' => ChannelType::IrcLocal,
115 '!' => ChannelType::LegionEncrypted,
116 _ => ChannelType::Invalid,
117 }
118 }
119
120 pub fn is_legion_encrypted_channel(channel: &str) -> bool {
122 matches!(get_channel_type(channel), ChannelType::LegionEncrypted)
123 }
124
125 #[deprecated(note = "Use is_legion_encrypted_channel instead")]
127 pub fn is_iron_encrypted_channel(channel: &str) -> bool {
128 is_legion_encrypted_channel(channel)
129 }
130
131 pub fn is_standard_irc_channel(channel: &str) -> bool {
133 matches!(get_channel_type(channel), ChannelType::IrcGlobal | ChannelType::IrcLocal)
134 }
135
136 pub fn is_valid_nick(nick: &str) -> bool {
138 if nick.is_empty() || nick.len() > MAX_NICK_LENGTH {
139 return false;
140 }
141
142 let first = nick.chars().next().unwrap();
144 if !first.is_ascii_alphabetic() && !matches!(first, '[' | ']' | '\\' | '`' | '_' | '^' | '{' | '|' | '}') {
145 return false;
146 }
147
148 nick.chars().skip(1).all(|c| {
150 c.is_ascii_alphanumeric() || matches!(c, '[' | ']' | '\\' | '`' | '_' | '^' | '{' | '|' | '}' | '-')
151 })
152 }
153
154 pub fn is_valid_channel(channel: &str) -> bool {
156 if channel.is_empty() || channel.len() > MAX_CHANNEL_LENGTH {
157 return false;
158 }
159
160 if !channel.starts_with('#') && !channel.starts_with('&') {
162 return false;
163 }
164
165 !channel.chars().any(|c| c.is_control() || c == ' ' || c == ',' || c == '\x07')
167 }
168
169 pub fn is_valid_legion_channel(channel: &str) -> bool {
171 if channel.is_empty() || channel.len() > MAX_CHANNEL_LENGTH {
172 return false;
173 }
174
175 if !channel.starts_with('!') {
177 return false;
178 }
179
180 !channel.chars().any(|c| c.is_control() || c == ' ' || c == ',' || c == '\x07')
182 }
183
184 #[deprecated(note = "Use is_valid_legion_channel instead")]
186 pub fn is_valid_iron_channel(channel: &str) -> bool {
187 is_valid_legion_channel(channel)
188 }
189
190 pub fn is_valid_any_channel(channel: &str) -> bool {
192 is_valid_channel(channel) || is_valid_legion_channel(channel)
193 }
194
195 pub fn escape_message(text: &str) -> String {
197 text.replace('\r', "")
198 .replace('\n', " ")
199 .replace('\0', "")
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use super::utils::*;
207
208 #[test]
209 fn test_valid_nicks() {
210 assert!(is_valid_nick("Alice"));
211 assert!(is_valid_nick("Bob123"));
212 assert!(is_valid_nick("user_name"));
213 assert!(is_valid_nick("[Bot]"));
214 assert!(is_valid_nick("test-user"));
215 }
216
217 #[test]
218 fn test_invalid_nicks() {
219 assert!(!is_valid_nick(""));
220 assert!(!is_valid_nick("123user")); assert!(!is_valid_nick("user name")); assert!(!is_valid_nick(&"a".repeat(50))); }
224
225 #[test]
226 fn test_valid_channels() {
227 assert!(is_valid_channel("#general"));
228 assert!(is_valid_channel("&local"));
229 assert!(is_valid_channel("#test-channel"));
230 assert!(is_valid_channel("#channel123"));
231 }
232
233 #[test]
234 fn test_invalid_channels() {
235 assert!(!is_valid_channel(""));
236 assert!(!is_valid_channel("general")); assert!(!is_valid_channel("#test channel")); assert!(!is_valid_channel("#test,channel")); assert!(!is_valid_channel(&format!("#{}", "a".repeat(60)))); }
241
242 #[test]
243 fn test_valid_legion_channels() {
244 assert!(is_valid_legion_channel("!encrypted"));
245 assert!(is_valid_legion_channel("!secure-chat"));
246 assert!(is_valid_legion_channel("!room123"));
247 assert!(is_valid_legion_channel("!test_channel"));
248 }
249
250 #[test]
251 fn test_invalid_legion_channels() {
252 assert!(!is_valid_legion_channel(""));
253 assert!(!is_valid_legion_channel("encrypted")); assert!(!is_valid_legion_channel("#encrypted")); assert!(!is_valid_legion_channel("!test channel")); assert!(!is_valid_legion_channel("!test,channel")); assert!(!is_valid_legion_channel(&format!("!{}", "a".repeat(60)))); }
259
260 #[test]
261 fn test_backward_compatibility_iron_channels() {
262 #[allow(deprecated)]
264 fn test_old_function() {
265 assert!(is_valid_iron_channel("!encrypted"));
266 assert!(!is_valid_iron_channel("#encrypted"));
267 }
268 test_old_function();
269 }
270
271 #[test]
272 fn test_channel_type_detection() {
273 use utils::*;
274
275 assert_eq!(get_channel_type("#general"), ChannelType::IrcGlobal);
276 assert_eq!(get_channel_type("&local"), ChannelType::IrcLocal);
277 assert_eq!(get_channel_type("!encrypted"), ChannelType::LegionEncrypted);
278 assert_eq!(get_channel_type("invalid"), ChannelType::Invalid);
279 assert_eq!(get_channel_type(""), ChannelType::Invalid);
280
281 assert!(is_legion_encrypted_channel("!encrypted"));
282 assert!(!is_legion_encrypted_channel("#general"));
283
284 #[allow(deprecated)]
286 {
287 assert!(is_iron_encrypted_channel("!encrypted"));
288 assert!(!is_iron_encrypted_channel("#general"));
289 }
290
291 assert!(is_standard_irc_channel("#general"));
292 assert!(is_standard_irc_channel("&local"));
293 assert!(!is_standard_irc_channel("!encrypted"));
294 }
295}