Skip to content

FEATURE: automatically downsize large images - #1

Open
everettbu wants to merge 1 commit into
image-processing-optimizationfrom
large-image-processing
Open

FEATURE: automatically downsize large images#1
everettbu wants to merge 1 commit into
image-processing-optimizationfrom
large-image-processing

Conversation

@everettbu

Copy link
Copy Markdown
Contributor

Test 1

@mfeuerstein mfeuerstein left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review — approved

Reviewed 3 files. 0 high-severity issues found. Verdict: approved.

app/assets/javascripts/discourse/lib/utilities.js (low)

  • Reviewed app/assets/javascripts/discourse/lib/utilities.js — looks good

app/models/optimized_image.rb (low)

  • Reviewed app/models/optimized_image.rb — looks good

app/controllers/uploads_controller.rb (low)

  • Reviewed app/controllers/uploads_controller.rb — looks good

@zach-source zach-source left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 4 blocking issues.

  • critical app/models/optimized_image.rb:147 — Duplicate downsize definition — original 4-arg overload is silently replaced, breaking all existing callers
  • high app/controllers/uploads_controller.rb:55 — URL-sourced download limit hardcoded to 10 MB, ignoring SiteSetting.max_image_size_kb
  • high app/assets/javascripts/discourse/lib/utilities.js:182 — Client-side size check hardcoded to 10 MB, bypassing per-type site settings
  • medium app/assets/javascripts/discourse/lib/utilities.js:246 — 413 error handler shows hardcoded 10 MB instead of actual server limit

def self.downsize(from, to, max_width, max_height, opts={})
optimize("downsize", from, to, max_width, max_height, opts)
optimize("downsize", from, to, "#{max_width}x#{max_height}", opts)
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[critical] Duplicate downsize definition — original 4-arg overload is silently replaced, breaking all existing callers

Ruby uses the last definition when the same method name appears twice. The diff adds a new 3-arg def self.downsize(from, to, dimensions, opts={}) but the prior def self.downsize(from, to, max_width, max_height, opts={}) was already rewritten in the same hunk (line 143) to delegate to optimize with "#{max_width}x#{max_height}". The net result is two self.downsize definitions: the one at ~143 (4-arg) and the new one at ~147 (3-arg). In Ruby the 3-arg version wins, so any caller still passing downsize(from, to, width, height) — including the now-modified resize-path callers anywhere else in the codebase — will silently pass a numeric max_width as dimensions, producing a malformed ImageMagick argument and a silent false return from convert_with.

Suggestion: Remove the duplicate 4-arg downsize definition entirely; keep only the 3-arg form and update all call sites to pre-format the dimension string before calling.

# API can provide a URL
if file.nil? && url.present? && is_api?
tempfile = FileHelper.download(url, SiteSetting.max_image_size_kb.kilobytes, "discourse-upload-#{type}") rescue nil
tempfile = FileHelper.download(url, 10.megabytes, "discourse-upload-#{type}") rescue nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[high] URL-sourced download limit hardcoded to 10 MB, ignoring SiteSetting.max_image_size_kb

The original code used SiteSetting.max_image_size_kb.kilobytes as the download size cap for API URL uploads. The diff replaces it with the literal 10.megabytes. This means admins who configure max_image_size_kb to a value smaller than 10 MB (e.g., 1 MB) will have that limit silently bypassed for API uploads — an API caller can now download and store images up to 10 MB regardless of site policy. It also means FileHelper.download fetches up to 10 MB before the downsize loop even runs, wasting bandwidth for non-image types that are not subject to the downsize path.

Suggestion: Keep using SiteSetting.max_image_size_kb.kilobytes as the download cap, or use [SiteSetting.max_image_size_kb.kilobytes, 10.megabytes].max if the intent is to allow downloading larger files for subsequent downsize.

Suggested change
tempfile = FileHelper.download(url, 10.megabytes, "discourse-upload-#{type}") rescue nil
tempfile = FileHelper.download(url, [SiteSetting.max_image_size_kb.kilobytes, 10.megabytes].max, "discourse-upload-#{type}") rescue nil

// check file size
var fileSizeKB = file.size / 1024;
var maxSizeKB = Discourse.SiteSettings['max_' + type + '_size_kb'];
var maxSizeKB = 10 * 1024; // 10MB

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[high] Client-side size check hardcoded to 10 MB, bypassing per-type site settings

The original code used Discourse.SiteSettings['max_' + type + '_size_kb'] to enforce per-type limits (image vs. attachment). The diff replaces this with a hardcoded 10 * 1024 (10 MB) for all types. This means: (1) non-image file types (attachments) that have a site setting smaller than 10 MB are no longer client-side validated — users see no error until the server rejects them; (2) the maxSizeKB value passed to the file_too_large i18n message will always show "10240 KB" regardless of the actual configured limit, confusing users.

