Project

General

Profile

Actions

Feature #44697

open

Show link previews (title, description and thumbnail) for URLs in issues and wiki pages

Added by Kohei Nakamura about 5 hours ago.

Status:
New
Priority:
Normal
Assignee:
-
Category:
Text formatting
Target version:
-
Resolution:

Description

Summary

URLs pasted into issues, notes and wiki pages are shown only as plain links, so readers cannot tell what a link points to without opening it.
I would like to propose link previews ("link cards"), similar to those shown by Slack, Microsoft Teams and Mattermost: the page title, a short description, the site name and favicon, and a thumbnail, taken from the page's Open Graph metadata.

The key point for Redmine is that Redmine fetches the metadata itself. No third-party preview API or service is used and no new gem is required, so this also works for on-premises installations that must not send URLs to external services.

I have implemented this as a plugin and have been using it with Redmine 6.1 and RedMica 3.2. A screenshot is attached.

Proposed behavior

  • A macro always shows a preview:
    {{link_preview(https://www.redmine.org/)}}
    
  • Optional setting (off by default): a paragraph that contains nothing but a bare URL is shown as a preview. URLs inside sentences and links with their own text ("text":url, [text](url)) stay plain links.
  • Metadata: og:title, og:description, og:image and og:site_name, falling back to Twitter Card tags, <title>, <meta name="description"> and the favicon. Legacy encodings such as Shift_JIS and EUC-JP are supported.
  • The first time a URL is shown, a placeholder is rendered and the preview is loaded asynchronously, so a slow remote site never blocks page rendering. Results are cached in the database and refreshed in the background after a configurable number of days.
  • The thumbnail and favicon are downloaded and resized on the server (with ImageMagick, as for attachment thumbnails) and served by Redmine. Viewers' browsers never contact the linked site.
  • E-mail notifications, PDF export, Atom feeds and wiki HTML export keep a plain link.
  • If a page cannot be fetched (a PDF file, a site that blocks bots, a timeout, ...), a plain link is shown with the reason as a tooltip.
  • The domain shown in the preview always comes from the URL itself, never from the page, so a page cannot pretend to be another site.

Security considerations

Because the server fetches URLs written by users, the implementation includes the following protections:

  • SSRF: loopback, private, link-local (including cloud metadata such as 169.254.169.254), multicast and other special-purpose addresses are refused, including IPv4-mapped IPv6 and decimal notations. Every redirect is checked again, and the connection is made to the address that was checked, which prevents DNS rebinding. Administrators can explicitly allow specific internal hosts.
  • No open proxy: the endpoint that builds a preview only accepts URLs signed by Redmine (HMAC), i.e. URLs that actually appear in content.
  • Secret URLs are never fetched: URLs with query parameters that look like credentials (token, pwd, key, signature, access_token, X-Amz-Signature, ...) stay plain links, because fetching them could consume one-time links or leak shared links.
  • Allow and deny lists: administrators can exclude hosts such as web meeting or file sharing services, or restrict previews to listed hosts.
  • Limits: HTML is read up to 1 MB and reading stops at </head>; images up to 5 MB; at most 5 redirects; 20 seconds in total per URL.
  • Images: only PNG, JPEG, GIF, WebP and ICO, detected by content rather than by the Content-Type header. SVG is never accepted. Images with huge dimensions ("pixel bombs") are rejected before decoding, and images are served with X-Content-Type-Options: nosniff and Content-Security-Policy: sandbox.
  • All text taken from remote pages is escaped.

Known trade-offs: the linked site sees a request coming from the Redmine server, and a site may later change its title or image. For these reasons I think the automatic mode should be off by default, while the explicit macro is always available.

Administration settings

  • Show bare URL paragraphs as previews (default: off)
  • Show images (default: on)
  • Allowed hosts / denied hosts
  • Hosts allowed on private networks
  • Cache lifetime in days (default: 7)
  • User-Agent
  • HTTP proxy for outgoing requests
  • Button to clear the cache

Reference implementation

  • Plugin: redmine_link_preview 1.1.1 (attached: redmine_link_preview-1.1.1.zip, see README.md for installation)
  • Tested with Redmine 6.1.5 and RedMica 3.2.3 (based on Redmine 6.0), on MySQL 8.0 and SQLite, including installations under a sub-URI such as /redmine
  • Uses only libraries Redmine already depends on (Nokogiri, Addressable) and Ruby's Net::HTTP; ImageMagick is optional
  • Includes 75 tests covering SSRF checks, redirects, encodings, image handling, escaping, and rendering with both Textile and CommonMark

Points for discussion

  • The macro name and syntax, or whether a dedicated text formatting syntax would be preferred
  • Storing thumbnails in the database (as the plugin does now) or under the files directory
  • Whether this feature belongs in core or should remain a plugin

Related to #15333 (oEmbed support). That proposal embeds content from a whitelist of providers using an additional gem; this proposal is generic (any page with Open Graph metadata or a <title>) and needs no additional dependency.

If this direction is acceptable, I would like to prepare a patch against trunk that implements this feature in core.


Files

No data to display

Actions

Also available in: Atom PDF