diff --git a/src/Hydra/lib/Hydra/Controller/Build.pm b/src/Hydra/lib/Hydra/Controller/Build.pm new file mode 100644 index 00000000..ae8077b6 --- /dev/null +++ b/src/Hydra/lib/Hydra/Controller/Build.pm @@ -0,0 +1,119 @@ +package Hydra::Controller::Build; + +use strict; +use warnings; +use parent 'Catalyst::Controller'; +use Hydra::Helper::Nix; +use Hydra::Helper::CatalystUtils; + + +# Security checking of filenames. +my $pathCompRE = "(?:[A-Za-z0-9-\+][A-Za-z0-9-\+\._]*)"; +my $relPathRE = "(?:$pathCompRE(?:\/$pathCompRE)*)"; + + +sub build : Chained('/') PathPart CaptureArgs(1) { + my ($self, $c, $id) = @_; + + $c->stash->{id} = $id; + + $c->stash->{build} = getBuild($c, $id); + + if (!defined $c->stash->{build}) { + error($c, "Build with ID $id doesn't exist."); + $c->response->status(404); + return; + } + + $c->stash->{curProject} = $c->stash->{build}->project; +} + + +sub view_build : Chained('build') PathPart('') Args(0) { + my ($self, $c) = @_; + + my $build = $c->stash->{build}; + + $c->stash->{template} = 'build.tt'; + $c->stash->{curTime} = time; + $c->stash->{available} = isValidPath $build->outpath; + + if (!$build->finished && $build->schedulingInfo->busy) { + my $logfile = $build->schedulingInfo->logfile; + $c->stash->{logtext} = `cat $logfile`; + } +} + + +sub view_nixlog : Chained('build') PathPart('nixlog') Args(1) { + my ($self, $c, $stepnr) = @_; + + my $step = $c->stash->{build}->buildsteps->find({stepnr => $stepnr}); + return error($c, "Build doesn't have a build step $stepnr.") if !defined $step; + + $c->stash->{template} = 'log.tt'; + $c->stash->{step} = $step; + + # !!! should be done in the view (as a TT plugin). + $c->stash->{logtext} = loadLog($c, $step->logfile); +} + + +sub view_log : Chained('build') PathPart('log') Args(0) { + my ($self, $c) = @_; + + return error($c, "Build didn't produce a log.") if !defined $c->stash->{build}->resultInfo->logfile; + + $c->stash->{template} = 'log.tt'; + + # !!! should be done in the view (as a TT plugin). + $c->stash->{logtext} = loadLog($c, $c->stash->{build}->resultInfo->logfile); +} + + +sub loadLog { + my ($c, $path) = @_; + + die unless defined $path; + + # !!! quick hack + my $pipeline = ($path =~ /.bz2$/ ? "cat $path | bzip2 -d" : "cat $path") + . " | nix-log2xml | xsltproc " . $c->path_to("xsl/mark-errors.xsl") . " -" + . " | xsltproc " . $c->path_to("xsl/log2html.xsl") . " - | tail -n +2"; + + return `$pipeline`; +} + + +sub download : Chained('build') PathPart('download') { + my ($self, $c, $productnr, $filename, @path) = @_; + + my $product = $c->stash->{build}->buildproducts->find({productnr => $productnr}); + return error($c, "Build doesn't have a product $productnr.") if !defined $product; + + return error($c, "Product " . $product->path . " has disappeared.") unless -e $product->path; + + # Security paranoia. + foreach my $elem (@path) { + return error($c, "Invalid filename $elem.") if $elem !~ /^$pathCompRE$/; + } + + my $path = $product->path; + $path .= "/" . join("/", @path) if scalar @path > 0; + + # If this is a directory but no "/" is attached, then redirect. + if (-d $path && substr($c->request->uri, -1) ne "/") { + return $c->res->redirect($c->request->uri . "/"); + } + + $path = "$path/index.html" if -d $path && -e "$path/index.html"; + + if (!-e $path) { + return error($c, "File $path does not exist."); + } + + $c->serve_static_file($path); +} + + +1; diff --git a/src/Hydra/lib/Hydra/Controller/Root.pm b/src/Hydra/lib/Hydra/Controller/Root.pm index 15269e6d..3ad52585 100644 --- a/src/Hydra/lib/Hydra/Controller/Root.pm +++ b/src/Hydra/lib/Hydra/Controller/Root.pm @@ -4,17 +4,13 @@ use strict; use warnings; use parent 'Catalyst::Controller'; use Hydra::Helper::Nix; +use Hydra::Helper::CatalystUtils; # Put this controller at top-level. __PACKAGE__->config->{namespace} = ''; -# Security checking of filenames. -my $pathCompRE = "(?:[A-Za-z0-9-\+][A-Za-z0-9-\+\._]*)"; -my $relPathRE = "(?:$pathCompRE(?:\/$pathCompRE)*)"; - - sub begin :Private { my ($self, $c) = @_; $c->stash->{projects} = [$c->model('DB::Projects')->search({}, {order_by => 'displayname'})]; @@ -22,13 +18,6 @@ sub begin :Private { } -sub error { - my ($c, $msg) = @_; - $c->error($msg); - $c->detach; -} - - sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; @@ -36,13 +25,6 @@ sub trim { } -sub getBuild { - my ($c, $id) = @_; - my $build = $c->model('DB::Builds')->find($id); - return $build; -} - - sub getBuildStats { my ($c, $builds) = @_; @@ -368,7 +350,7 @@ sub updateProject { # The Nix expression path must be relative and can't contain ".." elements. my $nixExprPath = trim $c->request->params->{"jobset-$baseName-nixexprpath"}; - die "Invalid Nix expression path: $nixExprPath" if $nixExprPath !~ /^$relPathRE$/; + die "Invalid Nix expression path: $nixExprPath" if $nixExprPath !~ /^$Build::relPathRE$/; my $nixExprInput = trim $c->request->params->{"jobset-$baseName-nixexprinput"}; die "Invalid Nix expression input name: $nixExprInput" unless $nixExprInput =~ /^\w+$/; @@ -573,110 +555,6 @@ sub default :Path { } -sub build : Chained('/') PathPart CaptureArgs(1) { - my ($self, $c, $id) = @_; - - $c->stash->{id} = $id; - - $c->stash->{build} = getBuild($c, $id); - - if (!defined $c->stash->{build}) { - error($c, "Build with ID $id doesn't exist."); - $c->response->status(404); - return; - } - - $c->stash->{curProject} = $c->stash->{build}->project; -} - - -sub view_build : Chained('build') PathPart('') Args(0) { - my ($self, $c) = @_; - - my $build = $c->stash->{build}; - - $c->stash->{template} = 'build.tt'; - $c->stash->{curTime} = time; - $c->stash->{available} = isValidPath $build->outpath; - - if (!$build->finished && $build->schedulingInfo->busy) { - my $logfile = $build->schedulingInfo->logfile; - $c->stash->{logtext} = `cat $logfile`; - } -} - - -sub view_nixlog : Chained('build') PathPart('nixlog') Args(1) { - my ($self, $c, $stepnr) = @_; - - my $step = $c->stash->{build}->buildsteps->find({stepnr => $stepnr}); - return error($c, "Build doesn't have a build step $stepnr.") if !defined $step; - - $c->stash->{template} = 'log.tt'; - $c->stash->{step} = $step; - - # !!! should be done in the view (as a TT plugin). - $c->stash->{logtext} = loadLog($c, $step->logfile); -} - - -sub view_log : Chained('build') PathPart('log') Args(0) { - my ($self, $c) = @_; - - return error($c, "Build didn't produce a log.") if !defined $c->stash->{build}->resultInfo->logfile; - - $c->stash->{template} = 'log.tt'; - - # !!! should be done in the view (as a TT plugin). - $c->stash->{logtext} = loadLog($c, $c->stash->{build}->resultInfo->logfile); -} - - -sub loadLog { - my ($c, $path) = @_; - - die unless defined $path; - - # !!! quick hack - my $pipeline = ($path =~ /.bz2$/ ? "cat $path | bzip2 -d" : "cat $path") - . " | nix-log2xml | xsltproc " . $c->path_to("xsl/mark-errors.xsl") . " -" - . " | xsltproc " . $c->path_to("xsl/log2html.xsl") . " - | tail -n +2"; - - return `$pipeline`; -} - - -sub download : Chained('build') PathPart('download') { - my ($self, $c, $productnr, $filename, @path) = @_; - - my $product = $c->stash->{build}->buildproducts->find({productnr => $productnr}); - return error($c, "Build doesn't have a product $productnr.") if !defined $product; - - return error($c, "Product " . $product->path . " has disappeared.") unless -e $product->path; - - # Security paranoia. - foreach my $elem (@path) { - return error($c, "Invalid filename $elem.") if $elem !~ /^$pathCompRE$/; - } - - my $path = $product->path; - $path .= "/" . join("/", @path) if scalar @path > 0; - - # If this is a directory but no "/" is attached, then redirect. - if (-d $path && substr($c->request->uri, -1) ne "/") { - return $c->res->redirect($c->request->uri . "/"); - } - - $path = "$path/index.html" if -d $path && -e "$path/index.html"; - - if (!-e $path) { - return error($c, "File $path does not exist."); - } - - $c->serve_static_file($path); -} - - sub closure :Local { my ($self, $c, $buildId) = @_; diff --git a/src/Hydra/lib/Hydra/Helper/CatalystUtils.pm b/src/Hydra/lib/Hydra/Helper/CatalystUtils.pm new file mode 100644 index 00000000..9fc71389 --- /dev/null +++ b/src/Hydra/lib/Hydra/Helper/CatalystUtils.pm @@ -0,0 +1,24 @@ +package Hydra::Helper::CatalystUtils; + +use strict; +use Exporter; + +our @ISA = qw(Exporter); +our @EXPORT = qw(getBuild error); + + +sub getBuild { + my ($c, $id) = @_; + my $build = $c->model('DB::Builds')->find($id); + return $build; +} + + +sub error { + my ($c, $msg) = @_; + $c->error($msg); + $c->detach; +} + + +1; diff --git a/src/Hydra/root/build.tt b/src/Hydra/root/build.tt index ad98f2bb..b62135cd 100644 --- a/src/Hydra/root/build.tt +++ b/src/Hydra/root/build.tt @@ -121,7 +121,7 @@ Logfile: - Available + Available [% END %] @@ -178,7 +178,7 @@ [% FOREACH step IN build.buildsteps -%] - [% log = c.uri_for('build' build.id 'nixlog' step.stepnr) %] + [% log = c.uri_for('/build' build.id 'nixlog' step.stepnr) %] [% step.stepnr %] diff --git a/src/Hydra/root/product-list.tt b/src/Hydra/root/product-list.tt index 2a7ff3a1..8331c69f 100644 --- a/src/Hydra/root/product-list.tt +++ b/src/Hydra/root/product-list.tt @@ -6,14 +6,14 @@ [% FOREACH product IN build.buildproducts -%] - [% uri = c.uri_for('build' build.id 'download' product.productnr product.name) %] + [% uri = c.uri_for('/build' build.id 'download' product.productnr product.name) %] [% SWITCH product.type %] [% CASE "nix-build" %]
  • - [% uri = c.uri_for('nixpkg' build.id) %] + [% uri = c.uri_for('/nixpkg' build.id) %] Source One-click install of Nix package [% build.nixname %]