diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dd4783597..a9819a6c79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,34 @@ All notable changes to this project will be documented in this file. +## [4.3.8] - 2025-05-06 + +### Security + +- Update dependencies +- Check scheme on account, profile, and media URLs ([GHSA-x2rc-v5wx-g3m5](https://github.com/mastodon/mastodon/security/advisories/GHSA-x2rc-v5wx-g3m5)) + +### Added + +- Add warning for REDIS_NAMESPACE deprecation at startup (#34581 by @ClearlyClaire) +- Add built-in context for interaction policies (#34574 by @ClearlyClaire) + +### Changed + +- Change activity distribution error handling to skip retrying for deleted accounts (#33617 by @ClearlyClaire) + +### Removed + +- Remove double-query for signed query strings (#34610 by @ClearlyClaire) + +### Fixed + +- Fix incorrect redirect in response to unauthenticated API requests in limited federation mode (#34549 by @ClearlyClaire) +- Fix sign-up e-mail confirmation page reloading on error or redirect (#34548 by @ClearlyClaire) + ## [4.3.7] - 2025-04-02 -### Add +### Added - Add delay to profile updates to debounce them (#34137 by @ClearlyClaire) - Add support for paginating partial collections in `SynchronizeFollowersService` (#34272 and #34277 by @ClearlyClaire) diff --git a/Gemfile b/Gemfile index b55ec1d730..44c4c9a54d 100644 --- a/Gemfile +++ b/Gemfile @@ -212,7 +212,7 @@ group :development, :test do gem 'test-prof', require: false # RSpec runner for rails - gem 'rspec-rails', '~> 7.0' + gem 'rspec-rails', '~> 8.0' end group :production do diff --git a/Gemfile.lock b/Gemfile.lock index e9cdfa2e7d..1df8c3ed3d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -435,7 +435,7 @@ GEM mutex_m (0.3.0) net-http (0.6.0) uri - net-imap (0.5.6) + net-imap (0.5.8) date net-protocol net-ldap (0.19.0) @@ -620,7 +620,7 @@ GEM psych (5.2.3) date stringio - public_suffix (6.0.1) + public_suffix (6.0.2) puma (6.6.0) nio4r (~> 2.0) pundit (2.5.0) @@ -721,18 +721,18 @@ GEM rspec-mocks (~> 3.13.0) rspec-core (3.13.3) rspec-support (~> 3.13.0) - rspec-expectations (3.13.3) + rspec-expectations (3.13.4) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-github (3.0.0) rspec-core (~> 3.0) - rspec-mocks (3.13.2) + rspec-mocks (3.13.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (7.1.1) - actionpack (>= 7.0) - activesupport (>= 7.0) - railties (>= 7.0) + rspec-rails (8.0.0) + actionpack (>= 7.2) + activesupport (>= 7.2) + railties (>= 7.2) rspec-core (~> 3.13) rspec-expectations (~> 3.13) rspec-mocks (~> 3.13) @@ -742,8 +742,8 @@ GEM rspec-expectations (~> 3.0) rspec-mocks (~> 3.0) sidekiq (>= 5, < 9) - rspec-support (3.13.2) - rubocop (1.75.4) + rspec-support (3.13.3) + rubocop (1.75.5) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -800,7 +800,7 @@ GEM activerecord (>= 4.0.0) railties (>= 4.0.0) securerandom (0.4.1) - selenium-webdriver (4.31.0) + selenium-webdriver (4.32.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) @@ -842,7 +842,7 @@ GEM base64 stoplight (4.1.1) redlock (~> 1.0) - stringio (3.1.6) + stringio (3.1.7) strong_migrations (2.3.0) activerecord (>= 7) swd (2.0.3) @@ -1045,7 +1045,7 @@ DEPENDENCIES redis-namespace (~> 1.10) rqrcode (~> 3.0) rspec-github (~> 3.0) - rspec-rails (~> 7.0) + rspec-rails (~> 8.0) rspec-sidekiq (~> 5.0) rubocop rubocop-capybara diff --git a/app/helpers/json_ld_helper.rb b/app/helpers/json_ld_helper.rb index 693cdf730f..7b7680661f 100644 --- a/app/helpers/json_ld_helper.rb +++ b/app/helpers/json_ld_helper.rb @@ -80,6 +80,18 @@ module JsonLdHelper !haystack.casecmp(needle).zero? end + def safe_prefetched_embed(account, object, context) + return unless object.is_a?(Hash) + + # NOTE: Replacing the object's context by that of the parent activity is + # not sound, but it's consistent with the rest of the codebase + object = object.merge({ '@context' => context }) + + return if value_or_id(first_of_value(object['attributedTo'])) != account.uri || non_matching_uri_hosts?(account.uri, object['id']) + + object + end + def canonicalize(json) graph = RDF::Graph.new << JSON::LD::API.toRdf(json, documentLoader: method(:load_jsonld_context)) graph.dump(:normalize) diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js index 93fc3b9fef..24dffe3121 100644 --- a/app/javascript/mastodon/actions/importer/normalizer.js +++ b/app/javascript/mastodon/actions/importer/normalizer.js @@ -89,6 +89,17 @@ export function normalizeStatus(status, normalOldStatus) { normalStatus.contentHtml = emojify(normalStatus.content, emojiMap); normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap); normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive; + + if (normalStatus.url && !(normalStatus.url.startsWith('http://') || normalStatus.url.startsWith('https://'))) { + normalStatus.url = null; + } + + normalStatus.url ||= normalStatus.uri; + + normalStatus.media_attachments.forEach(item => { + if (item.remote_url && !(item.remote_url.startsWith('http://') || item.remote_url.startsWith('https://'))) + item.remote_url = null; + }); } if (normalOldStatus) { diff --git a/app/javascript/mastodon/blurhash.ts b/app/javascript/mastodon/blurhash.ts index cafe7b12dc..a1d1a0f4e1 100644 --- a/app/javascript/mastodon/blurhash.ts +++ b/app/javascript/mastodon/blurhash.ts @@ -96,13 +96,19 @@ export const decode83 = (str: string) => { return value; }; -export const intToRGB = (int: number) => ({ +export interface RGB { + r: number; + g: number; + b: number; +} + +export const intToRGB = (int: number): RGB => ({ r: Math.max(0, int >> 16), g: Math.max(0, (int >> 8) & 255), b: Math.max(0, int & 255), }); -export const getAverageFromBlurhash = (blurhash: string) => { +export const getAverageFromBlurhash = (blurhash: string | null) => { if (!blurhash) { return null; } diff --git a/app/javascript/mastodon/components/admin/ReportReasonSelector.jsx b/app/javascript/mastodon/components/admin/ReportReasonSelector.jsx index 351f1c949e..3c33688b0c 100644 --- a/app/javascript/mastodon/components/admin/ReportReasonSelector.jsx +++ b/app/javascript/mastodon/components/admin/ReportReasonSelector.jsx @@ -105,7 +105,7 @@ class ReportReasonSelector extends PureComponent { }; componentDidMount() { - api(false).get('/api/v1/instance').then(res => { + api(false).get('/api/v2/instance').then(res => { this.setState({ rules: res.data.rules, }); diff --git a/app/javascript/mastodon/components/picture_in_picture_placeholder.jsx b/app/javascript/mastodon/components/picture_in_picture_placeholder.jsx deleted file mode 100644 index 50f91a9275..0000000000 --- a/app/javascript/mastodon/components/picture_in_picture_placeholder.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -import { FormattedMessage } from 'react-intl'; - -import { connect } from 'react-redux'; - -import CancelPresentationIcon from '@/material-icons/400-24px/cancel_presentation.svg?react'; -import { removePictureInPicture } from 'mastodon/actions/picture_in_picture'; -import { Icon } from 'mastodon/components/icon'; - -class PictureInPicturePlaceholder extends PureComponent { - - static propTypes = { - dispatch: PropTypes.func.isRequired, - aspectRatio: PropTypes.string, - }; - - handleClick = () => { - const { dispatch } = this.props; - dispatch(removePictureInPicture()); - }; - - render () { - const { aspectRatio } = this.props; - - return ( -
- - -
- ); - } - -} - -export default connect()(PictureInPicturePlaceholder); diff --git a/app/javascript/mastodon/components/picture_in_picture_placeholder.tsx b/app/javascript/mastodon/components/picture_in_picture_placeholder.tsx new file mode 100644 index 0000000000..829ab5febd --- /dev/null +++ b/app/javascript/mastodon/components/picture_in_picture_placeholder.tsx @@ -0,0 +1,46 @@ +import { useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import PipExitIcon from '@/material-icons/400-24px/pip_exit.svg?react'; +import { removePictureInPicture } from 'mastodon/actions/picture_in_picture'; +import { Icon } from 'mastodon/components/icon'; +import { useAppDispatch } from 'mastodon/store'; + +export const PictureInPicturePlaceholder: React.FC<{ aspectRatio: string }> = ({ + aspectRatio, +}) => { + const dispatch = useAppDispatch(); + + const handleClick = useCallback(() => { + dispatch(removePictureInPicture()); + }, [dispatch]); + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + e.stopPropagation(); + handleClick(); + } + }, + [handleClick], + ); + + return ( +
+ + +
+ ); +}; diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index c778666ceb..130aa83090 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -21,7 +21,7 @@ import AttachmentList from 'mastodon/components/attachment_list'; import { ContentWarning } from 'mastodon/components/content_warning'; import { FilterWarning } from 'mastodon/components/filter_warning'; import { Icon } from 'mastodon/components/icon'; -import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder'; +import { PictureInPicturePlaceholder } from 'mastodon/components/picture_in_picture_placeholder'; import { withOptionalRouter, WithOptionalRouterPropTypes } from 'mastodon/utils/react_router'; import Card from '../features/status/components/card'; @@ -507,9 +507,6 @@ class Status extends ImmutablePureComponent { foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])} accentColor={attachment.getIn(['meta', 'colors', 'accent'])} duration={attachment.getIn(['meta', 'original', 'duration'], 0)} - width={this.props.cachedMediaWidth} - height={110} - cacheWidth={this.props.cacheMediaWidth} deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined} sensitive={status.get('sensitive')} blurhash={attachment.get('blurhash')} diff --git a/app/javascript/mastodon/containers/media_container.jsx b/app/javascript/mastodon/containers/media_container.jsx index 9c07341faa..e826dbfa96 100644 --- a/app/javascript/mastodon/containers/media_container.jsx +++ b/app/javascript/mastodon/containers/media_container.jsx @@ -8,7 +8,7 @@ import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag'; import MediaGallery from 'mastodon/components/media_gallery'; import ModalRoot from 'mastodon/components/modal_root'; import { Poll } from 'mastodon/components/poll'; -import Audio from 'mastodon/features/audio'; +import { Audio } from 'mastodon/features/audio'; import Card from 'mastodon/features/status/components/card'; import MediaModal from 'mastodon/features/ui/components/media_modal'; import { Video } from 'mastodon/features/video'; diff --git a/app/javascript/mastodon/features/alt_text_modal/index.tsx b/app/javascript/mastodon/features/alt_text_modal/index.tsx index e2d05a99ca..08e4a8917c 100644 --- a/app/javascript/mastodon/features/alt_text_modal/index.tsx +++ b/app/javascript/mastodon/features/alt_text_modal/index.tsx @@ -27,7 +27,7 @@ import { Button } from 'mastodon/components/button'; import { GIFV } from 'mastodon/components/gifv'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { Skeleton } from 'mastodon/components/skeleton'; -import Audio from 'mastodon/features/audio'; +import { Audio } from 'mastodon/features/audio'; import { CharacterCounter } from 'mastodon/features/compose/components/character_counter'; import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components'; import { Video, getPointerPosition } from 'mastodon/features/video'; @@ -212,11 +212,11 @@ const Preview: React.FC<{ return (