Mercurial > crates > nonstick
comparison src/conv.rs @ 93:efc2b56c8928
Remove undefined behavior per MIRI.
This replaces a bunch of raw pointers with NonNull and removes all the
undefined behavior that we can find with MIRI.
We also remove the `SecureString` dependency (since it doesn't work with MIRI,
and because it's not really necessary).
| author | Paul Fisher <paul@pfish.zone> |
|---|---|
| date | Mon, 23 Jun 2025 13:02:58 -0400 |
| parents | 05291b601f0a |
| children | db167f96ba46 |
comparison
equal
deleted
inserted
replaced
| 92:5ddbcada30f2 | 93:efc2b56c8928 |
|---|---|
| 2 | 2 |
| 3 // Temporarily allowed until we get the actual conversation functions hooked up. | 3 // Temporarily allowed until we get the actual conversation functions hooked up. |
| 4 #![allow(dead_code)] | 4 #![allow(dead_code)] |
| 5 | 5 |
| 6 use crate::constants::{ErrorCode, Result}; | 6 use crate::constants::{ErrorCode, Result}; |
| 7 use secure_string::SecureString; | |
| 8 use std::cell::Cell; | 7 use std::cell::Cell; |
| 9 use std::fmt; | 8 use std::fmt; |
| 9 use std::fmt::Debug; | |
| 10 use std::result::Result as StdResult; | 10 use std::result::Result as StdResult; |
| 11 | 11 |
| 12 /// The types of message and request that can be sent to a user. | 12 /// The types of message and request that can be sent to a user. |
| 13 /// | 13 /// |
| 14 /// The data within each enum value is the prompt (or other information) | 14 /// The data within each enum value is the prompt (or other information) |
| 120 | 120 |
| 121 q_and_a!( | 121 q_and_a!( |
| 122 /// A Q&A that asks the user for text and does not show it while typing. | 122 /// A Q&A that asks the user for text and does not show it while typing. |
| 123 /// | 123 /// |
| 124 /// In other words, a password entry prompt. | 124 /// In other words, a password entry prompt. |
| 125 MaskedQAndA<'a, Q=&'a str, A=SecureString>, | 125 MaskedQAndA<'a, Q=&'a str, A=String>, |
| 126 Message::MaskedPrompt | 126 Message::MaskedPrompt |
| 127 ); | 127 ); |
| 128 | 128 |
| 129 q_and_a!( | 129 q_and_a!( |
| 130 /// A standard Q&A prompt that asks the user for text. | 130 /// A standard Q&A prompt that asks the user for text. |
| 282 /// | 282 /// |
| 283 /// For example, to use a `Conversation` as a `SimpleConversation`: | 283 /// For example, to use a `Conversation` as a `SimpleConversation`: |
| 284 /// | 284 /// |
| 285 /// ``` | 285 /// ``` |
| 286 /// # use nonstick::{Conversation, Result}; | 286 /// # use nonstick::{Conversation, Result}; |
| 287 /// # use secure_string::SecureString; | |
| 288 /// // Bring this trait into scope to get `masked_prompt`, among others. | 287 /// // Bring this trait into scope to get `masked_prompt`, among others. |
| 289 /// use nonstick::SimpleConversation; | 288 /// use nonstick::SimpleConversation; |
| 290 /// | 289 /// |
| 291 /// fn ask_for_token(convo: &mut impl Conversation) -> Result<SecureString> { | 290 /// fn ask_for_token(convo: &mut impl Conversation) -> Result<String> { |
| 292 /// convo.masked_prompt("enter your one-time token") | 291 /// convo.masked_prompt("enter your one-time token") |
| 293 /// } | 292 /// } |
| 294 /// ``` | 293 /// ``` |
| 295 /// | 294 /// |
| 296 /// or to use a `SimpleConversation` as a `Conversation`: | 295 /// or to use a `SimpleConversation` as a `Conversation`: |
| 297 /// | 296 /// |
| 298 /// ``` | 297 /// ``` |
| 299 /// use nonstick::{Conversation, SimpleConversation}; | 298 /// use nonstick::{Conversation, SimpleConversation}; |
| 300 /// use secure_string::SecureString; | |
| 301 /// # use nonstick::{BinaryData, Result}; | 299 /// # use nonstick::{BinaryData, Result}; |
| 302 /// mod some_library { | 300 /// mod some_library { |
| 303 /// # use nonstick::Conversation; | 301 /// # use nonstick::Conversation; |
| 304 /// pub fn get_auth_data(conv: &mut impl Conversation) { /* ... */ | 302 /// pub fn get_auth_data(conv: &mut impl Conversation) { /* ... */ |
| 305 /// } | 303 /// } |
| 312 /// // ... | 310 /// // ... |
| 313 /// # fn prompt(&mut self, request: &str) -> Result<String> { | 311 /// # fn prompt(&mut self, request: &str) -> Result<String> { |
| 314 /// # todo!() | 312 /// # todo!() |
| 315 /// # } | 313 /// # } |
| 316 /// # | 314 /// # |
| 317 /// # fn masked_prompt(&mut self, request: &str) -> Result<SecureString> { | 315 /// # fn masked_prompt(&mut self, request: &str) -> Result<String> { |
| 318 /// # todo!() | 316 /// # todo!() |
| 319 /// # } | 317 /// # } |
| 320 /// # | 318 /// # |
| 321 /// # fn error_msg(&mut self, message: &str) { | 319 /// # fn error_msg(&mut self, message: &str) { |
| 322 /// # todo!() | 320 /// # todo!() |
| 353 Demux(self) | 351 Demux(self) |
| 354 } | 352 } |
| 355 /// Prompts the user for something. | 353 /// Prompts the user for something. |
| 356 fn prompt(&mut self, request: &str) -> Result<String>; | 354 fn prompt(&mut self, request: &str) -> Result<String>; |
| 357 /// Prompts the user for something, but hides what the user types. | 355 /// Prompts the user for something, but hides what the user types. |
| 358 fn masked_prompt(&mut self, request: &str) -> Result<SecureString>; | 356 fn masked_prompt(&mut self, request: &str) -> Result<String>; |
| 359 /// Alerts the user to an error. | 357 /// Alerts the user to an error. |
| 360 fn error_msg(&mut self, message: &str); | 358 fn error_msg(&mut self, message: &str); |
| 361 /// Sends an informational message to the user. | 359 /// Sends an informational message to the user. |
| 362 fn info_msg(&mut self, message: &str); | 360 fn info_msg(&mut self, message: &str); |
| 363 /// \[Linux extension] Prompts the user for a yes/no/maybe conditional. | 361 /// \[Linux extension] Prompts the user for a yes/no/maybe conditional. |
| 401 }; | 399 }; |
| 402 } | 400 } |
| 403 | 401 |
| 404 impl<C: Conversation> SimpleConversation for C { | 402 impl<C: Conversation> SimpleConversation for C { |
| 405 conv_fn!(prompt(message: &str) -> String { QAndA }); | 403 conv_fn!(prompt(message: &str) -> String { QAndA }); |
| 406 conv_fn!(masked_prompt(message: &str) -> SecureString { MaskedQAndA } ); | 404 conv_fn!(masked_prompt(message: &str) -> String { MaskedQAndA } ); |
| 407 conv_fn!(error_msg(message: &str) { ErrorMsg }); | 405 conv_fn!(error_msg(message: &str) { ErrorMsg }); |
| 408 conv_fn!(info_msg(message: &str) { InfoMsg }); | 406 conv_fn!(info_msg(message: &str) { InfoMsg }); |
| 409 conv_fn!(radio_prompt(message: &str) -> String { RadioQAndA }); | 407 conv_fn!(radio_prompt(message: &str) -> String { RadioQAndA }); |
| 410 conv_fn!(binary_prompt((data, data_type): (&[u8], u8)) -> BinaryData { BinaryQAndA }); | 408 conv_fn!(binary_prompt((data, data_type): (&[u8], u8)) -> BinaryData { BinaryQAndA }); |
| 411 } | 409 } |
| 462 "what" => Ok("whatwhat".to_owned()), | 460 "what" => Ok("whatwhat".to_owned()), |
| 463 "give_err" => Err(ErrorCode::PermissionDenied), | 461 "give_err" => Err(ErrorCode::PermissionDenied), |
| 464 _ => panic!("unexpected prompt!"), | 462 _ => panic!("unexpected prompt!"), |
| 465 } | 463 } |
| 466 } | 464 } |
| 467 fn masked_prompt(&mut self, request: &str) -> Result<SecureString> { | 465 fn masked_prompt(&mut self, request: &str) -> Result<String> { |
| 468 assert_eq!("reveal", request); | 466 assert_eq!("reveal", request); |
| 469 Ok(SecureString::from("my secrets")) | 467 Ok("my secrets".to_owned()) |
| 470 } | 468 } |
| 471 fn error_msg(&mut self, message: &str) { | 469 fn error_msg(&mut self, message: &str) { |
| 472 self.error_ran = true; | 470 self.error_ran = true; |
| 473 assert_eq!("whoopsie", message); | 471 assert_eq!("whoopsie", message); |
| 474 } | 472 } |
| 505 info.message(), | 503 info.message(), |
| 506 has_err.message(), | 504 has_err.message(), |
| 507 ]); | 505 ]); |
| 508 | 506 |
| 509 assert_eq!("whatwhat", what.answer().unwrap()); | 507 assert_eq!("whatwhat", what.answer().unwrap()); |
| 510 assert_eq!(SecureString::from("my secrets"), pass.answer().unwrap()); | 508 assert_eq!("my secrets", pass.answer().unwrap()); |
| 511 assert_eq!(Ok(()), err.answer()); | 509 assert_eq!(Ok(()), err.answer()); |
| 512 assert_eq!(Ok(()), info.answer()); | 510 assert_eq!(Ok(()), info.answer()); |
| 513 assert_eq!(ErrorCode::PermissionDenied, has_err.answer().unwrap_err()); | 511 assert_eq!(ErrorCode::PermissionDenied, has_err.answer().unwrap_err()); |
| 514 assert!(tester.error_ran); | 512 assert!(tester.error_ran); |
| 515 assert!(tester.info_ran); | 513 assert!(tester.info_ran); |
| 570 } | 568 } |
| 571 | 569 |
| 572 let mut tester = MuxTester; | 570 let mut tester = MuxTester; |
| 573 | 571 |
| 574 assert_eq!("answer", tester.prompt("question").unwrap()); | 572 assert_eq!("answer", tester.prompt("question").unwrap()); |
| 575 assert_eq!( | 573 assert_eq!("open sesame", tester.masked_prompt("password!").unwrap()); |
| 576 SecureString::from("open sesame"), | |
| 577 tester.masked_prompt("password!").unwrap() | |
| 578 ); | |
| 579 tester.error_msg("oh no"); | 574 tester.error_msg("oh no"); |
| 580 tester.info_msg("let me tell you"); | 575 tester.info_msg("let me tell you"); |
| 581 // Linux-PAM extensions. Always implemented, but separate for clarity. | 576 // Linux-PAM extensions. Always implemented, but separate for clarity. |
| 582 { | 577 { |
| 583 assert_eq!("yes", tester.radio_prompt("radio?").unwrap()); | 578 assert_eq!("yes", tester.radio_prompt("radio?").unwrap()); |
