Permalink
Browse files

Webmention Sender matches W3C spec

  • Loading branch information...
adamdawkins committed Feb 8, 2019
1 parent d4ac057 commit 1feae4f718dddaea5df5e64957c6f7301fc5ace2
@@ -0,0 +1,10 @@
class SendWebmentionJob < ApplicationJob
queue_as :default

def perform(webmention)
@sender = WebmentionSender.new(webmention.source, webmention.target)
@sender.send

webmention.update(status: @sender.status)
end
end
@@ -15,6 +15,6 @@ def source
end

def send_webmention
WebmentionService.new(source, target)
SendWebmentionJob.perform_later(self)
end
end
@@ -3,20 +3,26 @@

Parser = URI::Parser.new

class WebmentionService
attr_reader :endpoint
class WebmentionSender
attr_reader :endpoint, :status

def initialize(source, target)
@source = source
@target = target
@response = HTTParty.get(@target)
@endpoint = to_absolute_url(@target, discover_endpoint)
set_endpoint
@status = "intialized"
end

def success?
@status == "success"
end


def send
return if @endpoint.nil?
return @status = "no_endpoint" if @endpoint.nil?
response = HTTParty.post(@endpoint, { body: {target: @target, source: @source }})
pp response
@status = "success" if response.code.between?(200, 299)
end

private
@@ -30,11 +36,11 @@ def discover_endpoint

def discover_endpoint_from_http_header
header_link = @response.links.by_rel('webmention')

res = header_link.andand.target.andand.to_s
res
end


def set_doc
@doc = Nokogiri::HTML(@response.body)
end
@@ -47,5 +53,19 @@ def discover_endpoint_from_document

def to_absolute_url(origin, target)
Parser.parse(origin).merge(Parser.parse(target)).to_s
rescue
nil
end

def endpoint_valid?(endpoint)
host = Parser.parse(endpoint).andand.host
!(host.andand.match(/127\.0\.0\..*/) || host == 'localhost')
rescue
false
end

def set_endpoint
discovered_endpoint = discover_endpoint
@endpoint = to_absolute_url(@target, discovered_endpoint) if endpoint_valid?(discovered_endpoint)
end
end
@@ -0,0 +1,5 @@
require 'rails_helper'

RSpec.describe SendWebmentionJob, type: :job do
pending "add some examples to (or delete) #{__FILE__}"
end
@@ -35,4 +35,5 @@
config.cassette_library_dir = "fixtures/vcr_cassettes"
config.hook_into :webmock
config.configure_rspec_metadata!
config.ignore_hosts 'webmentionfake.com', 'adamdawkinsfake.com'
end
@@ -9,17 +9,17 @@
def webmention_rocks_endpoint_test(test_numbers = [])
test_numbers.each do |number|
expect(
WebmentionService.new("adamdawkins.uk/1", "https://webmention.rocks/test/#{number}").endpoint
WebmentionSender.new("adamdawkins.uk/1", "https://webmention.rocks/test/#{number}").endpoint
).to eq "https://webmention.rocks/test/#{number}/webmention"
end
end

RSpec.describe WebmentionService do
RSpec.describe WebmentionSender do
describe "Sending Webmentions" do
describe "Sender discovers receiver Webmention endpoint", :vcr do
let(:source) { "http://adamdawkins.uk/1" }
it "*must* fetch the link" do
WebmentionService.new(source, "https://webmention.rocks/test/1")
WebmentionSender.new(source, "https://webmention.rocks/test/1")
expect(a_request(:get, "https://webmention.rocks/test/1")).to have_been_made
end
it "*must* follow all redirects" do
@@ -69,7 +69,7 @@ def webmention_rocks_endpoint_test(test_numbers = [])

# 15. uses a <link> with an empty string, testing that the page itself is the endpoint
expect(
WebmentionService.new(source, "https://webmention.rocks/test/15").endpoint
WebmentionSender.new(source, "https://webmention.rocks/test/15").endpoint
).to eq "https://webmention.rocks/test/15"
end

@@ -109,21 +109,73 @@ def webmention_rocks_endpoint_test(test_numbers = [])
let(:webmention_endpoint) { "https://webmention.rocks/test/1/webmention" }

it "*must* post x-www-form-urlencoded source and target to endpoint" do
WebmentionService.new(source, target).send
WebmentionSender.new(source, target).send
expect(a_request(:post, webmention_endpoint).with(body: { source: source, target: target }))
end

context "endpoint contains query string parameter" do
it "*must* preserve query string parameters and not send them in the POST body"
# https://webmention.rocks/test/21
let(:webmention_endpoint) { "https://webmention.rocks/test/21/webmention?query=yes" }
let(:target) { "https://webmention.rocks/test/21" }

it "*must* preserve query string parameters and not send them in the POST body" do
WebmentionSender.new(source, target).send
expect(a_request(:post, webmention_endpoint).with(body: { source: source, target: target }))
end
end

it "*must* consider any 2xx responses a success"
describe "handling response" do
let(:mock_source) { "http://adamdawkinsfake.com/source" }
let(:mock_target) { "http://webmentionfake.com/test/1" }
let(:mock_endpoint) { "http://webmention.example/test/1/endpoint" }

before do
stub_request(:get, mock_target).to_return(headers: { "Link" => "<#{mock_endpoint}>; rel=webmention" })
end

it "*must* consider any 2xx responses a success" do
# The success range of statuses are 200-208 and 226
success_statuses = (200..208).to_a << 226

success_statuses.each do |status|
stub_request(:post, mock_endpoint).to_return(status: status)

sender = WebmentionSender.new(mock_source, mock_target)
sender.send

context "if localhost or 127.0.0.* endpoint detected" do
# it "*should not* send the Webmention to that endpoint"
expect(sender.success?).to eq true
end
end

context "localhost endpoint detected" do
let(:mock_endpoint) { "http://localhost/malicious" }
before do
stub_request(:get, mock_target).to_return(headers: { "Link" => "<#{mock_endpoint}>; rel=webmention" })
stub_request(:post, mock_endpoint)
end

it "*should not* send the Webmention to that endpoint" do
sender = WebmentionSender.new(mock_source, mock_target)
sender.send
expect(a_request(:post, mock_endpoint)).not_to have_been_made
end
end

context "127.0.0.* endpoint detected" do
let(:mock_endpoint) { "http://127.0.0.4/malicious" }
before do
stub_request(:get, mock_target).to_return(headers: { "Link" => "<#{mock_endpoint}>; rel=webmention" })
stub_request(:post, mock_endpoint)
end

it "*should not* send the Webmention to that endpoint" do
sender = WebmentionSender.new(mock_source, mock_target)
sender.send
expect(a_request(:post, mock_endpoint)).not_to have_been_made
end
end
end
end

describe "Sending Webmentions for updated posts" do
# it "*should* re-send any previously sent Webmentions"
# it "*should* re-send Webmentions to any URLs that have been removed from the document"

0 comments on commit 1feae4f

Please sign in to comment.