summary history branches tags files
commit:99cdc02af592c8724cb2672d380865990631fe60
author:Trevor Bentley
committer:Trevor Bentley
date:Thu Jan 12 21:02:38 2023 +0100
parents:bc29ccd66a635f9058f209087c92fb3c8fc80e86
protect against writes outside of output directory
diff --git a/src/generate.rs b/src/generate.rs
line changes: +49/-32
index e0c79a4..7a86297
--- a/src/generate.rs
+++ b/src/generate.rs
@@ -123,7 +123,37 @@ impl GitsyGenerator {
         Ok(file)
     }
 
-    fn write_rendered(path: &str, rendered: &str) -> usize {
+    fn write_rendered(&self, path: &str, rendered: &str) -> usize {
+        // Ensure that the requested output path is actually a child
+        // of the output directory, as a sanity check to ensure we
+        // aren't writing out of bounds.
+        let canonical_root = self.settings.outputs.path.canonicalize().expect(&format!(
+            "Cannot find canonical version of output path: {}",
+            self.settings.outputs.path.display()
+        ));
+        let canonical_path = PathBuf::from(path);
+        let has_relative_dirs = canonical_path.ancestors().any(|x| x.file_name().is_none() &&
+                                                               x != Path::new("/"));
+        assert!(
+            canonical_path.is_absolute(),
+            "ERROR: write_rendered called with a relative path: {}",
+            path
+        );
+        assert!(
+            !has_relative_dirs,
+            "ERROR: write_rendered called with a relative path: {}",
+            path
+        );
+        let _ = canonical_path
+            .ancestors()
+            .find(|x| x == &canonical_root)
+            .expect(&format!(
+                "Output file {} not contained in output path: {}",
+                canonical_path.display(),
+                canonical_root.display()
+            ));
+
+        // Write the file to disk
         let mut file = File::create(path).expect(&format!("Unable to write to output path: {}", path));
         file.write(rendered.as_bytes())
             .expect(&format!("Failed to save rendered html to path: {}", path));
@@ -305,10 +335,8 @@ impl GitsyGenerator {
             if let Some(templ_file) = self.settings.templates.repo_summary.as_deref() {
                 match tera.render(templ_file, &local_ctx) {
                     Ok(rendered) => {
-                        repo_bytes += GitsyGenerator::write_rendered(
-                            &self.settings.outputs.repo_summary(Some(&summary), None),
-                            &rendered,
-                        );
+                        repo_bytes +=
+                            self.write_rendered(&self.settings.outputs.repo_summary(Some(&summary), None), &rendered);
                     }
                     Err(x) => match x.kind {
                         _ => error!("ERROR: {:?}", x),
@@ -330,7 +358,7 @@ impl GitsyGenerator {
                     paged_ctx.insert("branches", &page);
                     match tera.render(templ_file, &paged_ctx) {
                         Ok(rendered) => {
-                            repo_bytes += GitsyGenerator::write_rendered(&pagination.cur_page, &rendered);
+                            repo_bytes += self.write_rendered(&pagination.cur_page, &rendered);
                         }
                         Err(x) => match x.kind {
                             _ => error!("ERROR: {:?}", x),
@@ -347,10 +375,8 @@ impl GitsyGenerator {
                 if let Some(templ_file) = self.settings.templates.branch.as_deref() {
                     match tera.render(templ_file, &local_ctx) {
                         Ok(rendered) => {
-                            repo_bytes += GitsyGenerator::write_rendered(
-                                &self.settings.outputs.branch(Some(&summary), Some(branch)),
-                                &rendered,
-                            );
+                            repo_bytes += self
+                                .write_rendered(&self.settings.outputs.branch(Some(&summary), Some(branch)), &rendered);
                         }
                         Err(x) => match x.kind {
                             _ => error!("ERROR: {:?}", x),
@@ -374,7 +400,7 @@ impl GitsyGenerator {
                     paged_ctx.insert("tags", &page);
                     match tera.render(templ_file, &paged_ctx) {
                         Ok(rendered) => {
-                            repo_bytes += GitsyGenerator::write_rendered(&pagination.cur_page, &rendered);
+                            repo_bytes += self.write_rendered(&pagination.cur_page, &rendered);
                         }
                         Err(x) => match x.kind {
                             _ => error!("ERROR: {:?}", x),
@@ -396,10 +422,8 @@ impl GitsyGenerator {
                 if let Some(templ_file) = self.settings.templates.tag.as_deref() {
                     match tera.render(templ_file, &local_ctx) {
                         Ok(rendered) => {
-                            repo_bytes += GitsyGenerator::write_rendered(
-                                &self.settings.outputs.tag(Some(&summary), Some(tag)),
-                                &rendered,
-                            );
+                            repo_bytes +=
+                                self.write_rendered(&self.settings.outputs.tag(Some(&summary), Some(tag)), &rendered);
                         }
                         Err(x) => match x.kind {
                             _ => error!("ERROR: {:?}", x),
@@ -424,7 +448,7 @@ impl GitsyGenerator {
                     paged_ctx.insert("history", &page);
                     match tera.render(templ_file, &paged_ctx) {
                         Ok(rendered) => {
-                            repo_bytes += GitsyGenerator::write_rendered(&pagination.cur_page, &rendered);
+                            repo_bytes += self.write_rendered(&pagination.cur_page, &rendered);
                         }
                         Err(x) => match x.kind {
                             _ => error!("ERROR: {:?}", x),
@@ -443,10 +467,8 @@ impl GitsyGenerator {
                 if let Some(templ_file) = self.settings.templates.commit.as_deref() {
                     match tera.render(templ_file, &local_ctx) {
                         Ok(rendered) => {
-                            repo_bytes += GitsyGenerator::write_rendered(
-                                &self.settings.outputs.commit(Some(&summary), Some(commit)),
-                                &rendered,
-                            );
+                            repo_bytes += self
+                                .write_rendered(&self.settings.outputs.commit(Some(&summary), Some(commit)), &rendered);
                         }
                         Err(x) => match x.kind {
                             _ => error!("ERROR: {:?}", x),
@@ -470,10 +492,8 @@ impl GitsyGenerator {
                     .expect("Invalid syntax highlighting theme specified.");
                 let css: String = css_for_theme_with_class_style(theme, syntect::html::ClassStyle::Spaced)
                     .expect("Invalid syntax highlighting theme specified.");
-                repo_bytes += GitsyGenerator::write_rendered(
-                    &self.settings.outputs.syntax_css(Some(&summary), None),
-                    css.as_str(),
-                );
+                repo_bytes +=
+                    self.write_rendered(&self.settings.outputs.syntax_css(Some(&summary), None), css.as_str());
             }
 
             // TODO: parallelize the rest of the processing steps.  This one is
@@ -503,7 +523,7 @@ impl GitsyGenerator {
                         if let Some(templ_file) = self.settings.templates.file.as_deref() {
                             match tera.render(templ_file, &local_ctx) {
                                 Ok(rendered) => {
-                                    local_bytes = GitsyGenerator::write_rendered(
+                                    local_bytes = self.write_rendered(
                                         &self.settings.outputs.file(Some(&summary), Some(&file)),
                                         &rendered,
                                     );
@@ -534,10 +554,8 @@ impl GitsyGenerator {
                 if let Some(templ_file) = self.settings.templates.dir.as_deref() {
                     match tera.render(templ_file, &local_ctx) {
                         Ok(rendered) => {
-                            repo_bytes += GitsyGenerator::write_rendered(
-                                &self.settings.outputs.dir(Some(&summary), Some(dir)),
-                                &rendered,
-                            );
+                            repo_bytes +=
+                                self.write_rendered(&self.settings.outputs.dir(Some(&summary), Some(dir)), &rendered);
                         }
                         Err(x) => match x.kind {
                             _ => error!("ERROR: {:?}", x),
@@ -612,8 +630,7 @@ impl GitsyGenerator {
         if let Some(templ_file) = self.settings.templates.repo_list.as_deref() {
             match tera.render(templ_file, &global_ctx) {
                 Ok(rendered) => {
-                    global_bytes +=
-                        GitsyGenerator::write_rendered(&self.settings.outputs.repo_list(None, None), &rendered);
+                    global_bytes += self.write_rendered(&self.settings.outputs.repo_list(None, None), &rendered);
                 }
                 Err(x) => match x.kind {
                     _ => error!("ERROR: {:?}", x),
@@ -624,7 +641,7 @@ impl GitsyGenerator {
         if let Some(templ_file) = self.settings.templates.error.as_deref() {
             match tera.render(templ_file, &global_ctx) {
                 Ok(rendered) => {
-                    global_bytes += GitsyGenerator::write_rendered(&self.settings.outputs.error(None, None), &rendered);
+                    global_bytes += self.write_rendered(&self.settings.outputs.error(None, None), &rendered);
                 }
                 Err(x) => match x.kind {
                     _ => error!("ERROR: {:?}", x),

diff --git a/src/settings.rs b/src/settings.rs
line changes: +2/-1
index c2df4fc..320927b
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -126,7 +126,8 @@ macro_rules! output_path_fn {
                 _ => tmpl_str,
             };
             let tmpl = PathBuf::from(tmpl_str);
-            let mut path = self.path.clone();
+            let mut path = self.path.clone().canonicalize()
+                .expect(&format!("ERROR: unable to canonicalize output path: {}", self.path.display()));
             path.push(tmpl);
             match $is_dir {
                 true => {