summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/init.rs4
-rw-r--r--src/main.rs53
-rw-r--r--src/trust.rs72
3 files changed, 117 insertions, 12 deletions
diff --git a/src/init.rs b/src/init.rs
index 459519d..d051fc4 100644
--- a/src/init.rs
+++ b/src/init.rs
@@ -6,7 +6,7 @@ use std::{
path::Path,
};
-use crate::{embedded::EMBEDDED_FILES, info_file::ExerciseInfo};
+use crate::{embedded::EMBEDDED_FILES, info_file::ExerciseInfo, trust::trust_current_dir};
fn create_cargo_toml(exercise_infos: &[ExerciseInfo]) -> io::Result<()> {
let mut cargo_toml = Vec::with_capacity(1 << 13);
@@ -85,6 +85,8 @@ pub fn init(exercise_infos: &[ExerciseInfo]) -> Result<()> {
create_vscode_dir().context("Failed to create the file `rustlings/.vscode/extensions.json`")?;
+ trust_current_dir()?;
+
Ok(())
}
diff --git a/src/main.rs b/src/main.rs
index ed5becf..7b63f70 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,4 @@
use anyhow::{Context, Result};
-use app_state::StateFileStatus;
use clap::{Parser, Subcommand};
use crossterm::{
terminal::{Clear, ClearType},
@@ -19,14 +18,16 @@ mod init;
mod list;
mod progress_bar;
mod run;
+mod trust;
mod watch;
use self::{
- app_state::AppState,
+ app_state::{AppState, StateFileStatus},
info_file::InfoFile,
init::init,
list::list,
run::run,
+ trust::{current_dir_is_trusted, trust_current_dir},
watch::{watch, WatchExit},
};
@@ -61,6 +62,11 @@ enum Subcommands {
/// The name of the exercise
name: String,
},
+ /// Trust the current directory with its exercises.
+ ///
+ /// You only need to run this if you want to work on third-party exercises or after you moved
+ /// the official exercises that were initialized with `rustlings init`.
+ Trust,
}
fn main() -> Result<()> {
@@ -72,14 +78,26 @@ fn main() -> Result<()> {
if matches!(args.command, Some(Subcommands::Init)) {
init(&info_file.exercises).context("Initialization failed")?;
-
println!("{POST_INIT_MSG}");
return Ok(());
- } else if !Path::new("exercises").is_dir() {
+ }
+
+ if !Path::new("exercises").is_dir() {
println!("{PRE_INIT_MSG}");
exit(1);
}
+ if matches!(args.command, Some(Subcommands::Trust)) {
+ trust_current_dir()?;
+ println!("{POST_TRUST_MSG}");
+ return Ok(());
+ }
+
+ if !current_dir_is_trusted()? {
+ println!("{NOT_TRUSTED_MSG}");
+ exit(1);
+ }
+
let (mut app_state, state_file_status) = AppState::new(
info_file.exercises,
info_file.final_message.unwrap_or_default(),
@@ -130,8 +148,6 @@ fn main() -> Result<()> {
}
}
}
- // `Init` is handled above.
- Some(Subcommands::Init) => (),
Some(Subcommands::Run { name }) => {
if let Some(name) = name {
app_state.set_current_exercise_by_name(&name)?;
@@ -149,6 +165,8 @@ fn main() -> Result<()> {
app_state.set_current_exercise_by_name(&name)?;
println!("{}", app_state.current_exercise().hint);
}
+ // `Init` and `Trust` are handled above.
+ Some(Subcommands::Init | Subcommands::Trust) => (),
}
Ok(())
@@ -158,8 +176,13 @@ const CARGO_NOT_FOUND_ERR: &str = "Failed to find `cargo`.
Did you already install Rust?
Try running `cargo --version` to diagnose the problem.";
+const POST_INIT_MSG: &str = "Done initialization!
+
+Run `cd rustlings` to go into the generated directory.
+Then run `rustlings` to get started.";
+
const PRE_INIT_MSG: &str = r"
- welcome to...
+ Welcome to...
_ _ _
_ __ _ _ ___| |_| (_)_ __ __ _ ___
| '__| | | / __| __| | | '_ \ / _` / __|
@@ -170,11 +193,19 @@ const PRE_INIT_MSG: &str = r"
The `exercises` directory wasn't found in the current directory.
If you are just starting with Rustlings, run the command `rustlings init` to initialize it.";
-const POST_INIT_MSG: &str = "
-Done initialization!
+const POST_TRUST_MSG: &str = "You now trust the exercises in the current directory.
+Run `rustlings` to start working on them.";
-Run `cd rustlings` to go into the generated directory.
-Then run `rustlings` to get started.";
+const NOT_TRUSTED_MSG: &str = "It looks like you are trying to work on third-party exercises.
+Rustlings supports third-party exercises. But because Rustlings runs the code inside an exercise,
+we need to warn you about the possibility of malicious code.
+We recommend that you read all the exercise files in the `exercises` directory and check the
+dependencies in the `Cargo.toml` file.
+If everything looks fine and you want to trust this directory, run `rustlings trust`.
+
+If you you are trying to work on the official exercises that were generated using `rustlings init`,
+then you probably moved the directory containing them. In that case, you can run `rustlings trust`
+without a problem.";
const FENISH_LINE: &str = "+----------------------------------------------------+
| You made it to the Fe-nish line! |
diff --git a/src/trust.rs b/src/trust.rs
new file mode 100644
index 0000000..7e36f73
--- /dev/null
+++ b/src/trust.rs
@@ -0,0 +1,72 @@
+use anyhow::{Context, Error, Result};
+use std::{
+ env,
+ fs::{self, OpenOptions},
+ io::{ErrorKind, Write},
+};
+
+const DATA_DIR_NAME: &str = "rustlings";
+const TRUSTED_DIRS_FILE_NAME: &str = "trusted-dirs.txt";
+
+pub fn trust_current_dir() -> Result<()> {
+ let mut path = dirs::data_dir().context("Failed to determine the data directory")?;
+ path.push(DATA_DIR_NAME);
+ if !path.is_dir() {
+ fs::create_dir(&path)
+ .with_context(|| format!("Failed to create the directory {}", path.display()))?;
+ }
+
+ path.push(TRUSTED_DIRS_FILE_NAME);
+ let mut file = OpenOptions::new()
+ .create(true)
+ .append(true)
+ .open(&path)
+ .with_context(|| {
+ format!(
+ "Failed to create/open the file {} in write mode",
+ path.display(),
+ )
+ })?;
+
+ let dir = env::current_dir().context("Failed to get the current directory path")?;
+ let dir = dir.to_string_lossy();
+ let mut line = Vec::with_capacity(dir.as_bytes().len() + 1);
+ line.extend_from_slice(dir.as_bytes());
+ line.push(b'\n');
+
+ file.write_all(&line)
+ .with_context(|| format!("Failed to append to the file {}", path.display()))
+}
+
+pub fn current_dir_is_trusted() -> Result<bool> {
+ let mut path = dirs::data_dir().context("Failed to determine the data directory")?;
+ path.push(DATA_DIR_NAME);
+ path.push(TRUSTED_DIRS_FILE_NAME);
+
+ let content = match fs::read(&path) {
+ Ok(v) => v,
+ Err(e) => match e.kind() {
+ ErrorKind::NotFound => return Ok(false),
+ _ => {
+ return Err(
+ Error::from(e).context(format!("Failed to read the file {}", path.display()))
+ )
+ }
+ },
+ };
+
+ let current_dir = env::current_dir().context("Failed to get the current directory path")?;
+ let current_dir = current_dir.to_string_lossy();
+
+ for line in content.split(|c| *c == b'\n') {
+ if line.is_empty() {
+ break;
+ }
+
+ if line == current_dir.as_bytes() {
+ return Ok(true);
+ }
+ }
+
+ Ok(false)
+}