Suggestion: Restore the dynamic lookup: var maxSizeKB = Discourse.SiteSettings['max_' + type + '_size_kb']; — or, if the intent is to allow large image uploads for server-side downsize, special-case only image types.

Suggested change
var maxSizeKB = 10 * 1024; // 10MB
var maxSizeKB = type === 'image' ? 10 * 1024 : Discourse.SiteSettings['max_' + type + '_size_kb'];

// entity too large, usually returned from the web server
case 413:
var maxSizeKB = Discourse.SiteSettings.max_image_size_kb;
var maxSizeKB = 10 * 1024; // 10 MB

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[medium] 413 error handler shows hardcoded 10 MB instead of actual server limit

The 413 (Request Entity Too Large) handler is supposed to tell the user the configured maximum. By hardcoding 10 * 1024 here, any site with max_image_size_kb set differently will show the wrong limit in the error dialog. This is misleading: the server rejected the upload based on its configured limit, but the client reports a different number.

Suggestion: Restore var maxSizeKB = Discourse.SiteSettings.max_image_size_kb;

Suggested change
var maxSizeKB = 10 * 1024; // 10 MB
var maxSizeKB = Discourse.SiteSettings.max_image_size_kb;

@zach-source zach-source left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method overloading, missing error handling, hardcoded upload limits.

optimize("downsize", from, to, max_width, max_height, opts)
optimize("downsize", from, to, "#{max_width}x#{max_height}", opts)
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ruby method overloading: second downsize replaces the first. All four-arg callers will fail silently.

Remove the old definition entirely; keep only the new 3-argument version.

# allow users to upload large images that will be automatically reduced to allowed size
if tempfile && tempfile.size > 0 && SiteSetting.max_image_size_kb > 0 && FileHelper.is_image?(filename)
attempt = 5
while attempt > 0 && tempfile.size > SiteSetting.max_image_size_kb.kilobytes

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return value ignored: if all downsize calls fail, loop exhausts attempts and uploads oversized file anyway.

          break unless OptimizedImage.downsize(tempfile.path, tempfile.path, "80%", allow_animation: SiteSetting.allow_animated_thumbnails)
          attempt -= 1

// check file size
var fileSizeKB = file.size / 1024;
var maxSizeKB = Discourse.SiteSettings['max_' + type + '_size_kb'];
var maxSizeKB = 10 * 1024; // 10MB

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded 10MB bypasses admin-configured limits for non-image uploads.

Suggested change
var maxSizeKB = 10 * 1024; // 10MB
var maxSizeKB = (type === 'image') ? (10 * 1024) : Discourse.SiteSettings['max_' + type + '_size_kb'];
Suggested change
var maxSizeKB = 10 * 1024; // 10MB
var maxSizeKB = (type === 'image') ? 10 * 1024 : Discourse.SiteSettings['max_' + type + '_size_kb'];

// entity too large, usually returned from the web server
case 413:
var maxSizeKB = Discourse.SiteSettings.max_image_size_kb;
var maxSizeKB = 10 * 1024; // 10 MB

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error message shows hardcoded 10MB instead of actual admin-configured limit.

Suggested change
var maxSizeKB = 10 * 1024; // 10 MB
var maxSizeKB = Discourse.SiteSettings.max_image_size_kb;
Suggested change
var maxSizeKB = 10 * 1024; // 10 MB
var maxSizeKB = Discourse.SiteSettings.max_image_size_kb;

@zach-source zach-source left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method shadowing causes parameter mismatch; resize loop proceeds even when images don't shrink; size limits hardcoded throughout, ignoring site settings — see inline.


def self.optimize(operation, from, to, width, height, opts={})
dim = dimensions(width, height)
def self.downsize(from, to, dimensions, opts={})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method downsize defined twice — 3-arg version shadows the 4-arg version at line 145. Remove the redundant 4-arg method (lines 145–147) since both convert dimensions the same way.

# allow users to upload large images that will be automatically reduced to allowed size
if tempfile && tempfile.size > 0 && SiteSetting.max_image_size_kb > 0 && FileHelper.is_image?(filename)
attempt = 5
while attempt > 0 && tempfile.size > SiteSetting.max_image_size_kb.kilobytes

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image re-encoding can increase file size (format overhead). Loop runs 5 attempts regardless of shrinkage, then uploads oversized file. Track previous size and break if unchanged:

// check file size
var fileSizeKB = file.size / 1024;
var maxSizeKB = Discourse.SiteSettings['max_' + type + '_size_kb'];
var maxSizeKB = 10 * 1024; // 10MB

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded 10 MB check ignores max_image_size_kb site setting. Sites with lower limits bypass validation entirely. Read the site setting instead:

Suggested change
var maxSizeKB = 10 * 1024; // 10MB
var maxSizeKB = Discourse.SiteSettings['max_' + type + '_size_kb'];

