FIX: Allow SVG uploads if dimensions are a fraction of a unit (#13409)

* FIX: Allow SVG uploads if dimensions are a fraction of a unit

`UploadCreator` counts the number of pixels in an file to determine if it is valid. `pixels` is calculated by multiplying the width and height of the image, as determined by FastImage.

SVG files can have their width/height expressed in a variety of different units of measurement. For example, ‘px’, ‘in’, ‘cm’, ‘mm’, ‘pt’, ‘pc’, etc are all valid within SVG files. If an image has a width of `0.5in`, FastImage may interpret this as being a width of `0`, meaning it will report the `size` as being `0`.

However, we don’t need to concern ourselves with the number of ‘pixels’ in a SVG files, as that is irrelevant for this file format, so we can skip over the check for `pixels == 0` when processing this file type.

* DEV: Speed up getting SVG dimensions

The `-ping` flag prevents the entire image from being rasterized before a result is returned. See:

https://imagemagick.org/script/command-line-options.php#ping
This commit is contained in:
jbrw 2021-06-17 15:56:11 -04:00 committed by GitHub
parent 04a3cd3814
commit fbfd1fd80b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 69 additions and 254 deletions

View File

@ -131,7 +131,7 @@ class UploadCreator
begin
w, h = Discourse::Utils
.execute_command("identify", "-format", "%w %h", @file.path, timeout: Upload::MAX_IDENTIFY_SECONDS)
.execute_command("identify", "-ping", "-format", "%w %h", @file.path, timeout: Upload::MAX_IDENTIFY_SECONDS)
.split(' ')
rescue
# use default 0, 0
@ -194,7 +194,7 @@ class UploadCreator
@upload.errors.add(:base, I18n.t("upload.images.not_supported_or_corrupted"))
elsif filesize <= 0
@upload.errors.add(:base, I18n.t("upload.empty"))
elsif pixels == 0
elsif pixels == 0 && @image_info.type.to_s != 'svg'
@upload.errors.add(:base, I18n.t("upload.images.size_not_found"))
elsif max_image_pixels > 0 && pixels >= max_image_pixels * 2
@upload.errors.add(:base, I18n.t("upload.images.larger_than_x_megapixels", max_image_megapixels: SiteSetting.max_image_megapixels * 2))

9
spec/fixtures/images/massive.svg vendored Normal file
View File

@ -0,0 +1,9 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg
xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
width="120.0in" height="120.99in"
viewBox="0 0 1200 900"
>
<line x1="20" x2="1000" y1="20" y2="700" stroke="#ff88ff" stroke-width="25"/>
</svg>

After

Width:  |  Height:  |  Size: 369 B

View File

@ -1,245 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="8in"
height="12in"
viewBox="0 0 210 297"
version="1.1"
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="drawingpencil.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="446.80634"
inkscape:cy="475.54119"
inkscape:document-units="mm"
inkscape:current-layer="g964"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-global="false"
inkscape:window-width="1280"
inkscape:window-height="705"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1">
<sodipodi:guide
position="-148.16667,180.67263"
orientation="1,0"
id="guide825"
inkscape:locked="false" />
<sodipodi:guide
position="-204.88879,167.72065"
orientation="1,0"
id="guide829"
inkscape:locked="false" />
<sodipodi:guide
position="-331.86309,-5.2916668"
orientation="1,0"
id="guide929"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g1389"
transform="translate(112.6369,-4.5357143)">
<g
transform="matrix(0.39765029,-0.37613964,0.40337233,0.37080392,-33.707976,96.609766)"
id="g964">
<g
id="g866">
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
sodipodi:nodetypes="ccccccccccccc"
inkscape:connector-curvature="0"
id="path827"
d="m -171.39994,58.522452 4.56312,-0.517578 -96.41169,41.09967 92.39126,41.964876 0.0103,0.65784 1.02112,-0.18965 0.41703,0.18965 6.80757,-1.02363 213.349901,-40.08755 -218.860531,-42.072367 0.0119,-0.777213 16.14253,2.05244 z"
style="opacity:1;fill:#ffe680;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
sodipodi:nodetypes="cccccc"
inkscape:connector-curvature="0"
id="path909"
d="m -167.29418,58.090286 -1.96525,-0.130741 -94.74512,40.389265 0.46973,0.213426 203.965548,27.214284 z"
style="opacity:1;fill:#d4aa00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
transform="scale(0.26458333)"
id="path833"
d="m -648.57227,534.84766 v 1.95703 H 522.85742 V 219.66211 H -639.06836 l 85.92578,47.42969 -89.9707,54.27734 91.11328,50.29297 -87.39453,52.72461 90.82226,50.13281 z"
style="fill:#ffcc00;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999994"
inkscape:connector-curvature="0" />
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
inkscape:connector-curvature="0"
id="path841"
d="m -230.72987,84.444087 -4.91908,2.137336 -28.12903,12.220958 27.87271,12.660209 3.58428,1.62781 a 25.702381,21.544643 0 0 0 7.23057,-12.98629 l 0.0264,-0.243397 a 25.702381,21.544643 0 0 0 -5.6658,-15.416626 z m -4.1367,3.635951 a 20.410631,23.056417 0 0 1 0.60771,1.484665 20.410631,23.056417 0 0 0 -0.60771,-1.484665 z"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
transform="scale(0.26458333)"
id="path870"
d="M 282.85742,219.66211 H 116.00195 -17.142578 V 536.80469 H 98.123047 282.85742 Z"
style="fill:#ffcc00;fill-rule:evenodd;stroke-width:0.99999994"
inkscape:connector-curvature="0" />
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
sodipodi:nodetypes="ccaccccccczcccccccaac"
inkscape:connector-curvature="0"
id="path855"
d="m 99.256406,58.118933 v 83.910637 c 0,0 29.238264,4.4148 42.807194,0 1.68331,-0.54768 2.91959,-2.04917 4.2938,-3.12487 1.87046,-1.50578 3.62656,-3.07284 5.26169,-4.69532 1.63503,-1.62234 3.14708,-3.29807 4.53048,-5.02089 1.3834,-1.72304 2.6364,-3.49103 3.75429,-5.29735 1.11783,-1.8061 2.09918,-3.64825 2.94039,-5.51956 0.84127,-1.87139 1.54132,-3.76959 2.09754,-5.6875 0.55623,-1.91799 0.96792,-3.85328 1.23352,-5.79862 0.18047,-1.32188 0.28267,-2.33456 0.33848,-3.97443 0.0558,-1.63987 -0.0139,-3.906494 -0.24081,-5.853906 -0.22696,-1.947507 -0.60018,-3.886244 -1.11828,-5.808948 -0.51809,-1.922508 -1.18037,-3.826579 -1.98437,-5.705078 -0.80406,-1.878704 -1.74885,-3.729481 -2.83084,-5.545397 -1.08198,-1.815817 -2.29979,-3.594502 -3.64887,-5.329391 -1.349,-1.734967 -2.82759,-3.423968 -4.43022,-5.060675 -1.60267,-1.636582 -3.32737,-3.218822 -5.16764,-4.740793 -1.84031,-1.521963 -3.83669,-3.00806 -5.85339,-4.373893 -1.25586,-0.850545 -2.42193,-1.962964 -3.88194,-2.374016 -12.22507,-3.441843 -38.101024,0 -38.101024,0 z"
style="fill:#e164e1;fill-opacity:1;fill-rule:evenodd;stroke-width:0.26458332" />
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
inkscape:connector-curvature="0"
id="path865"
d="M 74.461382,58.118935 V 142.02957 H 109.991 V 58.118935 Z"
style="fill:#d4aa00;fill-rule:evenodd;stroke:#000000;stroke-width:2.16499996;stroke-miterlimit:4;stroke-dasharray:none" />
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
inkscape:connector-curvature="0"
style="fill:#d4aa00;fill-rule:evenodd;stroke:#000000;stroke-width:1.57742977;stroke-miterlimit:4;stroke-dasharray:none"
d="M 82.861049,57.825147 V 142.32336 H 101.59133 V 57.825147 Z"
id="path872" />
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
inkscape:connector-curvature="0"
id="path874"
d="M 91.470238,56.607143 V 141.27381"
style="fill:none;stroke:#000000;stroke-width:1.16499996;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
inkscape:connector-curvature="0"
id="path878"
d="M -146.65476,69.458334 H 74.083332"
style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
inkscape:connector-curvature="0"
id="path880"
d="M -148.16666,98.562498 H 74.839285"
style="fill:none;stroke:#000000;stroke-width:0.26685447px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
inkscape:connector-curvature="0"
id="path882"
d="m -146.65476,124.64285 c 221.494044,0 221.494044,-1.5119 221.494044,-1.5119"
style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
inkscape:connector-curvature="0"
id="path902"
d="m -262.35526,98.184334 -1.42266,0.618048 27.87272,12.660208 3.58428,1.62781 a 25.702381,21.544643 0 0 0 7.23056,-12.98629 l 0.0264,-0.243395 a 25.702381,21.544643 0 0 0 0.0698,-1.676381 z"
style="opacity:1;fill:#666666;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
transform="scale(0.26458333)"
inkscape:connector-curvature="0"
id="path915"
d="M -594.28571,242.48104 -630,222.57702 l 232.85714,-0.0307 c 128.07143,-0.0169 332.178574,-0.88418 453.571431,-1.92732 l 220.714289,-1.89662 v 20.47008 20.47008 h -414.05203 c -227.72863,0 -415.76434,0.61257 -417.85715,1.36126 -2.09281,0.7487 -19.87654,-7.59555 -39.51939,-18.54276 z"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2.15972996;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
transform="scale(0.26458333)"
inkscape:connector-curvature="0"
id="path919"
d="m -328.43869,533.96722 -312.72437,-1.44753 48.43867,-29.10458 48.43868,-29.10457 330,-1.62545 c 181.499996,-0.894 366.32142,-2.39569 410.71428,-3.33708 l 80.71429,-1.71164 v 34.58452 34.58451 l -146.42857,-0.69532 c -80.535719,-0.38243 -287.15455,-1.34671 -459.15298,-2.14286 z"
style="opacity:1;fill:#ffcc00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2.15972996;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
transform="scale(0.26458333)"
inkscape:connector-curvature="0"
id="path921"
d="m -594.81703,447.23188 c -19.93509,-11.23457 -36.99845,-21.20413 -37.91858,-22.15458 -0.92014,-0.95046 16.89601,-12.84332 39.59145,-26.42857 l 41.26442,-24.70047 h 414.51131 414.51129 v 45.25103 45.25104 l -103.32437,1.89182 c -56.8284,1.0405 -244.864119,1.76231 -417.85715,1.60402 l -314.53277,-0.28781 z"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2.15972996;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
transform="scale(0.26458333)"
inkscape:connector-curvature="0"
id="path923"
d="m -594.76332,346.13569 -44.76332,-24.77486 37.58925,-22.56956 c 20.67409,-12.41325 41.01606,-25.00965 45.20438,-27.99199 7.39409,-5.26506 19.60649,-5.42245 420.7455,-5.42245 h 413.13037 v 52.85714 52.85714 L -136.42857,371.00084 -550,370.91056 Z"
style="opacity:1;fill:#ffcc00;fill-opacity:1;fill-rule:evenodd;stroke:#ffcc00;stroke-width:2.15972996;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
transform="scale(0.26458333)"
inkscape:connector-curvature="0"
id="path927"
d="m -123.48163,532.3714 c -0.86726,-0.86727 -115.78988,-1.51455 -255.38358,-1.43841 -211.99773,0.11565 -252.8807,-0.48716 -248.18505,-3.6594 3.09193,-2.08882 22.97883,-14.15284 44.19312,-26.80893 l 38.57143,-23.01108 265.71428,-1.76416 c 146.14286,-0.97029 330.32143,-2.56695 409.28572,-3.54814 l 143.57142,-1.78397 v 31.79547 31.79548 H 76.190486 c -108.9524,0 -198.804826,-0.70959 -199.672116,-1.57686 z"
style="opacity:1;fill:#ffcc00;fill-opacity:1;fill-rule:evenodd;stroke:#ffcc00;stroke-width:2.15972996;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
<path
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
sodipodi:nodetypes="ccc"
inkscape:connector-curvature="0"
id="path931"
d="m 141.36309,140.51785 c -18.8988,-44.601185 -3.77976,-74.839279 -3.77976,-74.839279 l -3.77976,9.071428"
style="fill:none;stroke:#ffffff;stroke-width:0.465;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<flowRoot
inkscape:export-ydpi="362.10001"
inkscape:export-xdpi="362.10001"
transform="matrix(0.55651922,0,0,0.98776644,530.40574,-587.96682)"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:24px;line-height:1.25;font-family:'Estrangelo Edessa';-inkscape-font-specification:'Estrangelo Edessa, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.45992514;stroke-miterlimit:4;stroke-dasharray:none"
id="flowRoot933"
xml:space="preserve"><flowRegion
style="stroke:#ffffff;stroke-width:0.45992514;stroke-miterlimit:4;stroke-dasharray:none"
id="flowRegion935"><rect
style="stroke:#ffffff;stroke-width:0.45992514;stroke-miterlimit:4;stroke-dasharray:none"
y="665.37683"
x="-985.71429"
height="254.28572"
width="754.28571"
id="rect937" /></flowRegion><flowPara
id="flowPara939">Alfredit Designs</flowPara></flowRoot> </g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 15 KiB

9
spec/fixtures/images/tiny.svg vendored Normal file
View File

@ -0,0 +1,9 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg
xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
width="1.2in" height="0.9in"
viewBox="0 0 1200 900"
>
<line x1="20" x2="1000" y1="20" y2="700" stroke="#ff88ff" stroke-width="25"/>
</svg>

After

Width:  |  Height:  |  Size: 364 B

9
spec/fixtures/images/zero_sized.svg vendored Normal file
View File

@ -0,0 +1,9 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg
xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
width="0.0in" height="0.0in"
viewBox="0 0 120 90"
>
<line x1="20" x2="1000" y1="20" y2="700" stroke="#ff88ff" stroke-width="25"/>
</svg>

After

Width:  |  Height:  |  Size: 362 B

View File

@ -570,17 +570,50 @@ RSpec.describe UploadCreator do
end
end
describe "svg sizing" do
let(:svg_filename) { "pencil.svg" }
let(:svg_file) { file_from_fixtures(svg_filename) }
describe "svg sizes expressed in units other than pixels" do
let(:tiny_svg_filename) { "tiny.svg" }
let(:tiny_svg_file) { file_from_fixtures(tiny_svg_filename) }
it "should handle units in width and height" do
upload = UploadCreator.new(svg_file, svg_filename,
let(:massive_svg_filename) { "massive.svg" }
let(:massive_svg_file) { file_from_fixtures(massive_svg_filename) }
let(:zero_sized_svg_filename) { "zero_sized.svg" }
let(:zero_sized_svg_file) { file_from_fixtures(zero_sized_svg_filename) }
it "should be viewable when a dimension is a fraction of a unit" do
upload = UploadCreator.new(tiny_svg_file, tiny_svg_filename,
force_optimize: true,
).create_for(user.id)
expect(upload.width).to be > 100
expect(upload.height).to be > 100
expect(upload.width).to be > 50
expect(upload.height).to be > 50
expect(upload.thumbnail_width).to be <= SiteSetting.max_image_width
expect(upload.thumbnail_height).to be <= SiteSetting.max_image_height
end
it "should not be larger than the maximum thumbnail size" do
upload = UploadCreator.new(massive_svg_file, massive_svg_filename,
force_optimize: true,
).create_for(user.id)
expect(upload.width).to be > 50
expect(upload.height).to be > 50
expect(upload.thumbnail_width).to be <= SiteSetting.max_image_width
expect(upload.thumbnail_height).to be <= SiteSetting.max_image_height
end
it "should handle zero dimension files" do
upload = UploadCreator.new(zero_sized_svg_file, zero_sized_svg_filename,
force_optimize: true,
).create_for(user.id)
expect(upload.width).to be > 50
expect(upload.height).to be > 50
expect(upload.thumbnail_width).to be <= SiteSetting.max_image_width
expect(upload.thumbnail_height).to be <= SiteSetting.max_image_height
end
end