From 7857f8325152daba97ef3764133ebc8b58e9860a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 14 Nov 2017 17:15:05 +0100 Subject: [PATCH] Serve directories and symlinks correctly --- src/lib/Hydra/Controller/Build.pm | 76 ++++++++++++++++++++++++------- src/lib/Hydra/View/Plain.pm | 3 +- 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/src/lib/Hydra/Controller/Build.pm b/src/lib/Hydra/Controller/Build.pm index 08f864a6..8ad866f2 100644 --- a/src/lib/Hydra/Controller/Build.pm +++ b/src/lib/Hydra/Controller/Build.pm @@ -15,6 +15,7 @@ use Nix::Config; use List::MoreUtils qw(all); use Encode; use MIME::Types; +use JSON::PP; sub buildChain :Chained('/') :PathPart('build') :CaptureArgs(1) { @@ -187,6 +188,53 @@ sub checkPath { } +sub serveFile { + my ($c, $path) = @_; + + my $res = run(cmd => ["nix", "ls-store", "--store", getStoreUri(), "--json", "$path"]); + + if ($res->{status}) { + notFound($c, "File '$path' does not exist.") if $res->{stderr} =~ /does not exist/; + die "$res->{stderr}\n"; + } + + my $ls = decode_json($res->{stdout}); + + if ($ls->{type} eq "directory" && substr($c->request->uri, -1) ne "/") { + return $c->res->redirect($c->request->uri . "/"); + } + + elsif ($ls->{type} eq "directory" && defined $ls->{entries}->{"index.html"}) { + return serveFile($c, "$path/index.html"); + } + + elsif ($ls->{type} eq "symlink") { + my $target = $ls->{target}; + return serveFile($c, substr($target, 0, 1) eq "/" ? $target : dirname($path) . "/" . $target); + } + + elsif ($ls->{type} eq "regular") { + + $c->stash->{'plain'} = { data => grab(cmd => ["nix", "cat-store", "--store", getStoreUri(), "$path"]) }; + + # Detect MIME type. Borrowed from Catalyst::Plugin::Static::Simple. + my $type = "text/plain"; + if ($path =~ /.*\.(\S{1,})$/xms) { + my $ext = $1; + my $mimeTypes = MIME::Types->new(only_complete => 1); + my $t = $mimeTypes->mimeTypeOf($ext); + $type = ref $t ? $t->type : $t if $t; + } + $c->response->content_type($type); + $c->forward('Hydra::View::Plain'); + } + + else { + error($c, "Do not know how to serve path '$path'."); + } +} + + sub download : Chained('buildChain') PathPart { my ($self, $c, $productRef, @path) = @_; @@ -202,7 +250,7 @@ sub download : Chained('buildChain') PathPart { notFound($c, "Build doesn't have a product $productRef.") if !defined $product; if ($product->path !~ /^($Nix::Config::storeDir\/[^\/]+)/) { - die "Invalid store path " . $product->path . ".\n"; + die "Invalid store path '" . $product->path . "'.\n"; } my $storePath = $1; @@ -215,7 +263,7 @@ sub download : Chained('buildChain') PathPart { # Security paranoia. foreach my $elem (@path) { - error($c, "Invalid filename $elem.") if $elem !~ /^$pathCompRE$/; + error($c, "Invalid filename '$elem'.") if $elem !~ /^$pathCompRE$/; } my $path = $product->path; @@ -223,7 +271,7 @@ sub download : Chained('buildChain') PathPart { if (isLocalStore) { - notFound($c, "File " . $product->path . " does not exist.") unless -e $product->path; + notFound($c, "File '" . $product->path . "' does not exist.") unless -e $product->path; # Make sure the file is in the Nix store. $path = checkPath($self, $c, $path); @@ -235,26 +283,17 @@ sub download : Chained('buildChain') PathPart { $path = "$path/index.html" if -d $path && -e "$path/index.html"; - notFound($c, "File $path does not exist.") if !-e $path; + notFound($c, "File '$path' does not exist.") if !-e $path; - notFound($c, "Path $path is a directory.") if -d $path; + notFound($c, "Path '$path' is a directory.") if -d $path; $c->serve_static_file($path); - $c->response->headers->last_modified($c->stash->{build}->stoptime); } else { - $c->stash->{'plain'} = { data => readNixFile($path) }; - # Detect MIME type. Borrowed from Catalyst::Plugin::Static::Simple. - my $type = "text/plain"; - if ($path =~ /.*\.(\S{1,})$/xms) { - my $ext = $1; - my $mimeTypes = MIME::Types->new(only_complete => 1); - my $t = $mimeTypes->mimeTypeOf($ext); - $type = ref $t ? $t->type : $t if $t; - } - $c->response->content_type($type); - $c->forward('Hydra::View::Plain'); + serveFile($c, $path); } + + $c->response->headers->last_modified($c->stash->{build}->stoptime); } @@ -308,9 +347,12 @@ sub contents : Chained('buildChain') PathPart Args(1) { # FIXME: don't use shell invocations below. + # FIXME: use nix cat-store + my $res; if ($product->type eq "nix-build" && -d $path) { + # FIXME: use nix ls-store -R --json $res = `cd '$path' && find . -print0 | xargs -0 ls -ld --`; error($c, "`ls -lR' error: $?") if $? != 0; diff --git a/src/lib/Hydra/View/Plain.pm b/src/lib/Hydra/View/Plain.pm index 3e992471..868c74f8 100644 --- a/src/lib/Hydra/View/Plain.pm +++ b/src/lib/Hydra/View/Plain.pm @@ -6,7 +6,8 @@ use base 'Catalyst::View::Download::Plain'; sub process { my ($self, $c) = @_; - $c->response->content_type('text/plain; charset=utf-8') unless $c->response->content_type() ne ""; + $c->clear_encoding; + $c->response->content_type('text/plain; charset=utf-8') if $c->response->content_type() eq "text/plain"; $c->response->body($c->stash->{plain}->{data}); }