// entity too large, usually returned from the web server
case 413:
var maxSizeKB = Discourse.SiteSettings.max_image_size_kb;
var maxSizeKB = 10 * 1024; // 10 MB

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error message hardcoded to 10240 KB, ignoring actual max_image_size_kb setting. Users see wrong limit if configured differently. Restore the site-setting read:

Suggested change
var maxSizeKB = 10 * 1024; // 10 MB
var maxSizeKB = Discourse.SiteSettings.max_image_size_kb;

# API can provide a URL
if file.nil? && url.present? && is_api?
tempfile = FileHelper.download(url, SiteSetting.max_image_size_kb.kilobytes, "discourse-upload-#{type}") rescue nil
tempfile = FileHelper.download(url, 10.megabytes, "discourse-upload-#{type}") rescue nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Download cap hardcoded to 10 MB ignores max_image_size_kb setting. Wastes bandwidth on small-limit sites, truncates valid uploads on large-limit sites. Use proportional cap:

Suggested change
tempfile = FileHelper.download(url, 10.megabytes, "discourse-upload-#{type}") rescue nil
tempfile = FileHelper.download(url, [SiteSetting.max_image_size_kb.kilobytes * 5, 10.megabytes].max, "discourse-upload-#{type}") rescue nil

@zach-source zach-source left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method redefinition creates runtime errors; hard-coded limits override site settings; confusing error message. See inline.

optimize("downsize", from, to, max_width, max_height, opts)
optimize("downsize", from, to, "#{max_width}x#{max_height}", opts)
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate downsize method—line 145 already defines it. Ruby keeps only the last definition (line 148), making the 4-arg version unreachable and breaking existing callers. Remove lines 145–147.

// check file size
var fileSizeKB = file.size / 1024;
var maxSizeKB = Discourse.SiteSettings['max_' + type + '_size_kb'];
var maxSizeKB = 10 * 1024; // 10MB

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded 10 MB override ignores per-file-type site settings. Admins' max_image_size_kb and max_attachment_size_kb now ignored. Restore:

Suggested change
var maxSizeKB = 10 * 1024; // 10MB
var maxSizeKB = Discourse.SiteSettings['max_' + type + '_size_kb'];
Suggested change
var maxSizeKB = 10 * 1024; // 10MB
var maxSizeKB = Discourse.SiteSettings['max_' + type + '_size_kb'] || 10 * 1024;

// entity too large, usually returned from the web server
case 413:
var maxSizeKB = Discourse.SiteSettings.max_image_size_kb;
var maxSizeKB = 10 * 1024; // 10 MB

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded error message (10240 KB) hides actual server limit, confusing users if different. Use:

Suggested change
var maxSizeKB = 10 * 1024; // 10 MB
var maxSizeKB = Discourse.SiteSettings.max_image_size_kb;
Suggested change
var maxSizeKB = 10 * 1024; // 10 MB
var maxSizeKB = Discourse.SiteSettings.max_image_size_kb || 10 * 1024;

optimize("resize", from, to, "#{width}x#{height}", opts)
end

def self.downsize(from, to, max_width, max_height, opts={})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead-code method—unreachable since line 148 redefines downsize with a different arity (3 args vs 4 args). Delete:


@zach-source zach-source left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method collision and hardcoded limits flagged inline.

def self.downsize(from, to, max_width, max_height, opts={})
optimize("downsize", from, to, max_width, max_height, opts)
optimize("downsize", from, to, "#{max_width}x#{max_height}", opts)
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two downsize methods with same name — Ruby uses last definition, breaking old 4-arg callers. Delete the old definition and update all callers to pre-format dimensions as "WIDTHxHEIGHT" string (or percentage like "80%").

// check file size
var fileSizeKB = file.size / 1024;
var maxSizeKB = Discourse.SiteSettings['max_' + type + '_size_kb'];
var maxSizeKB = 10 * 1024; // 10MB

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded 10 MB limit replaces per-type server setting. Error message shows wrong limit, breaks client-side validation for non-image types. Use Discourse.SiteSettings['max_' + type + '_size_kb']:

Suggested change
var maxSizeKB = 10 * 1024; // 10MB
var maxSizeKB = Discourse.SiteSettings['max_' + type + '_size_kb'];

// entity too large, usually returned from the web server
case 413:
var maxSizeKB = Discourse.SiteSettings.max_image_size_kb;
var maxSizeKB = 10 * 1024; // 10 MB

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded 10 MB limit replaces max_image_size_kb setting. Error message shows wrong limit if admin configured lower value. Use Discourse.SiteSettings.max_image_size_kb:

Suggested change
var maxSizeKB = 10 * 1024; // 10 MB
var maxSizeKB = Discourse.SiteSettings.max_image_size_kb;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants