From 1f0a30148d88bd39e478a363b855f37dca540ab8 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 24 Dec 2015 23:14:59 +0000 Subject: [PATCH] repobrowse: support raw blob display This allows displaying blob objects, including trees, as is in "bit perfect" fashion. --- lib/PublicInbox/RepoBrowse.pm | 2 +- lib/PublicInbox/RepoBrowseBase.pm | 27 ++++++++++++ lib/PublicInbox/RepoBrowseGitBlob.pm | 66 ++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 lib/PublicInbox/RepoBrowseGitBlob.pm diff --git a/lib/PublicInbox/RepoBrowse.pm b/lib/PublicInbox/RepoBrowse.pm index 8fa1d035c..414221933 100644 --- a/lib/PublicInbox/RepoBrowse.pm +++ b/lib/PublicInbox/RepoBrowse.pm @@ -23,7 +23,7 @@ use warnings; use URI::Escape qw(uri_escape_utf8 uri_unescape); use PublicInbox::RepoConfig; -my %CMD = map { lc($_) => $_ } qw(Log Commit Tree Patch); +my %CMD = map { lc($_) => $_ } qw(Log Commit Tree Patch Blob); my %VCS = (git => 'Git'); my %LOADED; diff --git a/lib/PublicInbox/RepoBrowseBase.pm b/lib/PublicInbox/RepoBrowseBase.pm index e0ea2854e..7f69108b4 100644 --- a/lib/PublicInbox/RepoBrowseBase.pm +++ b/lib/PublicInbox/RepoBrowseBase.pm @@ -19,4 +19,31 @@ sub call { $@ ? [ 500, ['Content-Type'=>'text/plain'], [] ] : $rv; } +sub mime_load { + my ($self, $file) = @_; + my %rv; + open my $fh, '<', $file or return \%rv; + foreach (<$fh>) { + next if /^#/; # no comments + my ($type, @ext) = split(/\s+/); + + # XSS protection. Assume the browser knows what to do + # with images/audio/video... + if (defined $type && $type =~ m!\A(?:image|audio|video)/!) { + $rv{$_} = $type foreach @ext; + } + } + \%rv; +} + +# returns undef if missing, so users can scan the blob if needed +sub mime_type { + my ($self, $fn) = @_; + $fn =~ /\.([^\.]+)\z/ or return; + my $ext = $1; + my $m = $self->{mime_types} ||= $self->mime_load('/etc/mime.types'); + + $m->{$ext}; +} + 1; diff --git a/lib/PublicInbox/RepoBrowseGitBlob.pm b/lib/PublicInbox/RepoBrowseGitBlob.pm new file mode 100644 index 000000000..d94f0d94c --- /dev/null +++ b/lib/PublicInbox/RepoBrowseGitBlob.pm @@ -0,0 +1,66 @@ +# Copyright (C) 2015 all contributors +# License: AGPL-3.0+ + +# Show a blob as-is +package PublicInbox::RepoBrowseGitBlob; +use strict; +use warnings; +use base qw(PublicInbox::RepoBrowseBase); + +sub call_git_blob { + my ($self, $req) = @_; + my $git = $req->{repo_info}->{git}; + my $to_read = 8000; # git uses this size to detect binary files + my $q = PublicInbox::RepoBrowseQuery->new($req->{cgi}); + my $id = $q->{id}; + $id eq '' and $id = 'HEAD'; + + if (length(my $expath = $req->{expath})) { + $id .= ":$expath"; + } + my ($cat, $hex, $type, $size) = $git->cat_file_begin($id); + return unless defined $cat; + + my ($r, $buf); + my $left = $size; + if ($type eq 'blob') { + my $base = $req->{extra}->[-1]; + $type = $self->mime_type($base) if defined $base; + unless ($type) { + $to_read = $left if $to_read > $left; + $r = read($cat, $buf, $to_read); + if (!defined $r || $r <= 0) { + $git->cat_file_finish($left); + return; + } + $left -= $r; + $type = (index($buf, "\0") < 0) ? + 'text/plain' : + 'application/octet-stream'; + } + } elsif ($type eq 'commit' || $type eq 'tag') { + $type = 'text/plain'; + } else { + $type = 'application/octet-stream'; + } + + sub { + my ($res) = @_; + eval { + my $fh = $res->([ 200, ['Content-Length' => $size, + 'Content-Type' => $type]]); + $fh->write($buf) if defined $buf; + while ($left > 0) { + $to_read = $left if $to_read > $left; + $r = read($cat, $buf, $to_read); + last if (!defined $r || $r <= 0); + $left -= $r; + $fh->write($buf); + } + $fh->close; + }; + $git->cat_file_finish($left); + } +} + +1; -- 2.47.3