For small thumbnails, use 180x180 thumbnails scaled down to 150x150.
This is so we can get rid of 150x150 images and just use 180x180 for
both small and medium size thumbnails.
Also fix RSS feeds, XML sitemaps, and Discord embeds to use 360x360
thumbnails instead of 150x150 thumbnails.
Refactor controllers so that endpoint rate limits are declared locally,
with the endpoint, instead of globally, in a single method in ApplicationController.
This way an endpoint's rate limit is declared in the same file as the
endpoint itself.
This is so we can add fine-grained rate limits for certain GET requests.
Before rate limits were only for non-GET requests.
Make "show scores" setting persistent.
The setting is stored in a `post_preview_show_votes` cookie. This means
it's remembered on a per-device basis, but not on a per-account basis.
This is so users without an account can use the setting, and so you can
use different settings on desktop and mobile.
The `view=score` URL param has been replaced by `show_votes=true`. The
`show_votes` URL param overrides the `post_preview_show_votes` cookie.
This is used to provide higher resolution thumbnails for high pixel
density displays, such as phones or laptops. If your screen has a 2x
pixel density ratio, then 360x360 thumbnails will be rendered at 720x720
resolution.
We use WebP here because it's about 15% smaller than the equivalent
JPEG, and because if a device has a high enough pixel density to use
this, then it probably supports WebP.
720x720 thumbnails average about 36kb in size, compared to 20.35kb for
360x360 thumbnails and 7.55kb for 180x180 thumbnails.
Remove the SFTP file storage backend. Downstream users can use either
sshfs (which is what Danbooru now uses in production) or rclone instead.
The Ruby SFTP gem was much slower than sshfs.
These other formats aren't actually generated during upload, but support
for creating them is there.
Also tune the parameters for generating JPEGs:
* Use Q=85 instead of Q=90 because Q=85 enables 4:2:0 chroma
subsampling, while Q=90 doesn't use subsampling. Subsampling reduces
filesize by ~30% in most cases. It does reduce quality for certain
images, particularly for images with lots of bright red, but in most
cases the quality difference isn't noticeable.
* Enable several MozJPEG-specific options, including trellis
quantization and scan optimization. These reduce filesize without
reducing quality, at the cost of slower encoding times.
Calculate the dimensions of thumbnails ourselves instead of letting
libvips calculate them for us. This way we know the exact size of
thumbnails, so we can set the right width and height for <img> tags. If
we let libvips calculate thumbnail sizes for us, then we can't predict
the exact size of thumbnails, because sometimes libvips rounds numbers
differently than us.
The problem was that we were stripping color profiles from thumbnails,
but we weren't setting `export_profile: "srgb"` to convert images to
sRGB first. This resulted in wrong colors for images with non-sRGB color
profiles, such as Adobe RGB.
The fix is to convert images to sRGB when possible, while leaving CMYK
and greyscale images alone. We leave CMYK images alone because we can't
convert CMYK to sRGB without losing color. We leave greyscale images
alone if they don't have a color profile, that way they stay as
one-channel greyscale (or two-channel greyscale, in case of alpha)
instead of being converted to three-channel sRGB. However, if a
greyscale image has a color profile, then we have to convert to sRGB,
otherwise the colors would be wrong when we strip the profile.
We also have to set the import profile, otherwise images with broken
embedded color profiles won't have a fallback profile and may get
incorrect colors. In this case we also have to be careful, because we
can't specify an sRGB fallback for greyscale or CMYK images.
Make the `order:random` metatag truly randomize the search. Add a
`random:N` metatag that returns up to N random posts, like what
`order:random` did before.
`order:random` now returns the entire search in random order. Before it
just returned a pageful of pseudorandom posts. This will be more
accurate for small searches, but slower for large searches. If
`order:random` times out, try `random:N` instead.
The `random:N` metatag returns up to N pseudorandom posts. This is
faster than `order:random` for large searches, but for small searches,
it may return less than N posts, and the randomness may be biased. Some
posts may be more likely than others to appear. N must be between 0 and
200.
Also, `/posts?tags=touhou&random=1` now redirects to `/posts?tags=touhou+random:N`.
Before the `random=1` param acted like a free `order:random` tag; now it
redirects to a `random:N` search, so it counts against your tag limit.
Optimize metatag searches involving usernames, including user:,
approver:, appealer:, commenter:, upvoter:, etc.
Do `User.find_by_name` instead of `User.name_matches` because this
fetches the user upfront instead of doing it inside a subquery. Using a
subquery makes the SQL more complicated and leads to worse query plans.
This especially helps searches involving multiple username metatags.
Allow admins to remove votes on posts. This is for fixing vote abuse.
Votes can be removed by going to the vote list on the /post_votes page,
or by clicking on a post's score, then using the "Remove" option in the
"..." dropdown menu next to the vote.
Votes are soft-deleted - they're marked as deleted in the database, but
not fully deleted. Removed votes are only visible to admins, not to
regular users. When a vote is removed by an admin, it leaves a mod
action.
Technically it's possible to undelete votes, but there's no UI for it.
Optimize searches using the `search[user_name]=...` URL parameter. If
we're not doing a wildcard search, then do a regular user lookup, which
generates better SQL.
* Add a gap between thumbnails on mobile.
* Adjust CSS for scores and vote buttons.
* Include "Private favorites" as an incentive on the user upgrade page.
* Fix vote buttons not being visible beneath thumbnails on mobile.
* Fix the "Show scores" link not preserving the current page number.
* Fix vote buttons being unintentionally enabled for all thumbnails by default.
* Fix banned and restricted users being able to favorite posts by
tagging them with `fav:self`.
* Fix search engines being able to crawl /posts?view=score pages.
* Fix broken tests.
Add upvote and downvote buttons beneath thumbnails on the post index page.
This is disabled by default. To enable it, click the "..." menu in the top
right of the page, then click "Show scores".
This is currently a per-search setting, not an account setting. If you
enable it in one tab, it won't be enabled in other tabs.
Make private favorites and upvotes a Gold-only account option.
Existing Members with private favorites enabled are allowed to keep it
enabled, as long as they don't disable it. If they disable it, then they
can't re-enable it again without upgrading to Gold first.
This is a Gold-only option to prevent uploaders from creating multiple
accounts to upvote their own posts. If private upvotes were allowed for
Members, then it would be too easy to use fake accounts and private
upvotes to upvote your own posts.
Add `upvotes:N`, `downvotes:N`, `order:upvotes`, `order:downvotes`,
`order:upvotes_asc`, `order:downvotes_asc` metatags.
In the API, the field is called up_score / down_score. Here it's called
`upvotes` and `downvotes` because this should be easier to understand
for end users.
Note that internally, `down_score` is negative. A post that matches
`downvotes:>5` will have down_score < -5 internally.
Fix certain artist commentaries for foundation.app containing scrambled
characters. Apparently caused by the Nokogiri HTML5 parser not handling
UTF-8 input correctly when the encoding isn't explicitly set to UTF-8.
* Fix /proc not being mounted read-only.
* Fix other read-only directories not actually being mounted read-only.
This was because the mount(2) system call ignores mount flags when
creating a bind mount. The solution is to bind mount the directory
first, then remount it as read-only second.
Known bug: submounts still don't get mounted as read-only. That is, if
we're mounting /usr as read-only, and /usr has a submount of /usr/local,
then /usr/local won't get mounted as read-only.
Add a Sandbox class for running untrusted external programs like ffmpeg
or exiftool inside a sandbox. This uses Linux namespaces to run the
process in an isolated container, much like a Docker container. Unlike a
Docker container, we can use it to sandbox programs when Danbooru itself
is already running inside a Docker container.
This is also more restrictive than Docker in several ways:
* It has a system call filter that is more restrictive and more
customizable than Docker's filter by default. Even if the process
breaks out of the container, the syscall filter will limit what it can
do, even if it escalates to root.
* It blocks the use of setuid binaries, so the process can't use things
like sudo to escalate to root inside the sandbox.
* It blocks all network access inside the sandbox by default.
* All files in the container are read-only by default. The sandboxed
process can only communicate by writing to stdout.
See app/logical/sandbox.rb for more details.
This isn't actually enabled yet. It will be rolled out progressively to
ensure it doesn't break things.
Add a Ruby wrapper library around the libseccomp library. Seccomp is
used to restrict the syscalls a program can make. See comments in
app/logical/seccomp.rb for further details.
This is not used for anything yet. It's simply adding part of the
sandboxing infrastructure for later use.
Send all logs to stderr by default instead of stdout. Fixes a problem
where parsing the output of sandboxed commands could fail, because they
could contain Rails log messages in their stdout.
When we run a command in a sandbox, we call fork+exec to run the command
in the background so we can capture its output. If Rails prints
anything to stdout between the fork and exec calls, then it will be
inadvertently captured along with the command's output. This will break
parsing of the command's output. This can happen if warning messages are
printed by Rails while setting up the sandbox between the fork and exec
calls.
Writing to stderr is also more correct, since stdout is buffered by
default, which means logs could potentially be lost if the process dies
unexpectedly before the buffers are flushed. Stderr is unbuffered by
default, which means logs will always be output immediately.
Change the rules for automatically retiring aliases and implications:
* Retire aliases to tags that are empty, or that are for a general or
artist tag that hasn't received any new posts in the last two years.
* Retire implications from tags that are empty.
* Don't retire aliases or implications for character, copyright, or
meta tags any more, unless the tags are empty.
Fix `https://danbooru.donmai.us/artists.json?expires_in=300` failing with
an `'300' is not a valid duration` error. This call pattern is used by the
Translate Pixiv Tags userscript.
Caused by a5ed8c72, which changed the `age:N` metatag to require time
units, but this inadvertently changed the `expires_in` parameter to
require them too.
Using `expires_in` without time units is deprecated and will be removed
in the future.
Make it possible to reapprove failed BURs that removed aliases or
implications.
Before if a BUR failed midway through, and we tried to reapprove it,
then it would fail when it got to a `remove alias` line because the
alias had already been removed. Now we keep going if we try to remove an
alias or implication that has already been removed.
* Change `age:` metatag to require time units. This means e.g.
`age:<600` no longer works; instead you have to say `age:<600sec`.
* Allow time units in the `age:` metatag to be abbreviated as long as
they're unambiguous. This means `age:<60sec`, `age:<5min`, and
`age:<5mon` now work, in addition to `age:<60s` and `age:<60seconds`.
* Allow the `ratio:` metatag to be written like `ratio:16/9` in addition
to `ratio:16:9`.
* Fix invalid date searches like `date:foo` or `date:05-15-2021`
to return nothing instead of raising an "undefined method
'beginning_of_day' for nil" exception. (`date:05-15-2021` is invalid
because it's parsed as DD-MM-YYYY).
* Fix invalid searches like `score:foo`, `ratio:foo`, and `mpixels:foo`
to return nothing instead of being treated like `score:0`, `ratio:0`,
`mpixels:0`.
* Fix `age:<60m` to return nothing instead of silently being treated
like `age:<60seconds`.
* Fix `age:foo` to return nothing instead of silently being treated like
`age:0d` (return all uploads from today).
Fixes#4389.
The favorites table is too big and dumping it tends to time out. Then
the job keeps retrying even though it always fails, then multiple
instances of the job build up in the job queue because the old jobs
never finish.