Skip to content

Commit 379b715

Browse files
committed
Merge pull request #17 from clue-labs/uri
Build properly escaped URIs via Browser
2 parents 98d5c14 + 1c39a2e commit 379b715

File tree

6 files changed

+90
-59
lines changed

6 files changed

+90
-59
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ listing from the given ViewVC URL:
1010
```php
1111
$loop = React\EventLoop\Factory::create();
1212
$browser = new Clue\React\Buzz\Browser($loop);
13-
$client = new Client('http://example.com/viewvc/', $browser);
13+
$client = new Client($browser->withBase('http://example.com/viewvc/'));
1414

1515
$client->fetchDirectory('/')->then(function ($files) {
1616
echo 'Files: ' . implode(', ', $files) . PHP_EOL;
@@ -34,9 +34,12 @@ in order to handle async requests:
3434
$loop = React\EventLoop\Factory::create();
3535
$browser = new Clue\React\Buzz\Browser($loop);
3636

37-
$client = new Client($url, $browser);
37+
$client = new Client($browser->withBase('http://example.com/viewvc/'));
3838
```
3939

40+
The `Client` API uses relative URIs to reference files and directories in your
41+
ViewVC installation, so make sure to apply the base URI as depicted above.
42+
4043
If you need custom DNS or proxy settings, you can explicitly pass a
4144
custom [`Browser`](https://github.com/clue/php-buzz-react#browser) instance.
4245

@@ -95,7 +98,7 @@ use Clue\React\Block;
9598
$loop = React\EventLoop\Factory::create();
9699
$browser = new Clue\React\Buzz\Browser($loop);
97100

98-
$client = new Client($url /* change me */, $browser);
101+
$client = new Client($browser->withBase($uri /* change me */));
99102
$promise = $client->fetchFile($path /* change me */);
100103

101104
try {

examples/directory.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@
2626

2727
$browser = new Browser($loop, $sender);
2828

29-
$client = new Client($url, $browser);
29+
$client = new Client($browser->withBase($url));
3030

3131
if (substr($path, -1) === '/') {
32-
$client->fetchDirectory($path, $revision)->then('var_dump', 'printf');
32+
$client->fetchDirectory($path, $revision)->then('print_r', 'printf');
3333
} else {
34-
$client->fetchFile($path, $revision)->then('printf', 'printf');
34+
$client->fetchFile($path, $revision)->then('print_r', 'printf');
3535
}
3636

3737
$loop->run();

src/Client.php

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@
1414

1515
class Client
1616
{
17-
private $url;
1817
private $brower;
1918

20-
public function __construct($url, Browser $browser, Parser $parser = null, Loader $loader = null)
19+
public function __construct(Browser $browser, Parser $parser = null, Loader $loader = null)
2120
{
2221
if ($parser === null) {
2322
$parser = new Parser();
@@ -31,7 +30,6 @@ public function __construct($url, Browser $browser, Parser $parser = null, Loade
3130
// 'follow_redirects' => false
3231
// ));
3332

34-
$this->url = rtrim($url, '/') . '/';
3533
$this->browser = $browser;
3634
$this->parser = $parser;
3735
$this->loader = $loader;
@@ -43,17 +41,20 @@ public function fetchFile($path, $revision = null)
4341
return Promise\reject(new InvalidArgumentException('File path MUST NOT end with trailing slash'));
4442
}
4543

46-
$url = $path . '?view=co';
47-
if ($revision !== null) {
48-
$url .= '&pathrev=' . $revision;
49-
}
50-
5144
// TODO: fetching a directory redirects to path with trailing slash
5245
// TODO: status returns 200 OK, but displays an error message anyways..
5346
// TODO: see not-a-file.html
5447
// TODO: reject all paths with trailing slashes
5548

56-
return $this->fetch($url);
49+
return $this->fetch(
50+
$this->browser->resolve(
51+
'/{+path}?view=co{&pathrev}',
52+
array(
53+
'path' => ltrim($path, '/'),
54+
'pathrev' => $revision
55+
)
56+
)
57+
);
5758
}
5859

5960
public function fetchDirectory($path, $revision = null, $showAttic = false)
@@ -62,21 +63,19 @@ public function fetchDirectory($path, $revision = null, $showAttic = false)
6263
return Promise\reject(new InvalidArgumentException('Directory path MUST end with trailing slash'));
6364
}
6465

65-
$url = $path;
66-
67-
if ($revision !== null) {
68-
$url .= '?pathrev=' . $revision;
69-
}
70-
71-
if ($showAttic) {
72-
$url .= (strpos($url, '?') === false) ? '?' : '&';
73-
$url .= 'hideattic=0';
74-
}
75-
7666
// TODO: path MUST end with trailing slash
7767
// TODO: accessing files will redirect to file with relative location URL (not supported by clue/buzz-react)
7868

79-
return $this->fetchXml($url)->then(function (SimpleXMLElement $xml) {
69+
return $this->fetchXml(
70+
$this->browser->resolve(
71+
'/{+path}{?pathrev,hideattic}',
72+
array(
73+
'path' => ltrim($path, '/'),
74+
'pathrev' => $revision,
75+
'hideattic' => $showAttic ? '0' : null
76+
)
77+
)
78+
)->then(function (SimpleXMLElement $xml) {
8079
// TODO: reject if this is a file, instead of directory => contains "Log of" instead of "Index of"
8180
// TODO: see is-a-file.html
8281

@@ -86,23 +85,31 @@ public function fetchDirectory($path, $revision = null, $showAttic = false)
8685

8786
public function fetchPatch($path, $r1, $r2)
8887
{
89-
$url = $path . '?view=patch&r1=' . $r1 . '&r2=' . $r2;
90-
91-
return $this->fetch($url);
88+
return $this->fetch(
89+
$this->browser->resolve(
90+
'/{+path}?view=patch{&r1,r2}',
91+
array(
92+
'path' => ltrim($path, '/'),
93+
'r1' => $r1,
94+
'r2' => $r2
95+
)
96+
)
97+
);
9298
}
9399

94100
public function fetchLog($path, $revision = null)
95101
{
96-
$url = $path . '?view=log';
97-
98102
// TODO: invalid revision shows error page, but HTTP 200 OK
99103

100-
if ($revision !== null) {
101-
$url .= (strpos($url, '?') === false) ? '?' : '&';
102-
$url .= 'pathrev=' . $revision;
103-
}
104-
105-
return $this->fetchXml($url)->then(array($this->parser, 'parseLogEntries'));
104+
return $this->fetchXml(
105+
$this->browser->resolve(
106+
'/{+path}?view=log{&pathrev}',
107+
array(
108+
'path' => ltrim($path, '/'),
109+
'pathrev' => $revision
110+
)
111+
)
112+
)->then(array($this->parser, 'parseLogEntries'));
106113
}
107114

108115
public function fetchRevisionPrevious($path, $revision)
@@ -123,9 +130,14 @@ public function fetchAllPreviousRevisions($path)
123130

124131
private function fetchLogXml($path)
125132
{
126-
$url = $path . '?view=log';
127-
128-
return $this->fetchXml($url);
133+
return $this->fetchXml(
134+
$this->browser->resolve(
135+
'/{+path}?view=log',
136+
array(
137+
'path' => ltrim($path, '/')
138+
)
139+
)
140+
);
129141
}
130142

131143
private function fetchXml($url)
@@ -135,7 +147,7 @@ private function fetchXml($url)
135147

136148
private function fetch($url)
137149
{
138-
return $this->browser->get($this->url . ltrim($url, '/'))->then(
150+
return $this->browser->get($url)->then(
139151
function (Response $response) {
140152
return (string)$response->getBody();
141153
},

tests/ClientTest.php

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,22 @@
44
use Clue\React\Buzz\Message\Response;
55
use Clue\React\Buzz\Message\Body;
66
use React\Promise;
7+
use Clue\React\Buzz\Browser;
8+
use Clue\React\Buzz\Message\Request;
79

810
class ClientTest extends TestCase
911
{
10-
private $url;
11-
private $browser;
12+
private $uri = 'http://viewvc.example.org/';
13+
private $sender;
1214
private $client;
1315

1416
public function setUp()
1517
{
16-
$this->url = 'http://viewvc.example.org';
17-
$this->browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock();
18-
$this->client = new Client($this->url, $this->browser);
18+
$this->sender = $this->getMockBuilder('Clue\React\Buzz\Io\Sender')->disableOriginalConstructor()->getMock();
19+
20+
$browser = new Browser($this->getMock('React\EventLoop\LoopInterface'), $this->sender);
21+
22+
$this->client = new Client($browser->withBase($this->uri));
1923
}
2024

2125
public function testInvalidDirectory()
@@ -33,7 +37,8 @@ public function testInvalidFile()
3337
public function testFetchFile()
3438
{
3539
$response = new Response('HTTP/1.0', 200, 'OK', array(), new Body('# hello'));
36-
$this->browser->expects($this->once())->method('get')->with($this->equalTo('http://viewvc.example.org/README.md?view=co'))->will($this->returnValue(Promise\resolve($response)));
40+
41+
$this->expectRequest($this->uri . 'README.md?view=co')->will($this->returnValue(Promise\resolve($response)));
3742

3843
$promise = $this->client->fetchFile('README.md');
3944

@@ -42,17 +47,16 @@ public function testFetchFile()
4247

4348
public function testFetchFileExcessiveSlashesAreIgnored()
4449
{
45-
$this->browser->expects($this->once())->method('get')->with($this->equalTo('http://viewvc.example.org/README.md?view=co'))->will($this->returnValue(Promise\reject()));
50+
$this->expectRequest($this->uri . 'README.md?view=co')->will($this->returnValue(Promise\reject()));
4651

47-
$client = new Client($this->url . '/', $this->browser);
48-
$promise = $client->fetchFile('/README.md');
52+
$promise = $this->client->fetchFile('/README.md');
4953

5054
$this->expectPromiseReject($promise);
5155
}
5256

5357
public function testFetchFileRevision()
5458
{
55-
$this->browser->expects($this->once())->method('get')->with($this->equalTo('http://viewvc.example.org/README.md?view=co&pathrev=1.0'))->will($this->returnValue(Promise\reject()));
59+
$this->expectRequest($this->uri . 'README.md?view=co&pathrev=1.0')->will($this->returnValue(Promise\reject()));
5660

5761
$promise = $this->client->fetchFile('/README.md', '1.0');
5862

@@ -61,7 +65,7 @@ public function testFetchFileRevision()
6165

6266
public function testFetchDirectoryRevision()
6367
{
64-
$this->browser->expects($this->once())->method('get')->with($this->equalTo('http://viewvc.example.org/directory/?pathrev=1.0'))->will($this->returnValue(Promise\reject()));
68+
$this->expectRequest($this->uri . 'directory/?pathrev=1.0')->will($this->returnValue(Promise\reject()));
6569

6670
$promise = $this->client->fetchDirectory('/directory/', '1.0');
6771

@@ -70,7 +74,7 @@ public function testFetchDirectoryRevision()
7074

7175
public function testFetchDirectoryAttic()
7276
{
73-
$this->browser->expects($this->once())->method('get')->with($this->equalTo('http://viewvc.example.org/directory/?hideattic=0'))->will($this->returnValue(Promise\reject()));
77+
$this->expectRequest($this->uri . 'directory/?hideattic=0')->will($this->returnValue(Promise\reject()));
7478

7579
$promise = $this->client->fetchDirectory('/directory/', null, true);
7680

@@ -79,7 +83,7 @@ public function testFetchDirectoryAttic()
7983

8084
public function testFetchDirectoryRevisionAttic()
8185
{
82-
$this->browser->expects($this->once())->method('get')->with($this->equalTo('http://viewvc.example.org/directory/?pathrev=1.1&hideattic=0'))->will($this->returnValue(Promise\reject()));
86+
$this->expectRequest($this->uri . 'directory/?pathrev=1.1&hideattic=0')->will($this->returnValue(Promise\reject()));
8387

8488
$promise = $this->client->fetchDirectory('/directory/', '1.1', true);
8589

@@ -88,7 +92,7 @@ public function testFetchDirectoryRevisionAttic()
8892

8993
public function testFetchLogRevision()
9094
{
91-
$this->browser->expects($this->once())->method('get')->with($this->equalTo('http://viewvc.example.org/README.md?view=log&pathrev=1.0'))->will($this->returnValue(Promise\reject()));
95+
$this->expectRequest($this->uri . 'README.md?view=log&pathrev=1.0')->will($this->returnValue(Promise\reject()));
9296

9397
$promise = $this->client->fetchLog('/README.md', '1.0');
9498

@@ -97,10 +101,22 @@ public function testFetchLogRevision()
97101

98102
public function testFetchPatch()
99103
{
100-
$this->browser->expects($this->once())->method('get')->with($this->equalTo('http://viewvc.example.org/README.md?view=patch&r1=1.0&r2=1.1'))->will($this->returnValue(Promise\reject()));
104+
$this->expectRequest($this->uri . 'README.md?view=patch&r1=1.0&r2=1.1')->will($this->returnValue(Promise\reject()));
101105

102106
$promise = $this->client->fetchPatch('/README.md', '1.0', '1.1');
103107

104108
$this->expectPromiseReject($promise);
105109
}
110+
111+
private function expectRequest($uri)
112+
{
113+
$that = $this;
114+
115+
return $this->sender->expects($this->once())->method('send')->with($this->callback(function (Request $request) use ($that, $uri) {
116+
$that->assertEquals('GET', $request->getMethod());
117+
$that->assertEquals($uri, $request->getUri());
118+
119+
return true;
120+
}));
121+
}
106122
}

tests/FunctionalApacheClientTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public function setUp()
1717
$this->loop = LoopFactory::create();
1818
$browser = new Browser($this->loop);
1919

20-
$this->viewvc = new Client($url, $browser);
20+
$this->viewvc = new Client($browser->withBase($url));
2121
}
2222

2323
public function testFetchDirectory()

tests/FunctionalGentooClientTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public function setUp()
3636
$this->markTestSkipped('Unable to reach Gentoo ViewVC');
3737
}
3838

39-
$this->viewvc = new Client($url, $browser);
39+
$this->viewvc = new Client($browser->withBase($url));
4040
}
4141

4242
public function testFetchDirectoryAttic()

0 commit comments

Comments
 (0)