]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/blobdiff - IkiWiki/Plugin/img.pm
Changelog
[git.ikiwiki.info.git] / IkiWiki / Plugin / img.pm
index 1ab753352e2e28b65430dfd0cf574efc94dcf809..ed2e9354da988dfdfe1894d1fe6d7bb93e1ad4c1 100644 (file)
@@ -21,6 +21,28 @@ sub getsetup () {
                        rebuild => undef,
                        section => "widget",
                },
+               img_allowed_formats => {
+                       type => "string",
+                       default => [qw(jpeg png gif)],
+                       description => "Image formats to process (jpeg, png, gif, pdf, svg or 'everything' to accept all)",
+                       # ImageMagick has had arbitrary code execution flaws,
+                       # and the whole delegates mechanism is scary from
+                       # that perspective
+                       safe => 0,
+                       rebuild => 0,
+               },
+}
+
+sub allowed {
+       my $format = shift;
+       my $allowed = $config{img_allowed_formats};
+       $allowed = ['jpeg', 'png'] unless defined $allowed && @$allowed;
+
+       foreach my $a (@$allowed) {
+               return 1 if $a eq $format || $a eq 'everything';
+       }
+
+       return 0;
 }
 
 sub preprocess (@) {
@@ -64,6 +86,69 @@ sub preprocess (@) {
 
        my $dir = $params{page};
        my $base = IkiWiki::basename($file);
+       my $extension;
+       my $format;
+
+       if ($base =~ m/\.([a-z0-9]+)$/) {
+               $extension = $1;
+       }
+       else {
+               error gettext("Unable to detect image type from extension");
+       }
+
+       # Never interpret well-known file extensions as any other format,
+       # in case the wiki configuration unwisely allows attaching
+       # arbitrary files named *.jpg, etc.
+       my $magic;
+       my $offset = 0;
+       open(my $in, '<', $srcfile) or error sprintf(gettext("failed to read %s: %s"), $file, $!);
+       binmode($in);
+
+       if ($extension =~ m/^(jpeg|jpg)$/is) {
+               $format = 'jpeg';
+               $magic = "\377\330\377";
+       }
+       elsif ($extension =~ m/^(png)$/is) {
+               $format = 'png';
+               $magic = "\211PNG\r\n\032\n";
+       }
+       elsif ($extension =~ m/^(gif)$/is) {
+               $format = 'gif';
+               $magic = "GIF8";
+       }
+       elsif ($extension =~ m/^(svg)$/is) {
+               $format = 'svg';
+       }
+       elsif ($extension =~ m/^(pdf)$/is) {
+               $format = 'pdf';
+               $magic = "%PDF-";
+       }
+       else {
+               # allow ImageMagick to auto-detect (potentially dangerous)
+               $format = '';
+       }
+
+       error sprintf(gettext("%s image processing disabled in img_allowed_formats configuration"), $format ? $format : "\"$extension\"") unless allowed($format ? $format : "everything");
+
+       # Try harder to protect ImageMagick from itself
+       if ($format eq 'svg') {
+               my $content;
+               read($in, $content, 5) or error sprintf(gettext("failed to read %s: %s"), $file, $!);
+               # This is an over-simplification, but ?xml is the check that
+               # ImageMagick uses. We also accept <svg for the simplest
+               # possible SVGs.
+               if ($content !~ m/^(.\?xml|<svg)/is) {
+                       error sprintf(gettext("\"%s\" does not seem to be a valid %s file"), $file, $format);
+               }
+       }
+       elsif ($magic) {
+               my $content;
+               read($in, $content, length $magic) or error sprintf(gettext("failed to read %s: %s"), $file, $!);
+               if ($magic ne $content) {
+                       error sprintf(gettext("\"%s\" does not seem to be a valid %s file"), $file, $format);
+               }
+       }
+
        my $issvg = $base=~s/\.svg$/.png/i;
        my $ispdf = $base=~s/\.pdf$/.png/i;
        my $pagenumber = exists($params{pagenumber}) ? int($params{pagenumber}) : 0;
@@ -75,9 +160,14 @@ sub preprocess (@) {
        error gettext("Image::Magick is not installed") if $@;
        my $im = Image::Magick->new();
        my $imglink;
-       my $r = $im->Read("$srcfile\[$pagenumber]");
+       my $imgdatalink;
+       my $r = $im->Read("$format:$srcfile\[$pagenumber]");
        error sprintf(gettext("failed to read %s: %s"), $file, $r) if $r;
-       
+
+       if (! defined $im->Get("width") || ! defined $im->Get("height")) {
+               error sprintf(gettext("failed to get dimensions of %s"), $file);
+       }
+
        my ($dwidth, $dheight);
 
        if ($params{size} eq 'full') {
@@ -123,15 +213,17 @@ sub preprocess (@) {
                        $r = $im->Resize(geometry => "${dwidth}x${dheight}");
                        error sprintf(gettext("failed to resize: %s"), $r) if $r;
 
+                       $im->set(($issvg || $ispdf) ? (magick => 'png') : ());
+                       my @blob = $im->ImageToBlob();
                        # don't actually write resized file in preview mode;
                        # rely on width and height settings
                        if (! $params{preview}) {
-                               $im->set(($issvg || $ispdf) ? (magick => 'png') : ());
-                               my @blob = $im->ImageToBlob();
                                writefile($imglink, $config{destdir}, $blob[0], 1);
                        }
                        else {
-                               $imglink = $file;
+                               eval q{use MIME::Base64};
+                               error($@) if $@;
+                               $imgdatalink = "data:image/".$im->Get("magick").";base64,".encode_base64($blob[0]);
                        }
                }
 
@@ -148,14 +240,9 @@ sub preprocess (@) {
        }
 
        my ($fileurl, $imgurl);
-       if (! $params{preview}) {
-               $fileurl=urlto($file, $params{destpage});
-               $imgurl=urlto($imglink, $params{destpage});
-       }
-       else {
-               $fileurl=urlto($file);
-               $imgurl=urlto($imglink);
-       }
+       my $urltobase = $params{preview} ? undef : $params{destpage};
+       $fileurl=urlto($file, $urltobase);
+       $imgurl=$imgdatalink ? $imgdatalink : urlto($imglink, $urltobase);
 
        if (! exists $params{class}) {
                $params{class}="img";