diff --git a/controllers/API.php b/controllers/API.php index e116abc..be65c20 100644 --- a/controllers/API.php +++ b/controllers/API.php @@ -44,19 +44,28 @@ class API { ]); } - # Require source and target parameters - if((!$source=$request->get('source')) || (!$target=$request->get('target'))) { + # Require source and target or target_domain parameters + $target = $target_domain = null; + if((!$source=$request->get('source')) || ((!$target=$request->get('target')) && (!$target_domain=$request->get('target_domain')))) { return $this->respond($response, 400, [ 'error' => 'missing_parameters', - 'error_description' => 'The source or target parameters were missing' + 'error_description' => 'The source or target or target_domain parameters were missing' + ]); + } + if($target && $target_domain) { + return $this->respond($response, 400, [ + 'error' => 'invalid_parameter', + 'error_description' => 'Can\'t provide both target and target_domain together' ]); } $urlregex = '/^https?:\/\/[^ ]+\.[^ ]+$/'; + $domainregex = '/^[^ ]+$/'; # Verify source, target, and callback are URLs $callback = $request->get('callback'); - if(!preg_match($urlregex, $source) || !preg_match($urlregex, $target) || + if(!preg_match($urlregex, $source) || + (!preg_match($urlregex, $target) && !preg_match($domainregex, $target_domain)) || ($callback && !preg_match($urlregex, $callback))) { return $this->respond($response, 400, [ 'error' => 'invalid_parameter', @@ -90,44 +99,59 @@ class API { $xpath = new DOMXPath($doc); - $found = false; + $found = []; foreach($xpath->query('//a[@href]') as $href) { - if($href->getAttribute('href') == $target) { - $found = true; - continue; + $url = $href->getAttribute('href'); + $domain = parse_url($url, PHP_URL_HOST); + if($url == $target || $domain == $target_domain || + # subdomain check + ($target_domain and substr_compare($domain, '.' . $target_domain, -(strlen($target_domain) + 1)) == 0)) { + $found[$url] = null; } } if(!$found) { return $this->respond($response, 400, [ 'error' => 'no_link_found', - 'error_description' => 'The source document does not have a link to the target URL' + 'error_description' => 'The source document does not have a link to the target URL or domain' ]); } # Everything checked out, so write the webmention to the log and queue a job to start sending + # TODO: database transaction? + + $statusURLs = []; + foreach($found as $url=>$_) { + $w = ORM::for_table('webmentions')->create(); + $w->site_id = $role->site_id; + $w->created_by = $role->user_id; + $w->created_at = date('Y-m-d H:i:s'); + $w->token = self::generateStatusToken(); + $w->source = $source; + $w->target = $url; + $w->vouch = $request->get('vouch'); + $w->callback = $callback; + $w->save(); + + q()->queue('Telegraph\Webmention', 'send', [$w->id]); + + $statusURLs[] = Config::$base . 'webmention/' . $w->token; + } - $w = ORM::for_table('webmentions')->create(); - $w->site_id = $role->site_id; - $w->created_by = $role->user_id; - $w->created_at = date('Y-m-d H:i:s'); - $w->token = self::generateStatusToken(); - $w->source = $source; - $w->target = $target; - $w->vouch = $request->get('vouch'); - $w->callback = $callback; - $w->save(); - - q()->queue('Telegraph\Webmention', 'send', [$w->id]); - - $statusURL = Config::$base . 'webmention/' . $w->token; - - return $this->respond($response, 201, [ - 'status' => 'queued', - 'location' => $statusURL - ], [ - 'Location' => $statusURL - ]); + if ($target) { + $body = [ + 'status' => 'queued', + 'location' => $statusURLs[0] + ]; + $headers = ['Location' => $statusURLs[0]]; + } else { + $body = [ + 'status' => 'queued', + 'location' => $statusURLs + ]; + $headers = []; + } + return $this->respond($response, 201, $body, $headers); } public function webmention_status(Request $request, Response $response, $args) { diff --git a/tests/APITest.php b/tests/APITest.php index 9012386..a447f0b 100644 --- a/tests/APITest.php +++ b/tests/APITest.php @@ -46,6 +46,22 @@ class APITest extends PHPUnit_Framework_TestCase { $role->save(); } + private function _assertQueued($source, $target, $status_url) { + preg_match('/\/webmention\/(.+)/', $status_url, $match); + $this->assertNotNull($match); + + # Verify it queued the mention in the database + $d = ORM::for_table('webmentions')->where(['source' => $source, 'target' => $target])->find_one(); + $this->assertNotNull($d); + $this->assertEquals($match[1], $d->token); + + # Check the status endpoint to make sure it says it's still queued + $response = $this->status($d->token); + $this->assertEquals(200, $response->getStatusCode()); + $data = json_decode($response->getContent()); + $this->assertEquals('queued', $data->status); + } + public function testAuthentication() { $response = $this->webmention([]); $this->assertEquals(401, $response->getStatusCode()); @@ -82,6 +98,20 @@ class APITest extends PHPUnit_Framework_TestCase { $this->assertEquals(400, $response->getStatusCode()); $data = json_decode($response->getContent()); $this->assertEquals('missing_parameters', $data->error); + + $response = $this->webmention(['token'=>'a','target_domain'=>'foo']); + $this->assertEquals(400, $response->getStatusCode()); + $data = json_decode($response->getContent()); + $this->assertEquals('missing_parameters', $data->error); + } + + public function testTargetAndTargetDomain() { + $this->_createExampleAccount(); + + $response = $this->webmention(['token'=>'a','source'=>'foo','target'=>'foo','target_domain'=>'foo']); + $this->assertEquals(400, $response->getStatusCode()); + $data = json_decode($response->getContent()); + $this->assertEquals('invalid_parameter', $data->error); } public function testInvalidURLs() { @@ -122,7 +152,7 @@ class APITest extends PHPUnit_Framework_TestCase { $this->assertEquals(false, property_exists($data, 'error')); } - public function testQueuesWebmention() { + public function testTargetQueuesWebmention() { $this->_createExampleAccount(); $response = $this->webmention(['token'=>'a','source'=>'http://source.example.com/basictest','target'=>'http://target.example.com']); @@ -130,21 +160,35 @@ class APITest extends PHPUnit_Framework_TestCase { $data = json_decode($response->getContent()); $this->assertEquals(false, property_exists($data, 'error')); $this->assertEquals('queued', $data->status); - $this->assertEquals(true, property_exists($data, 'location')); + $this->_assertQueued('http://source.example.com/basictest', 'http://target.example.com', $data->location); + } - preg_match('/\/webmention\/(.+)/', $data->location, $match); - $this->assertNotNull($match); + public function testTargetDomainQueuesOneWebmention() { + $this->_createExampleAccount(); - # Verify it queued the mention in the database - $d = ORM::for_table('webmentions')->where(['source' => 'http://source.example.com/basictest', 'target' => 'http://target.example.com'])->find_one(); - $this->assertNotNull($d); - $this->assertEquals($match[1], $d->token); + $response = $this->webmention(['token'=>'a','source'=>'http://source.example.com/basictest','target_domain'=>'target.example.com']); + $body = $response->getContent(); + $this->assertEquals(201, $response->getStatusCode(), $body); + $data = json_decode($body); + $this->assertEquals(false, property_exists($data, 'error'), $body); + $this->assertEquals('queued', $data->status, $body); + $this->assertEquals(true, property_exists($data, 'location'), $body); + $this->assertEquals(1, count($data->location), $body); + $this->_assertQueued('http://source.example.com/basictest', 'http://target.example.com', $data->location[0]); + } - # Check the status endpoint to make sure it says it's still queued - $response = $this->status($d->token); - $this->assertEquals(200, $response->getStatusCode()); - $data = json_decode($response->getContent()); - $this->assertEquals('queued', $data->status); + public function testTargetDomainQueuesMultipleWebmentions() { + $this->_createExampleAccount(); + + $response = $this->webmention(['token'=>'a','source'=>'http://source.example.com/basictest','target_domain'=>'example.com']); + $body = $response->getContent(); + $this->assertEquals(201, $response->getStatusCode(), $body); + $data = json_decode($body); + $this->assertEquals(false, property_exists($data, 'error'), $body); + $this->assertEquals('queued', $data->status, $body); + $this->assertEquals(2, count($data->location), $body); + $this->_assertQueued('http://source.example.com/basictest', 'http://target.example.com', $data->location[0]); + $this->_assertQueued('http://source.example.com/basictest', 'http://target2.example.com', $data->location[1]); } public function testStatusNotFound() { diff --git a/tests/data/source.example.com/basictest b/tests/data/source.example.com/basictest index 0841787..b22f10b 100644 --- a/tests/data/source.example.com/basictest +++ b/tests/data/source.example.com/basictest @@ -9,6 +9,6 @@ Connection: keep-alive Test -

This page has a link to target.example.com.

+

This page has links to target.example.com and target2.example.com.