Skip to content

Commit

Permalink
Merge branch '4.1.10' into long_emoji_4.1.10
Browse files Browse the repository at this point in the history
  • Loading branch information
maa123 committed Aug 30, 2024
2 parents c311f9f + 746bb6b commit bdc0d8f
Show file tree
Hide file tree
Showing 29 changed files with 159 additions and 54 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@ Changelog

All notable changes to this project will be documented in this file.

## [4.1.19] - 2024-08-16

### Fixed

- Fix incorrect rate limit on PUT requests ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/31356))
- Fix presence of `ß` in adjacent word preventing mention and hashtag matching ([adamniedzielski](https://github.com/mastodon/mastodon/pull/31122))
- Fix processing of webfinger responses with multiple `self` links ([adamniedzielski](https://github.com/mastodon/mastodon/pull/31110))
- Fix status processing failing halfway when a remote post has a malformed `replies` attribute ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/31246))
- Fix division by zero on some video/GIF files ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30600))
- Fix hashtag regexp matching some link anchors ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30190))
- Fix local account search on LDAP login being case-sensitive ([raucao](https://github.com/mastodon/mastodon/pull/30113))
- Fix development environment admin account not being auto-approved ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29958))
- Fix report reason selector in moderation interface not unselecting rules when changing category ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29026))
- Fix already-invalid reports failing to resolve ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29027))
- Fix OCR when using S3/CDN for assets ([vmstan](https://github.com/mastodon/mastodon/pull/28551))
- Fix error when encountering malformed `Tag` objects from Kbin ([ShadowJonathan](https://github.com/mastodon/mastodon/pull/28235))
- Fix not all allowed image formats showing in file picker when uploading custom emoji ([june128](https://github.com/mastodon/mastodon/pull/28076))

## [4.1.18] - 2024-07-04

### Security
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class ReportReasonSelector extends React.PureComponent {

api().put(`/api/v1/admin/reports/${id}`, {
category,
rule_ids,
rule_ids: category === 'violation' ? rule_ids : [],
}).catch(err => {
console.error(err);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ class FocalPointModal extends ImmutablePureComponent {
const worker = createWorker({
workerPath: tesseractWorkerPath,
corePath: tesseractCorePath,
langPath: `${assetHost}/ocr/lang-data/`,
langPath: `${assetHost}/ocr/lang-data`,
logger: ({ status, progress }) => {
if (status === 'recognizing text') {
this.setState({ ocrStatus: 'detecting', progress });
Expand Down
6 changes: 4 additions & 2 deletions app/lib/activitypub/activity/create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -340,13 +340,15 @@ def resolve_thread(status)

def fetch_replies(status)
collection = @object['replies']
return if collection.nil?
return if collection.blank?

replies = ActivityPub::FetchRepliesService.new.call(status, collection, allow_synchronous_requests: false, request_id: @options[:request_id])
return unless replies.nil?

uri = value_or_id(collection)
ActivityPub::FetchRepliesWorker.perform_async(status.id, uri, { 'request_id' => @options[:request_id]}) unless uri.nil?
ActivityPub::FetchRepliesWorker.perform_async(status.id, uri, { 'request_id' => @options[:request_id] }) unless uri.nil?
rescue => e
Rails.logger.warn "Error fetching replies: #{e}"
end

def conversation_from_uri(uri)
Expand Down
10 changes: 8 additions & 2 deletions app/lib/video_metadata_extractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ def parse_metadata
@colorspace = video_stream[:pix_fmt]
@width = video_stream[:width]
@height = video_stream[:height]
@frame_rate = video_stream[:avg_frame_rate] == '0/0' ? nil : Rational(video_stream[:avg_frame_rate])
@r_frame_rate = video_stream[:r_frame_rate] == '0/0' ? nil : Rational(video_stream[:r_frame_rate])
@frame_rate = parse_framerate(video_stream[:avg_frame_rate])
@r_frame_rate = parse_framerate(video_stream[:r_frame_rate])
# For some video streams the frame_rate reported by `ffprobe` will be 0/0, but for these streams we
# should use `r_frame_rate` instead. Video screencast generated by Gnome Screencast have this issue.
@frame_rate ||= @r_frame_rate
Expand All @@ -55,4 +55,10 @@ def parse_metadata

@invalid = true if @metadata.key?(:error)
end

def parse_framerate(raw)
Rational(raw)
rescue ZeroDivisionError
nil
end
end
17 changes: 15 additions & 2 deletions app/lib/webfinger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ class GoneError < Error; end
class RedirectError < Error; end

class Response
ACTIVITYPUB_READY_TYPE = ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].freeze

attr_reader :uri

def initialize(uri, body)
Expand All @@ -20,17 +22,28 @@ def subject
end

def link(rel, attribute)
links.dig(rel, attribute)
links.dig(rel, 0, attribute)
end

def self_link_href
self_link.fetch('href')
end

private

def links
@links ||= @json['links'].index_by { |link| link['rel'] }
@links ||= @json.fetch('links', []).group_by { |link| link['rel'] }
end

def self_link
links.fetch('self', []).find do |link|
ACTIVITYPUB_READY_TYPE.include?(link['type'])
end
end

def validate_response!
raise Webfinger::Error, "Missing subject in response for #{@uri}" if subject.blank?
raise Webfinger::Error, "Missing self link in response for #{@uri}" if self_link.blank?
end
end

Expand Down
2 changes: 1 addition & 1 deletion app/models/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class Account < ApplicationRecord
)

USERNAME_RE = /[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?/i
MENTION_RE = %r{(?<![=/[:word:]])@((#{USERNAME_RE})(?:@[[:word:].-]+[[:word:]]+)?)}i
MENTION_RE = %r{(?<![=/[:word:]])@((#{USERNAME_RE})(?:@[[:word:].-]+[[:word:]]+)?)}
URL_PREFIX_RE = %r{\Ahttp(s?)://[^/]+}
USERNAME_ONLY_RE = /\A#{USERNAME_RE}\z/i

Expand Down
2 changes: 1 addition & 1 deletion app/models/concerns/ldap_authenticable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def ldap_get_user(attributes = {})
safe_username = safe_username.gsub(keys, replacement)
end

resource = joins(:account).find_by(accounts: { username: safe_username })
resource = joins(:account).merge(Account.where(Account.arel_table[:username].lower.eq safe_username.downcase)).take

if resource.blank?
resource = new(email: attributes[Devise.ldap_mail.to_sym].first, agreement: true, account_attributes: { username: safe_username }, admin: false, external: true, confirmed_at: Time.now.utc)
Expand Down
6 changes: 2 additions & 4 deletions app/models/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ class Report < ApplicationRecord
delegate :local?, to: :account

validates :comment, length: { maximum: 1_000 }, if: :local?
validates :rule_ids, absence: true, unless: :violation?
validates :rule_ids, absence: true, if: -> { (category_changed? || rule_ids_changed?) && !violation? }

validate :validate_rule_ids
validate :validate_rule_ids, if: -> { (category_changed? || rule_ids_changed?) && violation? }

enum category: {
other: 0,
Expand Down Expand Up @@ -147,8 +147,6 @@ def set_uri
end

def validate_rule_ids
return unless violation?

errors.add(:rule_ids, I18n.t('reports.errors.invalid_rules')) unless rules.size == rule_ids&.size
end

Expand Down
2 changes: 1 addition & 1 deletion app/models/tag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class Tag < ApplicationRecord
HASTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)'
HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASTAG_LAST_SEQUENCE}"

HASHTAG_RE = %r{(?<![=/)\w])#(#{HASHTAG_NAME_PAT})}i
HASHTAG_RE = %r{(?<![=/)\p{Alnum}])#(#{HASHTAG_NAME_PAT})}
HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i
HASHTAG_INVALID_CHARS_RE = /[^[:alnum:]#{HASHTAG_SEPARATORS}]/

Expand Down
10 changes: 4 additions & 6 deletions app/services/activitypub/fetch_remote_actor_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,16 @@ def check_webfinger!
confirmed_username, confirmed_domain = split_acct(webfinger.subject)

if @username.casecmp(confirmed_username).zero? && @domain.casecmp(confirmed_domain).zero?
raise Error, "Webfinger response for #{@username}@#{@domain} does not loop back to #{@uri}" if webfinger.link('self', 'href') != @uri
raise Error, "Webfinger response for #{@username}@#{@domain} does not loop back to #{@uri}" if webfinger.self_link_href != @uri

return
end

webfinger = webfinger!("acct:#{confirmed_username}@#{confirmed_domain}")
@username, @domain = split_acct(webfinger.subject)

unless confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?
raise Webfinger::RedirectError, "Too many webfinger redirects for URI #{@uri} (stopped at #{@username}@#{@domain})"
end

raise Error, "Webfinger response for #{@username}@#{@domain} does not loop back to #{@uri}" if webfinger.link('self', 'href') != @uri
raise Webfinger::RedirectError, "Too many webfinger redirects for URI #{@uri} (stopped at #{@username}@#{@domain})" unless confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?
raise Error, "Webfinger response for #{@username}@#{@domain} does not loop back to #{@uri}" if webfinger.self_link_href != @uri
rescue Webfinger::RedirectError => e
raise Error, e.message
rescue Webfinger::Error => e
Expand Down
4 changes: 2 additions & 2 deletions app/services/activitypub/process_status_update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,9 @@ def update_metadata!

as_array(@json['tag']).each do |tag|
if equals_or_includes?(tag['type'], 'Hashtag')
@raw_tags << tag['name']
@raw_tags << tag['name'] if tag['name'].present?
elsif equals_or_includes?(tag['type'], 'Mention')
@raw_mentions << tag['href']
@raw_mentions << tag['href'] if tag['href'].present?
elsif equals_or_includes?(tag['type'], 'Emoji')
@raw_emojis << tag
end
Expand Down
8 changes: 1 addition & 7 deletions app/services/resolve_account_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,6 @@ def split_acct(acct)
end

def fetch_account!
return unless activitypub_ready?

with_lock("resolve:#{@username}@#{@domain}") do
@account = ActivityPub::FetchRemoteAccountService.new.call(actor_url, suppress_errors: @options[:suppress_errors])
end
Expand All @@ -124,12 +122,8 @@ def webfinger_update_due?
@options[:skip_cache] || @account.nil? || @account.possibly_stale?
end

def activitypub_ready?
['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(@webfinger.link('self', 'type'))
end

def actor_url
@actor_url ||= @webfinger.link('self', 'href')
@actor_url ||= @webfinger.self_link_href
end

def gone_from_origin?
Expand Down
2 changes: 1 addition & 1 deletion app/views/admin/custom_emojis/new.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
.fields-group
= f.input :shortcode, wrapper: :with_label, label: t('admin.custom_emojis.shortcode'), hint: t('admin.custom_emojis.shortcode_hint')
.fields-group
= f.input :image, wrapper: :with_label, input_html: { accept: CustomEmoji::IMAGE_MIME_TYPES.join(' ') }, hint: t('admin.custom_emojis.image_hint', size: number_to_human_size(CustomEmoji::LIMIT))
= f.input :image, wrapper: :with_label, input_html: { accept: CustomEmoji::IMAGE_MIME_TYPES.join(',') }, hint: t('admin.custom_emojis.image_hint', size: number_to_human_size(CustomEmoji::LIMIT))

.actions
= f.button :button, t('admin.custom_emojis.upload'), type: :submit
2 changes: 1 addition & 1 deletion config/initializers/rack_attack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def paging_request?
end

throttle('throttle_password_change/account', limit: 10, period: 10.minutes) do |req|
req.warden_user_id if req.put? || (req.patch? && req.path_matches?('/auth'))
req.warden_user_id if (req.put? || req.patch?) && (req.path_matches?('/auth') || req.path_matches?('/auth/password'))
end

self.throttled_responder = lambda do |request|
Expand Down
4 changes: 3 additions & 1 deletion db/seeds/04_admin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
admin = Account.where(username: 'admin').first_or_initialize(username: 'admin')
admin.save(validate: false)

User.where(email: "admin@#{domain}").first_or_initialize(email: "admin@#{domain}", password: 'mastodonadmin', password_confirmation: 'mastodonadmin', confirmed_at: Time.now.utc, role: UserRole.find_by(name: 'Owner'), account: admin, agreement: true, approved: true).save!
user = User.where(email: "admin@#{domain}").first_or_initialize(email: "admin@#{domain}", password: 'mastodonadmin', password_confirmation: 'mastodonadmin', confirmed_at: Time.now.utc, role: UserRole.find_by(name: 'Owner'), account: admin, agreement: true, approved: true)
user.save!
user.approve!
end
6 changes: 3 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ services:

web:
build: .
image: ghcr.io/mastodon/mastodon:v4.1.18
image: ghcr.io/mastodon/mastodon:v4.1.19
restart: always
env_file: .env.production
command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
Expand All @@ -77,7 +77,7 @@ services:

streaming:
build: .
image: ghcr.io/mastodon/mastodon:v4.1.18
image: ghcr.io/mastodon/mastodon:v4.1.19
restart: always
env_file: .env.production
command: node ./streaming
Expand All @@ -95,7 +95,7 @@ services:

sidekiq:
build: .
image: ghcr.io/mastodon/mastodon:v4.1.18
image: ghcr.io/mastodon/mastodon:v4.1.19
restart: always
env_file: .env.production
command: bundle exec sidekiq
Expand Down
2 changes: 1 addition & 1 deletion lib/mastodon/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def minor
end

def patch
18
19
end

def flags
Expand Down
2 changes: 1 addition & 1 deletion spec/fixtures/requests/activitypub-webfinger.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Content-Type: application/jrd+json; charset=utf-8
X-Content-Type-Options: nosniff
Date: Sun, 17 Sep 2017 06:22:50 GMT

{"subject":"acct:foo@ap.example.com","aliases":["https://ap.example.com/@foo","https://ap.example.com/users/foo"],"links":[{"rel":"http://webfinger.net/rel/profile-page","type":"text/html","href":"https://ap.example.com/@foo"},{"rel":"http://schemas.google.com/g/2010#updates-from","type":"application/atom+xml","href":"https://ap.example.com/users/foo.atom"},{"rel":"self","type":"application/activity+json","href":"https://ap.example.com/users/foo"},{"rel":"salmon","href":"https://ap.example.com/api/salmon/1"},{"rel":"magic-public-key","href":"data:application/magic-public-key,RSA.u3L4vnpNLzVH31MeWI394F0wKeJFsLDAsNXGeOu0QF2x-h1zLWZw_agqD2R3JPU9_kaDJGPIV2Sn5zLyUA9S6swCCMOtn7BBR9g9sucgXJmUFB0tACH2QSgHywMAybGfmSb3LsEMNKsGJ9VsvYoh8lDET6X4Pyw-ZJU0_OLo_41q9w-OrGtlsTm_PuPIeXnxa6BLqnDaxC-4IcjG_FiPahNCTINl_1F_TgSSDZ4Taf4U9XFEIFw8wmgploELozzIzKq-t8nhQYkgAkt64euWpva3qL5KD1mTIZQEP-LZvh3s2WHrLi3fhbdRuwQ2c0KkJA2oSTFPDpqqbPGZ3QvuHQ==.AQAB"},{"rel":"http://ostatus.org/schema/1.0/subscribe","template":"https://ap.example.com/authorize_follow?acct={uri}"}]}
{"subject":"acct:foo@ap.example.com","aliases":["https://ap.example.com/@foo","https://ap.example.com/users/foo"],"links":[{"rel":"http://webfinger.net/rel/profile-page","type":"text/html","href":"https://ap.example.com/@foo"},{"rel":"http://schemas.google.com/g/2010#updates-from","type":"application/atom+xml","href":"https://ap.example.com/users/foo.atom"},{"rel":"self","type":"application/html","href":"https://ap.example.com/users/foo.html"},{"rel":"self","type":"application/activity+json","href":"https://ap.example.com/users/foo"},{"rel":"self","type":"application/json","href":"https://ap.example.com/users/foo.json"},{"rel":"salmon","href":"https://ap.example.com/api/salmon/1"},{"rel":"magic-public-key","href":"data:application/magic-public-key,RSA.u3L4vnpNLzVH31MeWI394F0wKeJFsLDAsNXGeOu0QF2x-h1zLWZw_agqD2R3JPU9_kaDJGPIV2Sn5zLyUA9S6swCCMOtn7BBR9g9sucgXJmUFB0tACH2QSgHywMAybGfmSb3LsEMNKsGJ9VsvYoh8lDET6X4Pyw-ZJU0_OLo_41q9w-OrGtlsTm_PuPIeXnxa6BLqnDaxC-4IcjG_FiPahNCTINl_1F_TgSSDZ4Taf4U9XFEIFw8wmgploELozzIzKq-t8nhQYkgAkt64euWpva3qL5KD1mTIZQEP-LZvh3s2WHrLi3fhbdRuwQ2c0KkJA2oSTFPDpqqbPGZ3QvuHQ==.AQAB"},{"rel":"http://ostatus.org/schema/1.0/subscribe","template":"https://ap.example.com/authorize_follow?acct={uri}"}]}
2 changes: 1 addition & 1 deletion spec/fixtures/requests/webfinger.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ Access-Control-Allow-Origin: *
Vary: Accept-Encoding,Cookie
Strict-Transport-Security: max-age=31536000; includeSubdomains;

{"subject":"acct:gargron@quitter.no","aliases":["https:\/\/quitter.no\/user\/7477","https:\/\/quitter.no\/gargron","https:\/\/quitter.no\/index.php\/user\/7477","https:\/\/quitter.no\/index.php\/gargron"],"links":[{"rel":"http:\/\/webfinger.net\/rel\/profile-page","type":"text\/html","href":"https:\/\/quitter.no\/gargron"},{"rel":"http:\/\/gmpg.org\/xfn\/11","type":"text\/html","href":"https:\/\/quitter.no\/gargron"},{"rel":"describedby","type":"application\/rdf+xml","href":"https:\/\/quitter.no\/gargron\/foaf"},{"rel":"http:\/\/apinamespace.org\/atom","type":"application\/atomsvc+xml","href":"https:\/\/quitter.no\/api\/statusnet\/app\/service\/gargron.xml"},{"rel":"http:\/\/apinamespace.org\/twitter","href":"https:\/\/quitter.no\/api\/"},{"rel":"http:\/\/specs.openid.net\/auth\/2.0\/provider","href":"https:\/\/quitter.no\/gargron"},{"rel":"http:\/\/schemas.google.com\/g\/2010#updates-from","type":"application\/atom+xml","href":"https:\/\/quitter.no\/api\/statuses\/user_timeline\/7477.atom"},{"rel":"magic-public-key","href":"data:application\/magic-public-key,RSA.1ZBkHTavLvxH3FzlKv4O6WtlILKRFfNami3_Rcu8EuogtXSYiS-bB6hElZfUCSHbC4uLemOA34PEhz__CDMozax1iI_t8dzjDnh1x0iFSup7pSfW9iXk_WU3Dm74yWWW2jildY41vWgrEstuQ1dJ8vVFfSJ9T_tO4c-T9y8vDI8=.AQAB"},{"rel":"salmon","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-replies","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-mention","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/ostatus.org\/schema\/1.0\/subscribe","template":"https:\/\/quitter.no\/main\/ostatussub?profile={uri}"}]}
{"subject":"acct:gargron@quitter.no","aliases":["https:\/\/quitter.no\/user\/7477","https:\/\/quitter.no\/gargron","https:\/\/quitter.no\/index.php\/user\/7477","https:\/\/quitter.no\/index.php\/gargron"],"links":[{"rel":"http:\/\/webfinger.net\/rel\/profile-page","type":"text\/html","href":"https:\/\/quitter.no\/gargron"},{"rel":"http:\/\/gmpg.org\/xfn\/11","type":"text\/html","href":"https:\/\/quitter.no\/gargron"},{"rel":"describedby","type":"application\/rdf+xml","href":"https:\/\/quitter.no\/gargron\/foaf"},{"rel":"self","type":"application/activity+json","href":"https://ap.example.com/users/foo"},{"rel":"http:\/\/apinamespace.org\/atom","type":"application\/atomsvc+xml","href":"https:\/\/quitter.no\/api\/statusnet\/app\/service\/gargron.xml"},{"rel":"http:\/\/apinamespace.org\/twitter","href":"https:\/\/quitter.no\/api\/"},{"rel":"http:\/\/specs.openid.net\/auth\/2.0\/provider","href":"https:\/\/quitter.no\/gargron"},{"rel":"http:\/\/schemas.google.com\/g\/2010#updates-from","type":"application\/atom+xml","href":"https:\/\/quitter.no\/api\/statuses\/user_timeline\/7477.atom"},{"rel":"magic-public-key","href":"data:application\/magic-public-key,RSA.1ZBkHTavLvxH3FzlKv4O6WtlILKRFfNami3_Rcu8EuogtXSYiS-bB6hElZfUCSHbC4uLemOA34PEhz__CDMozax1iI_t8dzjDnh1x0iFSup7pSfW9iXk_WU3Dm74yWWW2jildY41vWgrEstuQ1dJ8vVFfSJ9T_tO4c-T9y8vDI8=.AQAB"},{"rel":"salmon","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-replies","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-mention","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/ostatus.org\/schema\/1.0\/subscribe","template":"https:\/\/quitter.no\/main\/ostatussub?profile={uri}"}]}
Loading

0 comments on commit bdc0d8f

Please sign in to comment.