Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds inline rendering support for iCloud shared photo links. Previously, iCloud links were displayed as plain text links, but now they are enhanced to show images inline for single photos or rich preview cards for albums using OpenGraph metadata.
Key Changes:
- Detects iCloud photo links and fetches their HTML content to extract image URLs or OpenGraph metadata
- Renders single photos inline using extracted image URLs from Apple's page structure
- Displays album previews with images, titles, and descriptions using OpenGraph tags
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
htdocs/lib/tumble.pm
Outdated
| if ($is_album && $og_image) { | ||
| my $preview_html = '<div style="border: 1px solid #ddd; border-radius: 4px; padding: 10px; max-width: 500px; background: #f9f9f9;">'; | ||
| if ($og_image) { | ||
| $preview_html .= '<img src="' . $og_image . '" style="max-width: 100%; height: auto; border-radius: 4px; margin-bottom: 8px;" alt="' . ($og_title || 'Apple Photos album') . '">'; |
There was a problem hiding this comment.
The og_image URL from external content is directly embedded into HTML without sanitization, creating an XSS vulnerability. An attacker could craft a malicious iCloud page with JavaScript in the og:image meta tag. The URL should be HTML-escaped before being inserted into the img src attribute.
htdocs/lib/tumble.pm
Outdated
| if ($is_album && $og_image) { | ||
| my $preview_html = '<div style="border: 1px solid #ddd; border-radius: 4px; padding: 10px; max-width: 500px; background: #f9f9f9;">'; | ||
| if ($og_image) { | ||
| $preview_html .= '<img src="' . $og_image . '" style="max-width: 100%; height: auto; border-radius: 4px; margin-bottom: 8px;" alt="' . ($og_title || 'Apple Photos album') . '">'; |
There was a problem hiding this comment.
The og_title variable from external content is directly embedded into HTML without escaping, creating an XSS vulnerability. An attacker could inject malicious JavaScript through the og:title meta tag. The value should be HTML-escaped before being inserted into the alt attribute.
htdocs/lib/tumble.pm
Outdated
| # For Apple Photos links, fetch the page to extract the actual image URL or OpenGraph data | ||
| # This allows us to render images inline instead of requiring users to click through | ||
| if ($is_apple_photos && $data->{$item}->{'user'} !~ /nsfw|otd/) { | ||
| use LWP::UserAgent; |
There was a problem hiding this comment.
The 'use' statement should be placed at the top of the file with other module imports, not inside a conditional block. Placing it here means it's compiled every time this code path is executed, which is inefficient. Move this to the top of the file with the other use statements.
| # This maintains functionality even if Apple changes their page structure | ||
| $link_filler = $data->{$item}->{'title'}; | ||
| } | ||
| } |
There was a problem hiding this comment.
When the HTTP request fails (is_success returns false), there's no error handling or fallback behavior. The code silently continues with the default link_filler, which could be confusing if the request fails for unexpected reasons. Consider logging errors or providing feedback about failed requests.
| } | |
| } | |
| } else { | |
| # Log HTTP failures when attempting to fetch Apple Photos content | |
| warn "Failed to fetch Apple Photos URL '$apple_photos_url': " . $response->status_line; |
htdocs/lib/tumble.pm
Outdated
| if ($html =~ /<meta\s+property=["']og:image["']\s+content=["']([^"']+)["']/i) { | ||
| $og_image = $1; | ||
| $is_album = 1; | ||
| } | ||
| if ($html =~ /<meta\s+property=["']og:title["']\s+content=["']([^"']+)["']/i) { | ||
| $og_title = $1; | ||
| } | ||
| if ($html =~ /<meta\s+property=["']og:description["']\s+content=["']([^"']+)["']/i) { | ||
| $og_description = $1; | ||
| } |
There was a problem hiding this comment.
The regex patterns for extracting OpenGraph meta tags are fragile and may fail with variations in HTML formatting such as different quote styles, attribute ordering, or whitespace. Consider using an HTML parser like HTML::Parser or Mojo::DOM for more robust HTML parsing instead of regex-based extraction.
htdocs/lib/tumble.pm
Outdated
| my $preview_html = '<div style="border: 1px solid #ddd; border-radius: 4px; padding: 10px; max-width: 500px; background: #f9f9f9;">'; | ||
| if ($og_image) { | ||
| $preview_html .= '<img src="' . $og_image . '" style="max-width: 100%; height: auto; border-radius: 4px; margin-bottom: 8px;" alt="' . ($og_title || 'Apple Photos album') . '">'; | ||
| } | ||
| if ($og_title) { | ||
| $preview_html .= '<div style="font-weight: bold; margin-bottom: 4px;">' . $og_title . '</div>'; | ||
| } | ||
| if ($og_description) { | ||
| $preview_html .= '<div style="font-size: 0.9em; color: #666;">' . $og_description . '</div>'; |
There was a problem hiding this comment.
Inline CSS styles are hardcoded throughout the preview card HTML, making it difficult to maintain consistent styling and impossible to update via CSS without modifying code. Consider using CSS classes instead of inline styles for better maintainability and separation of concerns.
| my $preview_html = '<div style="border: 1px solid #ddd; border-radius: 4px; padding: 10px; max-width: 500px; background: #f9f9f9;">'; | |
| if ($og_image) { | |
| $preview_html .= '<img src="' . $og_image . '" style="max-width: 100%; height: auto; border-radius: 4px; margin-bottom: 8px;" alt="' . ($og_title || 'Apple Photos album') . '">'; | |
| } | |
| if ($og_title) { | |
| $preview_html .= '<div style="font-weight: bold; margin-bottom: 4px;">' . $og_title . '</div>'; | |
| } | |
| if ($og_description) { | |
| $preview_html .= '<div style="font-size: 0.9em; color: #666;">' . $og_description . '</div>'; | |
| my $preview_html = '<div class="preview-card">'; | |
| if ($og_image) { | |
| $preview_html .= '<img src="' . $og_image . '" class="preview-card-image" alt="' . ($og_title || 'Apple Photos album') . '">'; | |
| } | |
| if ($og_title) { | |
| $preview_html .= '<div class="preview-card-title">' . $og_title . '</div>'; | |
| } | |
| if ($og_description) { | |
| $preview_html .= '<div class="preview-card-description">' . $og_description . '</div>'; |
htdocs/lib/tumble.pm
Outdated
| if ($html =~ /<img[^>]+src=["']([^"']+\.(jpg|jpeg|png|gif|webp))["']/i) { | ||
| $img_url = $1; | ||
| # Convert relative URLs to absolute | ||
| if ($img_url !~ /^https?:/) { | ||
| $img_url = 'https://www.icloud.com' . $img_url if $img_url =~ /^\//; | ||
| } | ||
| } | ||
| # Pattern 2: Look for image URLs in JSON data structures | ||
| elsif ($html =~ /"url":"([^"]+\.(jpg|jpeg|png|gif|webp))"/i) { | ||
| $img_url = $1; | ||
| $img_url =~ s/\\\//\//g; # Unescape JSON-encoded slashes | ||
| } | ||
| # Pattern 3: Look for downloadURL in JSON (often contains the full image URL) | ||
| elsif ($html =~ /"downloadURL":"([^"]+)"/i) { | ||
| $img_url = $1; | ||
| $img_url =~ s/\\\//\//g; # Unescape JSON-encoded slashes | ||
| } |
There was a problem hiding this comment.
The regex patterns for extracting image URLs from HTML and JSON are fragile and may fail with formatting variations or changes to Apple's page structure. Consider using proper HTML and JSON parsers (HTML::Parser and JSON module) for more robust and reliable extraction.
htdocs/lib/tumble.pm
Outdated
| if ($html =~ /<meta\s+property=["']og:image["']\s+content=["']([^"']+)["']/i) { | ||
| $og_image = $1; | ||
| $is_album = 1; | ||
| } | ||
| if ($html =~ /<meta\s+property=["']og:title["']\s+content=["']([^"']+)["']/i) { | ||
| $og_title = $1; | ||
| } | ||
| if ($html =~ /<meta\s+property=["']og:description["']\s+content=["']([^"']+)["']/i) { | ||
| $og_description = $1; | ||
| } | ||
|
|
||
| # Handle albums: render a rich preview card with image, title, and description | ||
| # This gives users a better sense of what's in the album before clicking | ||
| if ($is_album && $og_image) { | ||
| my $preview_html = '<div style="border: 1px solid #ddd; border-radius: 4px; padding: 10px; max-width: 500px; background: #f9f9f9;">'; | ||
| if ($og_image) { | ||
| $preview_html .= '<img src="' . $og_image . '" style="max-width: 100%; height: auto; border-radius: 4px; margin-bottom: 8px;" alt="' . ($og_title || 'Apple Photos album') . '">'; | ||
| } | ||
| if ($og_title) { | ||
| $preview_html .= '<div style="font-weight: bold; margin-bottom: 4px;">' . $og_title . '</div>'; | ||
| } | ||
| if ($og_description) { | ||
| $preview_html .= '<div style="font-size: 0.9em; color: #666;">' . $og_description . '</div>'; | ||
| } |
There was a problem hiding this comment.
Values such as og_image, og_title, and og_description are extracted from remote HTML and concatenated directly into $preview_html and then into $content without any HTML escaping or sanitization. Because the upstream page is fetched from a URL that can be attacker-controlled (see the Apple Photos URL handling above), an attacker can craft OpenGraph tags like <meta property="og:title" content=""><script>...</script"> so that malicious HTML/JavaScript is injected into your page, resulting in stored XSS for all viewers. Treat these fields as untrusted: HTML-encode or otherwise sanitize them before including in your markup, or only insert them into contexts where the templating layer escapes by default and avoid concatenating raw HTML strings from remote pages.
| use LWP::UserAgent; | ||
| # Set up HTTP client with appropriate user agent to avoid being blocked | ||
| my $ua = LWP::UserAgent->new( | ||
| ssl_opts => { verify_hostname => 0 }, |
There was a problem hiding this comment.
The HTTP client is created with ssl_opts => { verify_hostname => 0 }, which disables TLS hostname verification for Apple Photos fetches. This means the certificate’s hostname is not checked against the requested host, weakening HTTPS and enabling certain man-in-the-middle scenarios where an attacker with a valid certificate for another domain can intercept and supply malicious HTML that your application will trust and render. Avoid turning off hostname verification; instead rely on the default certificate and hostname checks or address specific certificate issues with a more targeted trust configuration.
| ssl_opts => { verify_hostname => 0 }, |
htdocs/lib/tumble.pm
Outdated
| if ($data->{$item}->{'url'} =~ /icloud\.com\/photos.*icloudlinks/i) { | ||
| $is_apple_photos = 1; | ||
| $apple_photos_url = $data->{$item}->{'url'}; | ||
| } | ||
|
|
||
| # For Apple Photos links, fetch the page to extract the actual image URL or OpenGraph data | ||
| # This allows us to render images inline instead of requiring users to click through | ||
| if ($is_apple_photos && $data->{$item}->{'user'} !~ /nsfw|otd/) { | ||
| use LWP::UserAgent; | ||
| # Set up HTTP client with appropriate user agent to avoid being blocked | ||
| my $ua = LWP::UserAgent->new( | ||
| ssl_opts => { verify_hostname => 0 }, | ||
| timeout => 5 | ||
| ); | ||
| $ua->agent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'); | ||
| my $response = $ua->get($apple_photos_url); |
There was a problem hiding this comment.
This block treats any url containing icloud.com/photos and icloudlinks as an Apple Photos URL and passes it directly to LWP::UserAgent->get via apple_photos_url, without validating the actual scheme/host. An attacker can craft a URL like http://169.254.169.254/icloud.com/photos/.../icloudlinks or https://attacker.example/icloud.com/photos/.../icloudlinks so it matches the regex, causing the server to make arbitrary HTTP(S) requests to internal or attacker-controlled services (SSRF) and process the returned HTML. To prevent SSRF, strictly parse and validate the URL (e.g., enforce https scheme, require an exact host like www.icloud.com, and reject IP literals and internal hosts) before issuing the request, or derive the fetch URL from a trusted base plus a validated identifier rather than using the full user-supplied URL.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 9 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if ($is_apple_photos && $data->{$item}->{'user'} !~ /nsfw|otd/) { | ||
| # Set up HTTP client with appropriate user agent to avoid being blocked | ||
| my $ua = LWP::UserAgent->new( | ||
| ssl_opts => { verify_hostname => 0 }, | ||
| timeout => 5 | ||
| ); | ||
| $ua->agent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'); | ||
| my $response = $ua->get($apple_photos_url); | ||
| if ($response->is_success) { | ||
| my $html = $response->content; | ||
| my $is_album = 0; | ||
| my $og_image = ''; | ||
| my $og_title = ''; | ||
| my $og_description = ''; | ||
|
|
||
| # Check for OpenGraph meta tags - these indicate an album or shared collection | ||
| # Single photos typically don't have OpenGraph tags, albums do | ||
| if ($html =~ /<meta\s+property=["']og:image["']\s+content=["']([^"']+)["']/i) { | ||
| $og_image = $1; | ||
| $is_album = 1; | ||
| } | ||
| if ($html =~ /<meta\s+property=["']og:title["']\s+content=["']([^"']+)["']/i) { | ||
| $og_title = $1; | ||
| } | ||
| if ($html =~ /<meta\s+property=["']og:description["']\s+content=["']([^"']+)["']/i) { | ||
| $og_description = $1; | ||
| } | ||
|
|
||
| # Handle albums: render a rich preview card with image, title, and description | ||
| # This gives users a better sense of what's in the album before clicking | ||
| if ($is_album && $og_image) { | ||
| my $preview_html = '<div style="border: 1px solid #ddd; border-radius: 4px; padding: 10px; max-width: 500px; background: #f9f9f9;">'; | ||
| if ($og_image) { | ||
| # Escape HTML in alt text to prevent XSS | ||
| my $escaped_title = $og_title ? encode_entities($og_title) : 'Apple Photos album'; | ||
| $preview_html .= '<img src="' . encode_entities($og_image) . '" style="max-width: 100%; height: auto; border-radius: 4px; margin-bottom: 8px;" alt="' . $escaped_title . '">'; | ||
| } | ||
| if ($og_title) { | ||
| $preview_html .= '<div style="font-weight: bold; margin-bottom: 4px;">' . encode_entities($og_title) . '</div>'; | ||
| } | ||
| if ($og_description) { | ||
| $preview_html .= '<div style="font-size: 0.9em; color: #666;">' . encode_entities($og_description) . '</div>'; | ||
| } | ||
| $preview_html .= '</div>'; | ||
| $link_filler = $preview_html; | ||
| } else { | ||
| # Handle single photos: extract the direct image URL from the page HTML | ||
| # We try multiple patterns because Apple's HTML structure may vary | ||
| my $img_url = ''; | ||
| # Pattern 1: Look for <img> tags with image file extensions | ||
| if ($html =~ /<img[^>]+src=["']([^"']+\.(jpg|jpeg|png|gif|webp))["']/i) { | ||
| $img_url = $1; | ||
| # Convert relative URLs to absolute | ||
| if ($img_url !~ /^https?:/) { | ||
| $img_url = 'https://www.icloud.com' . $img_url if $img_url =~ /^\//; | ||
| } | ||
| } | ||
| # Pattern 2: Look for image URLs in JSON data structures | ||
| elsif ($html =~ /"url":"([^"]+\.(jpg|jpeg|png|gif|webp))"/i) { | ||
| $img_url = $1; | ||
| $img_url =~ s/\\\//\//g; # Unescape JSON-encoded slashes | ||
| } | ||
| # Pattern 3: Look for downloadURL in JSON (often contains the full image URL) | ||
| elsif ($html =~ /"downloadURL":"([^"]+)"/i) { | ||
| $img_url = $1; | ||
| $img_url =~ s/\\\//\//g; # Unescape JSON-encoded slashes | ||
| } | ||
|
|
||
| if ($img_url) { | ||
| # Successfully extracted image URL - render it inline | ||
| # The image will be wrapped in an irclink anchor below for stats tracking | ||
| # Escape the URL to prevent XSS | ||
| $link_filler = '<img src="' . encode_entities($img_url) . '" alt="Apple Photos image">'; | ||
| } else { | ||
| # Couldn't extract image URL - fall back to showing the title as a link | ||
| # This maintains functionality even if Apple changes their page structure | ||
| $link_filler = $data->{$item}->{'title'}; | ||
| } | ||
| } | ||
| } else { | ||
| # HTTP request failed - log error and fall back to default behavior | ||
| # This prevents silent failures and ensures the page still renders | ||
| warn "Failed to fetch Apple Photos URL: " . $apple_photos_url . " - " . $response->status_line; | ||
| # Fall back to showing the title as a link | ||
| $link_filler = $data->{$item}->{'title'}; | ||
| } | ||
| } |
There was a problem hiding this comment.
This large block of Apple Photos handling code (over 80 lines) significantly increases the complexity of the displayTumble function. Consider extracting this into a separate helper method like 'handle_apple_photos_link' to improve readability and make the code more testable and maintainable.
| # Detect Apple Photos shared links (e.g., https://www.icloud.com/photos/#/icloudlinks/...) | ||
| # These links need special handling because they're not direct image URLs | ||
| if ($data->{$item}->{'url'} =~ /icloud\.com\/photos.*icloudlinks/i) { | ||
| $is_apple_photos = 1; | ||
| $apple_photos_url = $data->{$item}->{'url'}; | ||
| } | ||
|
|
||
| # For Apple Photos links, fetch the page to extract the actual image URL or OpenGraph data | ||
| # This allows us to render images inline instead of requiring users to click through | ||
| if ($is_apple_photos && $data->{$item}->{'user'} !~ /nsfw|otd/) { |
There was a problem hiding this comment.
While the inline comments are helpful, this new feature lacks high-level documentation. Consider adding a POD (Plain Old Documentation) section to document the Apple Photos inline rendering feature, including its behavior, limitations (e.g., requires network access, may slow page loads), and how it interacts with the nsfw/otd filtering.
| my $ua = LWP::UserAgent->new( | ||
| ssl_opts => { verify_hostname => 0 }, | ||
| timeout => 5 | ||
| ); | ||
| $ua->agent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'); | ||
| my $response = $ua->get($apple_photos_url); |
There was a problem hiding this comment.
Fetching external content synchronously during page rendering can significantly impact performance and user experience. Each iCloud link will add at least the timeout duration (5 seconds) to the page load time if the request fails, and potentially 1-2 seconds even on success. Consider implementing caching of the fetched image URLs or using asynchronous processing to avoid blocking page renders.
htdocs/lib/tumble.pm
Outdated
| $link_filler = '<img src="' . $data->{$item}->{'url'} . '">'; | ||
| } | ||
|
|
||
| if ($data->{$item}->{'url'} =~ /twitter/) { |
There was a problem hiding this comment.
The elsif condition can potentially conflict with the Twitter handling logic below (line 227). If a URL is both an iCloud image link and a Twitter URL, or if the Twitter block overrides the Apple Photos result, this could lead to unexpected behavior. Consider the order of these conditions and whether they should be mutually exclusive.
| if ($data->{$item}->{'url'} =~ /twitter/) { | |
| elsif (!$is_apple_photos && $data->{$item}->{'url'} =~ /twitter/) { |
htdocs/lib/tumble.pm
Outdated
| if ($html =~ /<meta\s+property=["']og:image["']\s+content=["']([^"']+)["']/i) { | ||
| $og_image = $1; | ||
| $is_album = 1; | ||
| } | ||
| if ($html =~ /<meta\s+property=["']og:title["']\s+content=["']([^"']+)["']/i) { | ||
| $og_title = $1; | ||
| } | ||
| if ($html =~ /<meta\s+property=["']og:description["']\s+content=["']([^"']+)["']/i) { | ||
| $og_description = $1; | ||
| } |
There was a problem hiding this comment.
The regex patterns used for extracting OpenGraph tags and image URLs are fragile and may break if the HTML structure changes or contains variations in formatting (e.g., single vs. double quotes, attribute ordering, whitespace). Consider using a proper HTML parser like HTML::TreeBuilder or Mojo::DOM instead of regex for more robust and maintainable HTML parsing.
| my $preview_html = '<div style="border: 1px solid #ddd; border-radius: 4px; padding: 10px; max-width: 500px; background: #f9f9f9;">'; | ||
| if ($og_image) { | ||
| # Escape HTML in alt text to prevent XSS | ||
| my $escaped_title = $og_title ? encode_entities($og_title) : 'Apple Photos album'; | ||
| $preview_html .= '<img src="' . encode_entities($og_image) . '" style="max-width: 100%; height: auto; border-radius: 4px; margin-bottom: 8px;" alt="' . $escaped_title . '">'; | ||
| } | ||
| if ($og_title) { | ||
| $preview_html .= '<div style="font-weight: bold; margin-bottom: 4px;">' . encode_entities($og_title) . '</div>'; | ||
| } | ||
| if ($og_description) { | ||
| $preview_html .= '<div style="font-size: 0.9em; color: #666;">' . encode_entities($og_description) . '</div>'; | ||
| } | ||
| $preview_html .= '</div>'; |
There was a problem hiding this comment.
Inline CSS styles make the HTML harder to maintain and can lead to inconsistent styling across the application. Consider moving these styles to a CSS class or reusing existing styles from the application's stylesheet. This would improve maintainability and allow for easier theme changes.
htdocs/lib/tumble.pm
Outdated
|
|
||
| use YAML qw( LoadFile ); | ||
| use LWP::UserAgent; | ||
| use LWP::Simple; |
There was a problem hiding this comment.
The LWP::Simple module is imported but never used in the Apple Photos feature code. It appears to only be used later in the Twitter handling block. Consider moving this import closer to where it's used or removing it from the top-level imports if it was only needed for Twitter handling.
htdocs/lib/tumble.pm
Outdated
| # fall back to normal linking of images if they could be nsfw | ||
| if (($data->{$item}->{'content_type'} =~ /image/) and ($data->{$item}->{'user'} !~ /nsfw|otd/)) { | ||
| elsif (($data->{$item}->{'content_type'} =~ /image/) and ($data->{$item}->{'user'} !~ /nsfw|otd/)) { | ||
| $link_filler = '<img src="' . $data->{$item}->{'url'} . '">'; |
There was a problem hiding this comment.
$data->{$item}->{'url'} is concatenated directly into an <img src="..."> attribute without any HTML escaping, so a crafted URL like http://example.com" onerror="alert(1) will produce <img src="http://example.com" onerror="alert(1)"> and execute arbitrary JavaScript in visitors' browsers. Because url ultimately comes from user-supplied input (via irclink/index.cgi), this is a straightforward, remotely exploitable XSS vector. You should HTML-escape or otherwise safely encode url before embedding it into the src attribute, and apply the same protection wherever other <img src="..."> tags are built from untrusted URLs.
| $link_filler = '<img src="' . $data->{$item}->{'url'} . '">'; | |
| my $safe_url = encode_entities($data->{$item}->{'url'}, q{"&<>}); | |
| $link_filler = '<img src="' . $safe_url . '">'; |
htdocs/lib/tumble.pm
Outdated
| if ($data->{$item}->{'url'} =~ /icloud\.com\/photos.*icloudlinks/i) { | ||
| $is_apple_photos = 1; | ||
| $apple_photos_url = $data->{$item}->{'url'}; | ||
| } | ||
|
|
||
| # For Apple Photos links, fetch the page to extract the actual image URL or OpenGraph data | ||
| # This allows us to render images inline instead of requiring users to click through | ||
| if ($is_apple_photos && $data->{$item}->{'user'} !~ /nsfw|otd/) { | ||
| # Set up HTTP client with appropriate user agent to avoid being blocked | ||
| my $ua = LWP::UserAgent->new( | ||
| ssl_opts => { verify_hostname => 0 }, | ||
| timeout => 5 | ||
| ); | ||
| $ua->agent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'); | ||
| my $response = $ua->get($apple_photos_url); | ||
| if ($response->is_success) { |
There was a problem hiding this comment.
$apple_photos_url is taken directly from $data->{$item}->{'url'} and only checked with the loose regex /icloud\.com\/photos.*icloudlinks/i, which does not actually restrict the hostname (e.g., http://127.0.0.1/icloud.com/photos/x/icloudlinks will pass). This allows an attacker who can submit links to point url at arbitrary internal or external hosts while still matching the regex, causing the server to issue HTTP(S) requests to attacker-chosen endpoints (SSRF). You should strongly constrain the Apple Photos URL (e.g., validate the scheme and host against an allowlist such as https://www.icloud.com) and avoid issuing backend requests to URLs that are not definitively trusted.
9fe1ef9 to
e8112a3
Compare
Previously, we had icloud links as just links, but often times they are pictures. This change allows for images to render inline or use an opengraph preview if it's an album.
e8112a3 to
edce971
Compare
|
Apple Photos opengraph previews suck. Noping out. |
Previously, we had icloud links as just links, but often times they are pictures. This change allows for images to render inline or use an opengraph preview if it's an album.