Fix IDN mentions not being processed, IDN domains not being rendered (#12715)
This changes the REST API to return unicode domains in the `acct` attribute instead of punycode, and to render unicode instead of punycode on public HTML pages as well. Fix #7812, fix #12246
This commit is contained in:
parent
b2f81060b7
commit
f86ee4b59f
5 changed files with 50 additions and 19 deletions
|
@ -13,7 +13,7 @@ module AccountsHelper
|
||||||
if account.local?
|
if account.local?
|
||||||
"@#{account.acct}@#{Rails.configuration.x.local_domain}"
|
"@#{account.acct}@#{Rails.configuration.x.local_domain}"
|
||||||
else
|
else
|
||||||
"@#{account.acct}"
|
"@#{account.pretty_acct}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
|
|
||||||
class Account < ApplicationRecord
|
class Account < ApplicationRecord
|
||||||
USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i
|
USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i
|
||||||
MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i
|
MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[[:word:]\.\-]+[a-z0-9]+)?)/i
|
||||||
|
|
||||||
include AccountAssociations
|
include AccountAssociations
|
||||||
include AccountAvatar
|
include AccountAvatar
|
||||||
|
@ -164,6 +164,10 @@ class Account < ApplicationRecord
|
||||||
local? ? username : "#{username}@#{domain}"
|
local? ? username : "#{username}@#{domain}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def pretty_acct
|
||||||
|
local? ? username : "#{username}@#{Addressable::IDNA.to_unicode(domain)}"
|
||||||
|
end
|
||||||
|
|
||||||
def local_username_and_domain
|
def local_username_and_domain
|
||||||
"#{username}@#{Rails.configuration.x.local_domain}"
|
"#{username}@#{Rails.configuration.x.local_domain}"
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,6 +24,10 @@ class REST::AccountSerializer < ActiveModel::Serializer
|
||||||
object.id.to_s
|
object.id.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def acct
|
||||||
|
object.pretty_acct
|
||||||
|
end
|
||||||
|
|
||||||
def note
|
def note
|
||||||
Formatter.instance.simplified_format(object)
|
Formatter.instance.simplified_format(object)
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,7 +14,16 @@ class ProcessMentionsService < BaseService
|
||||||
mentions = []
|
mentions = []
|
||||||
|
|
||||||
status.text = status.text.gsub(Account::MENTION_RE) do |match|
|
status.text = status.text.gsub(Account::MENTION_RE) do |match|
|
||||||
username, domain = Regexp.last_match(1).split('@')
|
username, domain = Regexp.last_match(1).split('@')
|
||||||
|
|
||||||
|
domain = begin
|
||||||
|
if TagManager.instance.local_domain?(domain)
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
TagManager.instance.normalize_domain(domain)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
mentioned_account = Account.find_remote(username, domain)
|
mentioned_account = Account.find_remote(username, domain)
|
||||||
|
|
||||||
if mention_undeliverable?(mentioned_account)
|
if mention_undeliverable?(mentioned_account)
|
||||||
|
|
|
@ -5,11 +5,11 @@ RSpec.describe ProcessMentionsService, type: :service do
|
||||||
let(:visibility) { :public }
|
let(:visibility) { :public }
|
||||||
let(:status) { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct}", visibility: visibility) }
|
let(:status) { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct}", visibility: visibility) }
|
||||||
|
|
||||||
|
subject { ProcessMentionsService.new }
|
||||||
|
|
||||||
context 'OStatus with public toot' do
|
context 'OStatus with public toot' do
|
||||||
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com') }
|
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com') }
|
||||||
|
|
||||||
subject { ProcessMentionsService.new }
|
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:post, remote_user.salmon_url)
|
stub_request(:post, remote_user.salmon_url)
|
||||||
subject.call(status)
|
subject.call(status)
|
||||||
|
@ -24,8 +24,6 @@ RSpec.describe ProcessMentionsService, type: :service do
|
||||||
let(:visibility) { :private }
|
let(:visibility) { :private }
|
||||||
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com') }
|
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com') }
|
||||||
|
|
||||||
subject { ProcessMentionsService.new }
|
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:post, remote_user.salmon_url)
|
stub_request(:post, remote_user.salmon_url)
|
||||||
subject.call(status)
|
subject.call(status)
|
||||||
|
@ -41,29 +39,45 @@ RSpec.describe ProcessMentionsService, type: :service do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'ActivityPub' do
|
context 'ActivityPub' do
|
||||||
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
|
context do
|
||||||
|
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
|
||||||
|
|
||||||
subject { ProcessMentionsService.new }
|
before do
|
||||||
|
stub_request(:post, remote_user.inbox_url)
|
||||||
|
subject.call(status)
|
||||||
|
end
|
||||||
|
|
||||||
before do
|
it 'creates a mention' do
|
||||||
stub_request(:post, remote_user.inbox_url)
|
expect(remote_user.mentions.where(status: status).count).to eq 1
|
||||||
subject.call(status)
|
end
|
||||||
|
|
||||||
|
it 'sends activity to the inbox' do
|
||||||
|
expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates a mention' do
|
context 'with an IDN domain' do
|
||||||
expect(remote_user.mentions.where(status: status).count).to eq 1
|
let(:remote_user) { Fabricate(:account, username: 'sneak', protocol: :activitypub, domain: 'xn--hresiar-mxa.ch', inbox_url: 'http://example.com/inbox') }
|
||||||
end
|
let(:status) { Fabricate(:status, account: account, text: "Hello @sneak@hæresiar.ch") }
|
||||||
|
|
||||||
it 'sends activity to the inbox' do
|
before do
|
||||||
expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once
|
stub_request(:post, remote_user.inbox_url)
|
||||||
|
subject.call(status)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a mention' do
|
||||||
|
expect(remote_user.mentions.where(status: status).count).to eq 1
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends activity to the inbox' do
|
||||||
|
expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'Temporarily-unreachable ActivityPub user' do
|
context 'Temporarily-unreachable ActivityPub user' do
|
||||||
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox', last_webfingered_at: nil) }
|
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox', last_webfingered_at: nil) }
|
||||||
|
|
||||||
subject { ProcessMentionsService.new }
|
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:get, "https://example.com/.well-known/host-meta").to_return(status: 404)
|
stub_request(:get, "https://example.com/.well-known/host-meta").to_return(status: 404)
|
||||||
stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:remote_user@example.com").to_return(status: 500)
|
stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:remote_user@example.com").to_return(status: 500)
|
||||||
|
|
Loading…
Reference in a new issue