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:
		@@ -13,7 +13,7 @@ module AccountsHelper
 | 
			
		||||
    if account.local?
 | 
			
		||||
      "@#{account.acct}@#{Rails.configuration.x.local_domain}"
 | 
			
		||||
    else
 | 
			
		||||
      "@#{account.acct}"
 | 
			
		||||
      "@#{account.pretty_acct}"
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,7 @@
 | 
			
		||||
 | 
			
		||||
class Account < ApplicationRecord
 | 
			
		||||
  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 AccountAvatar
 | 
			
		||||
@@ -164,6 +164,10 @@ class Account < ApplicationRecord
 | 
			
		||||
    local? ? username : "#{username}@#{domain}"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def pretty_acct
 | 
			
		||||
    local? ? username : "#{username}@#{Addressable::IDNA.to_unicode(domain)}"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def local_username_and_domain
 | 
			
		||||
    "#{username}@#{Rails.configuration.x.local_domain}"
 | 
			
		||||
  end
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,10 @@ class REST::AccountSerializer < ActiveModel::Serializer
 | 
			
		||||
    object.id.to_s
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def acct
 | 
			
		||||
    object.pretty_acct
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def note
 | 
			
		||||
    Formatter.instance.simplified_format(object)
 | 
			
		||||
  end
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,16 @@ class ProcessMentionsService < BaseService
 | 
			
		||||
    mentions = []
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
      if mention_undeliverable?(mentioned_account)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,11 +5,11 @@ RSpec.describe ProcessMentionsService, type: :service do
 | 
			
		||||
  let(:visibility) { :public }
 | 
			
		||||
  let(:status)     { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct}", visibility: visibility) }
 | 
			
		||||
 | 
			
		||||
  subject { ProcessMentionsService.new }
 | 
			
		||||
 | 
			
		||||
  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') }
 | 
			
		||||
 | 
			
		||||
    subject { ProcessMentionsService.new }
 | 
			
		||||
 | 
			
		||||
    before do
 | 
			
		||||
      stub_request(:post, remote_user.salmon_url)
 | 
			
		||||
      subject.call(status)
 | 
			
		||||
@@ -24,8 +24,6 @@ RSpec.describe ProcessMentionsService, type: :service do
 | 
			
		||||
    let(:visibility)  { :private }
 | 
			
		||||
    let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com') }
 | 
			
		||||
 | 
			
		||||
    subject { ProcessMentionsService.new }
 | 
			
		||||
 | 
			
		||||
    before do
 | 
			
		||||
      stub_request(:post, remote_user.salmon_url)
 | 
			
		||||
      subject.call(status)
 | 
			
		||||
@@ -41,29 +39,45 @@ RSpec.describe ProcessMentionsService, type: :service do
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  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
 | 
			
		||||
      stub_request(:post, remote_user.inbox_url)
 | 
			
		||||
      subject.call(status)
 | 
			
		||||
      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
 | 
			
		||||
 | 
			
		||||
    it 'creates a mention' do
 | 
			
		||||
      expect(remote_user.mentions.where(status: status).count).to eq 1
 | 
			
		||||
    end
 | 
			
		||||
    context 'with an IDN domain' do
 | 
			
		||||
      let(:remote_user) { Fabricate(:account, username: 'sneak', protocol: :activitypub, domain: 'xn--hresiar-mxa.ch', inbox_url: 'http://example.com/inbox') }
 | 
			
		||||
      let(:status) { Fabricate(:status, account: account, text: "Hello @sneak@hæresiar.ch") }
 | 
			
		||||
 | 
			
		||||
    it 'sends activity to the inbox' do
 | 
			
		||||
      expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once
 | 
			
		||||
      before do
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
  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) }
 | 
			
		||||
 | 
			
		||||
    subject { ProcessMentionsService.new }
 | 
			
		||||
 | 
			
		||||
    before do
 | 
			
		||||
      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)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user