1 |
cd132e86
|
Edson Brandi
|
<?php
|
2 |
|
|
/**
|
3 |
|
|
*
|
4 |
|
|
* Copyright (c) 2011, Dan Myers.
|
5 |
|
|
* Parts copyright (c) 2008, Donovan Schonknecht.
|
6 |
|
|
* All rights reserved.
|
7 |
|
|
*
|
8 |
|
|
* Redistribution and use in source and binary forms, with or without
|
9 |
|
|
* modification, are permitted provided that the following conditions are met:
|
10 |
|
|
*
|
11 |
|
|
* - Redistributions of source code must retain the above copyright notice,
|
12 |
|
|
* this list of conditions and the following disclaimer.
|
13 |
|
|
* - Redistributions in binary form must reproduce the above copyright
|
14 |
|
|
* notice, this list of conditions and the following disclaimer in the
|
15 |
|
|
* documentation and/or other materials provided with the distribution.
|
16 |
|
|
*
|
17 |
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18 |
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19 |
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
20 |
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
21 |
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
22 |
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
23 |
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
24 |
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
25 |
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
26 |
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27 |
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
28 |
|
|
*
|
29 |
|
|
* This is a modified BSD license (the third clause has been removed).
|
30 |
|
|
* The BSD license may be found here:
|
31 |
|
|
* http://www.opensource.org/licenses/bsd-license.php
|
32 |
|
|
*
|
33 |
|
|
* Amazon Route 53 is a trademark of Amazon.com, Inc. or its affiliates.
|
34 |
|
|
*
|
35 |
|
|
* Route53 is based on Donovan Schonknecht's Amazon S3 PHP class, found here:
|
36 |
|
|
* http://undesigned.org.za/2007/10/22/amazon-s3-php-class
|
37 |
|
|
*
|
38 |
|
|
*/
|
39 |
|
|
|
40 |
|
|
/**
|
41 |
|
|
* Amazon Route53 PHP class
|
42 |
|
|
*
|
43 |
|
|
* @link http://sourceforge.net/projects/php-r53/
|
44 |
|
|
* version 0.9.0
|
45 |
|
|
*
|
46 |
|
|
*/
|
47 |
|
|
class Route53
|
48 |
|
|
{
|
49 |
|
|
const API_VERSION = '2010-10-01';
|
50 |
|
|
|
51 |
|
|
protected $__accessKey; // AWS Access key
|
52 |
|
|
protected $__secretKey; // AWS Secret key
|
53 |
|
|
protected $__host;
|
54 |
|
|
|
55 |
|
|
public function getAccessKey() { return $this->__accessKey; }
|
56 |
|
|
public function getSecretKey() { return $this->__secretKey; }
|
57 |
|
|
public function getHost() { return $this->__host; }
|
58 |
|
|
|
59 |
|
|
protected $__verifyHost = 1;
|
60 |
|
|
protected $__verifyPeer = 1;
|
61 |
|
|
|
62 |
|
|
// verifyHost and verifyPeer determine whether curl verifies ssl certificates.
|
63 |
|
|
// It may be necessary to disable these checks on certain systems.
|
64 |
|
|
// These only have an effect if SSL is enabled.
|
65 |
|
|
public function verifyHost() { return $this->__verifyHost; }
|
66 |
|
|
public function enableVerifyHost($enable = true) { $this->__verifyHost = $enable; }
|
67 |
|
|
|
68 |
|
|
public function verifyPeer() { return $this->__verifyPeer; }
|
69 |
|
|
public function enableVerifyPeer($enable = true) { $this->__verifyPeer = $enable; }
|
70 |
|
|
|
71 |
|
|
/**
|
72 |
|
|
* Constructor
|
73 |
|
|
*
|
74 |
|
|
* @param string $accessKey Access key
|
75 |
|
|
* @param string $secretKey Secret key
|
76 |
|
|
* @return void
|
77 |
|
|
*/
|
78 |
|
|
public function __construct($accessKey = null, $secretKey = null, $host = 'route53.amazonaws.com') {
|
79 |
|
|
if ($accessKey !== null && $secretKey !== null) {
|
80 |
|
|
$this->setAuth($accessKey, $secretKey);
|
81 |
|
|
}
|
82 |
|
|
$this->__host = $host;
|
83 |
|
|
}
|
84 |
|
|
|
85 |
|
|
/**
|
86 |
|
|
* Set AWS access key and secret key
|
87 |
|
|
*
|
88 |
|
|
* @param string $accessKey Access key
|
89 |
|
|
* @param string $secretKey Secret key
|
90 |
|
|
* @return void
|
91 |
|
|
*/
|
92 |
|
|
public function setAuth($accessKey, $secretKey) {
|
93 |
|
|
$this->__accessKey = $accessKey;
|
94 |
|
|
$this->__secretKey = $secretKey;
|
95 |
|
|
}
|
96 |
|
|
|
97 |
|
|
/**
|
98 |
|
|
* Lists the hosted zones on the account
|
99 |
|
|
*
|
100 |
|
|
* @param string marker A pagination marker returned by a previous truncated call
|
101 |
|
|
* @param int maxItems The maximum number of items per page. The service uses min($maxItems, 100).
|
102 |
|
|
* @return A list of hosted zones
|
103 |
|
|
*/
|
104 |
|
|
public function listHostedZones($marker = null, $maxItems = 100) {
|
105 |
|
|
$rest = new Route53Request($this, 'hostedzone', 'GET');
|
106 |
|
|
|
107 |
|
|
if($marker !== null) {
|
108 |
|
|
$rest->setParameter('marker', $marker);
|
109 |
|
|
}
|
110 |
|
|
if($maxItems !== 100) {
|
111 |
|
|
$rest->setParameter('maxitems', $maxItems);
|
112 |
|
|
}
|
113 |
|
|
|
114 |
|
|
$rest = $rest->getResponse();
|
115 |
|
|
if($rest->error === false && $rest->code !== 200) {
|
116 |
|
|
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
117 |
|
|
}
|
118 |
|
|
if($rest->error !== false) {
|
119 |
|
|
$this->__triggerError('listHostedZones', $rest->error);
|
120 |
|
|
return false;
|
121 |
|
|
}
|
122 |
|
|
|
123 |
|
|
$response = array();
|
124 |
|
|
if (!isset($rest->body))
|
125 |
|
|
{
|
126 |
|
|
return $response;
|
127 |
|
|
}
|
128 |
|
|
|
129 |
|
|
$zones = array();
|
130 |
|
|
foreach($rest->body->HostedZones->HostedZone as $z)
|
131 |
|
|
{
|
132 |
|
|
$zones[] = $this->parseHostedZone($z);
|
133 |
|
|
}
|
134 |
|
|
$response['HostedZone'] = $zones;
|
135 |
|
|
|
136 |
|
|
if(isset($rest->body->MaxItems)) {
|
137 |
|
|
$response['MaxItems'] = (string)$rest->body->MaxItems;
|
138 |
|
|
}
|
139 |
|
|
|
140 |
|
|
if(isset($rest->body->IsTruncated)) {
|
141 |
|
|
$response['IsTruncated'] = (string)$rest->body->IsTruncated;
|
142 |
|
|
if($response['IsTruncated'] == 'true') {
|
143 |
|
|
$response['NextMarker'] = (string)$rest->body->NextMarker;
|
144 |
|
|
}
|
145 |
|
|
}
|
146 |
|
|
|
147 |
|
|
return $response;
|
148 |
|
|
}
|
149 |
|
|
|
150 |
|
|
/**
|
151 |
|
|
* Retrieves information on a specified hosted zone
|
152 |
|
|
*
|
153 |
|
|
* @param string zoneId The id of the hosted zone, as returned by CreateHostedZoneResponse or ListHostedZoneResponse
|
154 |
|
|
* In other words, if ListHostedZoneResponse shows the zone's Id as '/hostedzone/Z1PA6795UKMFR9',
|
155 |
|
|
* then that full value should be passed here, including the '/hostedzone/' prefix.
|
156 |
|
|
* @return A data structure containing information about the specified zone
|
157 |
|
|
*/
|
158 |
|
|
public function getHostedZone($zoneId) {
|
159 |
|
|
// we'll strip off the leading forward slash, so we can use it as the action directly.
|
160 |
|
|
$zoneId = trim($zoneId, '/');
|
161 |
|
|
|
162 |
|
|
$rest = new Route53Request($this, $zoneId, 'GET');
|
163 |
|
|
|
164 |
|
|
$rest = $rest->getResponse();
|
165 |
|
|
if($rest->error === false && $rest->code !== 200) {
|
166 |
|
|
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
167 |
|
|
}
|
168 |
|
|
if($rest->error !== false) {
|
169 |
|
|
$this->__triggerError('getHostedZone', $rest->error);
|
170 |
|
|
return false;
|
171 |
|
|
}
|
172 |
|
|
|
173 |
|
|
$response = array();
|
174 |
|
|
if (!isset($rest->body))
|
175 |
|
|
{
|
176 |
|
|
return $response;
|
177 |
|
|
}
|
178 |
|
|
|
179 |
|
|
$response['HostedZone'] = $this->parseHostedZone($rest->body->HostedZone);
|
180 |
|
|
$response['NameServers'] = $this->parseDelegationSet($rest->body->DelegationSet);
|
181 |
|
|
|
182 |
|
|
return $response;
|
183 |
|
|
}
|
184 |
|
|
|
185 |
|
|
/**
|
186 |
|
|
* Creates a new hosted zone
|
187 |
|
|
*
|
188 |
|
|
* @param string name The name of the hosted zone (e.g. "example.com.")
|
189 |
|
|
* @param string reference A user-specified unique reference for this request
|
190 |
|
|
* @param string comment An optional user-specified comment to attach to the zone
|
191 |
|
|
* @return A data structure containing information about the newly created zone
|
192 |
|
|
*/
|
193 |
|
|
public function createHostedZone($name, $reference, $comment = '') {
|
194 |
|
|
// hosted zone names must end with a period, but people will forget this a lot...
|
195 |
|
|
if(strrpos($name, '.') != (strlen($name) - 1)) {
|
196 |
|
|
$name .= '.';
|
197 |
|
|
}
|
198 |
|
|
|
199 |
|
|
$data = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
200 |
|
|
$data .= '<CreateHostedZoneRequest xmlns="https://route53.amazonaws.com/doc/'.Route53::API_VERSION."/\">\n";
|
201 |
|
|
$data .= '<Name>'.$name."</Name>\n";
|
202 |
|
|
$data .= '<CallerReference>'.$reference."</CallerReference>\n";
|
203 |
|
|
if(strlen($comment) > 0) {
|
204 |
|
|
$data .= "<HostedZoneConfig>\n";
|
205 |
|
|
$data .= '<Comment>'.$comment."</Comment>\n";
|
206 |
|
|
$data .= "</HostedZoneConfig>\n";
|
207 |
|
|
}
|
208 |
|
|
$data .= "</CreateHostedZoneRequest>\n";
|
209 |
|
|
|
210 |
|
|
$rest = new Route53Request($this, 'hostedzone', 'POST', $data);
|
211 |
|
|
|
212 |
|
|
$rest = $rest->getResponse();
|
213 |
|
|
|
214 |
|
|
if($rest->error === false && !in_array($rest->code, array(200, 201, 202, 204)) ) {
|
215 |
|
|
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
216 |
|
|
}
|
217 |
|
|
if($rest->error !== false) {
|
218 |
|
|
$this->__triggerError('createHostedZone', $rest->error);
|
219 |
|
|
return false;
|
220 |
|
|
}
|
221 |
|
|
|
222 |
|
|
$response = array();
|
223 |
|
|
if (!isset($rest->body))
|
224 |
|
|
{
|
225 |
|
|
return $response;
|
226 |
|
|
}
|
227 |
|
|
|
228 |
|
|
$response['HostedZone'] = $this->parseHostedZone($rest->body->HostedZone);
|
229 |
|
|
$response['ChangeInfo'] = $this->parseChangeInfo($rest->body->ChangeInfo);
|
230 |
|
|
$response['NameServers'] = $this->parseDelegationSet($rest->body->DelegationSet);
|
231 |
|
|
|
232 |
|
|
return $response;
|
233 |
|
|
}
|
234 |
|
|
|
235 |
|
|
/**
|
236 |
|
|
* Retrieves information on a specified hosted zone
|
237 |
|
|
*
|
238 |
|
|
* @param string zoneId The id of the hosted zone, as returned by CreateHostedZoneResponse or ListHostedZoneResponse
|
239 |
|
|
* In other words, if ListHostedZoneResponse shows the zone's Id as '/hostedzone/Z1PA6795UKMFR9',
|
240 |
|
|
* then that full value should be passed here, including the '/hostedzone/' prefix.
|
241 |
|
|
* @return The change request data corresponding to this delete
|
242 |
|
|
*/
|
243 |
|
|
public function deleteHostedZone($zoneId) {
|
244 |
|
|
// we'll strip off the leading forward slash, so we can use it as the action directly.
|
245 |
|
|
$zoneId = trim($zoneId, '/');
|
246 |
|
|
|
247 |
|
|
$rest = new Route53Request($this, $zoneId, 'DELETE');
|
248 |
|
|
|
249 |
|
|
$rest = $rest->getResponse();
|
250 |
|
|
if($rest->error === false && $rest->code !== 200) {
|
251 |
|
|
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
252 |
|
|
}
|
253 |
|
|
if($rest->error !== false) {
|
254 |
|
|
$this->__triggerError('deleteHostedZone', $rest->error);
|
255 |
|
|
return false;
|
256 |
|
|
}
|
257 |
|
|
|
258 |
|
|
if (!isset($rest->body))
|
259 |
|
|
{
|
260 |
|
|
return array();
|
261 |
|
|
}
|
262 |
|
|
|
263 |
|
|
return $this->parseChangeInfo($rest->body->ChangeInfo);
|
264 |
|
|
}
|
265 |
|
|
|
266 |
|
|
/**
|
267 |
|
|
* Retrieves a list of resource record sets for a given zone
|
268 |
|
|
*
|
269 |
|
|
* @param string zoneId The id of the hosted zone, as returned by CreateHostedZoneResponse or ListHostedZoneResponse
|
270 |
|
|
* In other words, if ListHostedZoneResponse shows the zone's Id as '/hostedzone/Z1PA6795UKMFR9',
|
271 |
|
|
* then that full value should be passed here, including the '/hostedzone/' prefix.
|
272 |
|
|
* @param string type The type of resource record set to begin listing from. If this is specified, $name must also be specified.
|
273 |
|
|
* Must be one of: A, AAAA, CNAME, MX, NS, PTR, SOA, SPF, SRV, TXT
|
274 |
|
|
* @param string name The name at which to begin listing resource records (in the lexographic order of records).
|
275 |
|
|
* @param int maxItems The maximum number of results to return. The service uses min($maxItems, 100).
|
276 |
|
|
* @return The list of matching resource record sets
|
277 |
|
|
*/
|
278 |
|
|
public function listResourceRecordSets($zoneId, $type = '', $name = '', $maxItems = 100) {
|
279 |
|
|
// we'll strip off the leading forward slash, so we can use it as the action directly.
|
280 |
|
|
$zoneId = trim($zoneId, '/');
|
281 |
|
|
|
282 |
|
|
$rest = new Route53Request($this, $zoneId.'/rrset', 'GET');
|
283 |
|
|
|
284 |
|
|
if(strlen($type) > 0) {
|
285 |
|
|
$rest->setParameter('type', $type);
|
286 |
|
|
}
|
287 |
|
|
if(strlen($name) > 0) {
|
288 |
|
|
$rest->setParameter('name', $name);
|
289 |
|
|
}
|
290 |
|
|
if($maxItems != 100) {
|
291 |
|
|
$rest->setParameter('maxitems', $maxItems);
|
292 |
|
|
}
|
293 |
|
|
|
294 |
|
|
$rest = $rest->getResponse();
|
295 |
|
|
if($rest->error === false && $rest->code !== 200) {
|
296 |
|
|
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
297 |
|
|
}
|
298 |
|
|
if($rest->error !== false) {
|
299 |
|
|
$this->__triggerError('listResourceRecordSets', $rest->error);
|
300 |
|
|
return false;
|
301 |
|
|
}
|
302 |
|
|
|
303 |
|
|
$response = array();
|
304 |
|
|
if (!isset($rest->body))
|
305 |
|
|
{
|
306 |
|
|
return $response;
|
307 |
|
|
}
|
308 |
|
|
|
309 |
|
|
$recordSets = array();
|
310 |
|
|
foreach($rest->body->ResourceRecordSets->ResourceRecordSet as $set) {
|
311 |
|
|
$recordSets[] = $this->parseResourceRecordSet($set);
|
312 |
|
|
}
|
313 |
|
|
|
314 |
|
|
$response['ResourceRecordSets'] = $recordSets;
|
315 |
|
|
|
316 |
|
|
if(isset($rest->body->MaxItems)) {
|
317 |
|
|
$response['MaxItems'] = (string)$rest->body->MaxItems;
|
318 |
|
|
}
|
319 |
|
|
|
320 |
|
|
if(isset($rest->body->IsTruncated)) {
|
321 |
|
|
$response['IsTruncated'] = (string)$rest->body->IsTruncated;
|
322 |
|
|
if($response['IsTruncated'] == 'true') {
|
323 |
|
|
$response['NextRecordName'] = (string)$rest->body->NextRecordName;
|
324 |
|
|
$response['NextRecordType'] = (string)$rest->body->NextRecordType;
|
325 |
|
|
}
|
326 |
|
|
}
|
327 |
|
|
|
328 |
|
|
return $response;
|
329 |
|
|
}
|
330 |
|
|
|
331 |
|
|
/**
|
332 |
|
|
* Makes the specified resource record set changes (create or delete).
|
333 |
|
|
*
|
334 |
|
|
* @param string zoneId The id of the hosted zone, as returned by CreateHostedZoneResponse or ListHostedZoneResponse
|
335 |
|
|
* In other words, if ListHostedZoneResponse shows the zone's Id as '/hostedzone/Z1PA6795UKMFR9',
|
336 |
|
|
* then that full value should be passed here, including the '/hostedzone/' prefix.
|
337 |
|
|
* @param array changes An array of change objects, as they are returned by the prepareChange utility method.
|
338 |
|
|
* You may also pass a single change object.
|
339 |
|
|
* @param string comment An optional comment to attach to the change request
|
340 |
|
|
* @return The status of the change request
|
341 |
|
|
*/
|
342 |
|
|
public function changeResourceRecordSets($zoneId, $changes, $comment = '') {
|
343 |
|
|
// we'll strip off the leading forward slash, so we can use it as the action directly.
|
344 |
|
|
$zoneId = trim($zoneId, '/');
|
345 |
|
|
|
346 |
|
|
$data = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
347 |
|
|
$data .= '<ChangeResourceRecordSetsRequest xmlns="https://route53.amazonaws.com/doc/'.Route53::API_VERSION."/\">\n";
|
348 |
|
|
$data .= "<ChangeBatch>\n";
|
349 |
|
|
|
350 |
|
|
if(strlen($comment) > 0) {
|
351 |
|
|
$data .= '<Comment>'.$comment."</Comment>\n";
|
352 |
|
|
}
|
353 |
|
|
|
354 |
|
|
if(!is_array($changes)) {
|
355 |
|
|
$changes = array($changes);
|
356 |
|
|
}
|
357 |
|
|
|
358 |
|
|
$data .= "<Changes>\n";
|
359 |
|
|
foreach($changes as $change) {
|
360 |
|
|
$data .= $change;
|
361 |
|
|
}
|
362 |
|
|
$data .= "</Changes>\n";
|
363 |
|
|
|
364 |
|
|
$data .= "</ChangeBatch>\n";
|
365 |
|
|
$data .= "</ChangeResourceRecordSetsRequest>\n";
|
366 |
|
|
|
367 |
|
|
$rest = new Route53Request($this, $zoneId.'/rrset', 'POST', $data);
|
368 |
|
|
|
369 |
|
|
$rest = $rest->getResponse();
|
370 |
|
|
if($rest->error === false && !in_array($rest->code, array(200, 201, 202, 204))) {
|
371 |
|
|
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
372 |
|
|
}
|
373 |
|
|
if($rest->error !== false) {
|
374 |
|
|
$this->__triggerError('changeResourceRecordSets', $rest->error);
|
375 |
|
|
return false;
|
376 |
|
|
}
|
377 |
|
|
|
378 |
|
|
if (!isset($rest->body))
|
379 |
|
|
{
|
380 |
|
|
return array();
|
381 |
|
|
}
|
382 |
|
|
|
383 |
|
|
return $this->parseChangeInfo($rest->body->ChangeInfo);
|
384 |
|
|
}
|
385 |
|
|
|
386 |
|
|
/**
|
387 |
|
|
* Retrieves information on a specified change request
|
388 |
|
|
*
|
389 |
|
|
* @param string changeId The id of the change, as returned by CreateHostedZoneResponse or ChangeResourceRecordSets
|
390 |
|
|
* In other words, if CreateHostedZoneResponse showed the change's Id as '/change/C2682N5HXP0BZ4',
|
391 |
|
|
* then that full value should be passed here, including the '/change/' prefix.
|
392 |
|
|
* @return The status of the change request
|
393 |
|
|
*/
|
394 |
|
|
public function getChange($changeId) {
|
395 |
|
|
// we'll strip off the leading forward slash, so we can use it as the action directly.
|
396 |
|
|
$zoneId = trim($changeId, '/');
|
397 |
|
|
|
398 |
|
|
$rest = new Route53Request($this, $changeId, 'GET');
|
399 |
|
|
|
400 |
|
|
$rest = $rest->getResponse();
|
401 |
|
|
if($rest->error === false && $rest->code !== 200) {
|
402 |
|
|
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
|
403 |
|
|
}
|
404 |
|
|
if($rest->error !== false) {
|
405 |
|
|
$this->__triggerError('getChange', $rest->error);
|
406 |
|
|
return false;
|
407 |
|
|
}
|
408 |
|
|
|
409 |
|
|
if (!isset($rest->body))
|
410 |
|
|
{
|
411 |
|
|
return array();
|
412 |
|
|
}
|
413 |
|
|
|
414 |
|
|
return $this->parseChangeInfo($rest->body->ChangeInfo);
|
415 |
|
|
}
|
416 |
|
|
|
417 |
|
|
|
418 |
|
|
|
419 |
|
|
/**
|
420 |
|
|
* Utility function to parse a HostedZone tag structure
|
421 |
|
|
*/
|
422 |
|
|
private function parseHostedZone($tag) {
|
423 |
|
|
$zone = array();
|
424 |
|
|
$zone['Id'] = (string)$tag->Id;
|
425 |
|
|
$zone['Name'] = (string)$tag->Name;
|
426 |
|
|
$zone['CallerReference'] = (string)$tag->CallerReference;
|
427 |
|
|
|
428 |
|
|
// these might always be set, but check just in case, since
|
429 |
|
|
// their values are option on CreateHostedZone requests
|
430 |
|
|
if(isset($tag->Config) && isset($tag->Config->Comment)) {
|
431 |
|
|
$zone['Config'] = array('Comment' => (string)$tag->Config->Comment);
|
432 |
|
|
}
|
433 |
|
|
|
434 |
|
|
return $zone;
|
435 |
|
|
}
|
436 |
|
|
|
437 |
|
|
/**
|
438 |
|
|
* Utility function to parse a ChangeInfo tag structure
|
439 |
|
|
*/
|
440 |
|
|
private function parseChangeInfo($tag) {
|
441 |
|
|
$info = array();
|
442 |
|
|
$info['Id'] = (string)$tag->Id;
|
443 |
|
|
$info['Status'] = (string)$tag->Status;
|
444 |
|
|
$info['SubmittedAt'] = (string)$tag->SubmittedAt;
|
445 |
|
|
return $info;
|
446 |
|
|
}
|
447 |
|
|
|
448 |
|
|
/**
|
449 |
|
|
* Utility function to parse a DelegationSet tag structure
|
450 |
|
|
*/
|
451 |
|
|
private function parseDelegationSet($tag) {
|
452 |
|
|
$servers = array();
|
453 |
|
|
foreach($tag->NameServers->NameServer as $ns) {
|
454 |
|
|
$servers[] = (string)$ns;
|
455 |
|
|
}
|
456 |
|
|
return $servers;
|
457 |
|
|
}
|
458 |
|
|
|
459 |
|
|
/**
|
460 |
|
|
* Utility function to parse a ResourceRecordSet tag structure
|
461 |
|
|
*/
|
462 |
|
|
private function parseResourceRecordSet($tag) {
|
463 |
|
|
$rrs = array();
|
464 |
|
|
$rrs['Name'] = (string)$tag->Name;
|
465 |
|
|
$rrs['Type'] = (string)$tag->Type;
|
466 |
|
|
$rrs['TTL'] = (string)$tag->TTL;
|
467 |
|
|
$rrs['ResourceRecords'] = array();
|
468 |
|
|
foreach($tag->ResourceRecords->ResourceRecord as $rr) {
|
469 |
|
|
$rrs['ResourceRecords'][] = (string)$rr->Value;
|
470 |
|
|
}
|
471 |
|
|
return $rrs;
|
472 |
|
|
}
|
473 |
|
|
|
474 |
|
|
/**
|
475 |
|
|
* Utility function to prepare a Change object for ChangeResourceRecordSets requests.
|
476 |
|
|
* All fields are required.
|
477 |
|
|
*
|
478 |
|
|
* @param string action The action to perform. One of: CREATE, DELETE
|
479 |
|
|
* @param string name The name to perform the action on.
|
480 |
|
|
* If it does not end with '.', then AWS treats the name as relative to the zone root.
|
481 |
|
|
* @param string type The type of record being modified.
|
482 |
|
|
* Must be one of: A, AAAA, CNAME, MX, NS, PTR, SOA, SPF, SRV, TXT
|
483 |
|
|
* @param int ttl The time-to-live value for this record, in seconds.
|
484 |
|
|
* @param array records An array of resource records to attach to this change.
|
485 |
|
|
* Each member of this array can either be a string, or an array of strings.
|
486 |
|
|
* Passing an array of strings will attach multiple values to a single resource record.
|
487 |
|
|
* If a single string is passed as $records instead of an array,
|
488 |
|
|
* it will be treated as a single-member array.
|
489 |
|
|
* @return object An opaque object containing the change request.
|
490 |
|
|
* Do not write code that depends on the contents of this object, as it may change at any time.
|
491 |
|
|
*/
|
492 |
|
|
public function prepareChange($action, $name, $type, $ttl, $records) {
|
493 |
|
|
$change = "<Change>\n";
|
494 |
|
|
$change .= '<Action>'.$action."</Action>\n";
|
495 |
|
|
$change .= "<ResourceRecordSet>\n";
|
496 |
|
|
$change .= '<Name>'.$name."</Name>\n";
|
497 |
|
|
$change .= '<Type>'.$type."</Type>\n";
|
498 |
|
|
$change .= '<TTL>'.$ttl."</TTL>\n";
|
499 |
|
|
$change .= "<ResourceRecords>\n";
|
500 |
|
|
|
501 |
|
|
if(!is_array($records)) {
|
502 |
|
|
$records = array($records);
|
503 |
|
|
}
|
504 |
|
|
|
505 |
|
|
foreach($records as $record) {
|
506 |
|
|
$change .= "<ResourceRecord>\n";
|
507 |
|
|
if(is_array($record)) {
|
508 |
|
|
foreach($record as $value) {
|
509 |
|
|
$change .= '<Value>'.$value."</Value>\n";
|
510 |
|
|
}
|
511 |
|
|
}
|
512 |
|
|
else {
|
513 |
|
|
$change .= '<Value>'.$record."</Value>\n";
|
514 |
|
|
}
|
515 |
|
|
$change .= "</ResourceRecord>\n";
|
516 |
|
|
}
|
517 |
|
|
|
518 |
|
|
$change .= "</ResourceRecords>\n";
|
519 |
|
|
$change .= "</ResourceRecordSet>\n";
|
520 |
|
|
$change .= "</Change>\n";
|
521 |
|
|
|
522 |
|
|
return $change;
|
523 |
|
|
}
|
524 |
|
|
|
525 |
|
|
/**
|
526 |
|
|
* Trigger an error message
|
527 |
|
|
*
|
528 |
|
|
* @internal Used by member functions to output errors
|
529 |
|
|
* @param array $error Array containing error information
|
530 |
|
|
* @return string
|
531 |
|
|
*/
|
532 |
|
|
public function __triggerError($functionname, $error)
|
533 |
|
|
{
|
534 |
|
|
if($error == false) {
|
535 |
|
|
trigger_error(sprintf("Route53::%s(): Encountered an error, but no description given", $functionname), E_USER_WARNING);
|
536 |
|
|
}
|
537 |
|
|
else if(isset($error['curl']) && $error['curl'])
|
538 |
|
|
{
|
539 |
|
|
trigger_error(sprintf("Route53::%s(): %s %s", $functionname, $error['code'], $error['message']), E_USER_WARNING);
|
540 |
|
|
}
|
541 |
|
|
else if(isset($error['Error']))
|
542 |
|
|
{
|
543 |
|
|
$e = $error['Error'];
|
544 |
|
|
$message = sprintf("Route53::%s(): %s - %s: %s\nRequest Id: %s\n", $functionname, $e['Type'], $e['Code'], $e['Message'], $error['RequestId']);
|
545 |
|
|
trigger_error($message, E_USER_WARNING);
|
546 |
|
|
}
|
547 |
|
|
}
|
548 |
|
|
|
549 |
|
|
/**
|
550 |
|
|
* Callback handler for 503 retries.
|
551 |
|
|
*
|
552 |
|
|
* @internal Used by SimpleDBRequest to call the user-specified callback, if set
|
553 |
|
|
* @param $attempt The number of failed attempts so far
|
554 |
|
|
* @return The retry delay in microseconds, or 0 to stop retrying.
|
555 |
|
|
*/
|
556 |
|
|
public function __executeServiceTemporarilyUnavailableRetryDelay($attempt)
|
557 |
|
|
{
|
558 |
|
|
if(is_callable($this->__serviceUnavailableRetryDelayCallback)) {
|
559 |
|
|
$callback = $this->__serviceUnavailableRetryDelayCallback;
|
560 |
|
|
return $callback($attempt);
|
561 |
|
|
}
|
562 |
|
|
return 0;
|
563 |
|
|
}
|
564 |
|
|
}
|
565 |
|
|
|
566 |
|
|
final class Route53Request
|
567 |
|
|
{
|
568 |
|
|
private $r53, $action, $verb, $data, $parameters = array();
|
569 |
|
|
public $response;
|
570 |
|
|
|
571 |
|
|
/**
|
572 |
|
|
* Constructor
|
573 |
|
|
*
|
574 |
|
|
* @param string $r53 The Route53 object making this request
|
575 |
|
|
* @param string $action SimpleDB action
|
576 |
|
|
* @param string $verb HTTP verb
|
577 |
|
|
* @param string $data For POST requests, the data being posted (optional)
|
578 |
|
|
* @return mixed
|
579 |
|
|
*/
|
580 |
|
|
function __construct($r53, $action, $verb, $data = '') {
|
581 |
|
|
$this->r53 = $r53;
|
582 |
|
|
$this->action = $action;
|
583 |
|
|
$this->verb = $verb;
|
584 |
|
|
$this->data = $data;
|
585 |
|
|
$this->response = new STDClass;
|
586 |
|
|
$this->response->error = false;
|
587 |
|
|
}
|
588 |
|
|
|
589 |
|
|
/**
|
590 |
|
|
* Set request parameter
|
591 |
|
|
*
|
592 |
|
|
* @param string $key Key
|
593 |
|
|
* @param string $value Value
|
594 |
|
|
* @param boolean $replace Whether to replace the key if it already exists (default true)
|
595 |
|
|
* @return void
|
596 |
|
|
*/
|
597 |
|
|
public function setParameter($key, $value, $replace = true) {
|
598 |
|
|
if(!$replace && isset($this->parameters[$key]))
|
599 |
|
|
{
|
600 |
|
|
$temp = (array)($this->parameters[$key]);
|
601 |
|
|
$temp[] = $value;
|
602 |
|
|
$this->parameters[$key] = $temp;
|
603 |
|
|
}
|
604 |
|
|
else
|
605 |
|
|
{
|
606 |
|
|
$this->parameters[$key] = $value;
|
607 |
|
|
}
|
608 |
|
|
}
|
609 |
|
|
|
610 |
|
|
/**
|
611 |
|
|
* Get the response
|
612 |
|
|
*
|
613 |
|
|
* @return object | false
|
614 |
|
|
*/
|
615 |
|
|
public function getResponse() {
|
616 |
|
|
|
617 |
|
|
$params = array();
|
618 |
|
|
foreach ($this->parameters as $var => $value)
|
619 |
|
|
{
|
620 |
|
|
if(is_array($value))
|
621 |
|
|
{
|
622 |
|
|
foreach($value as $v)
|
623 |
|
|
{
|
624 |
|
|
$params[] = $var.'='.$this->__customUrlEncode($v);
|
625 |
|
|
}
|
626 |
|
|
}
|
627 |
|
|
else
|
628 |
|
|
{
|
629 |
|
|
$params[] = $var.'='.$this->__customUrlEncode($value);
|
630 |
|
|
}
|
631 |
|
|
}
|
632 |
|
|
|
633 |
|
|
sort($params, SORT_STRING);
|
634 |
|
|
|
635 |
|
|
$query = implode('&', $params);
|
636 |
|
|
|
637 |
|
|
// must be in format 'Sun, 06 Nov 1994 08:49:37 GMT'
|
638 |
|
|
$date = gmdate('D, d M Y H:i:s e');
|
639 |
|
|
|
640 |
|
|
$headers = array();
|
641 |
|
|
$headers[] = 'Date: '.$date;
|
642 |
|
|
$headers[] = 'Host: '.$this->r53->getHost();
|
643 |
|
|
|
644 |
|
|
$auth = 'AWS3-HTTPS AWSAccessKeyId='.$this->r53->getAccessKey();
|
645 |
|
|
$auth .= ',Algorithm=HmacSHA256,Signature='.$this->__getSignature($date);
|
646 |
|
|
$headers[] = 'X-Amzn-Authorization: '.$auth;
|
647 |
|
|
|
648 |
|
|
$url = 'https://'.$this->r53->getHost().'/'.Route53::API_VERSION.'/'.$this->action.'?'.$query;
|
649 |
|
|
|
650 |
|
|
// Basic setup
|
651 |
|
|
$curl = curl_init();
|
652 |
|
|
curl_setopt($curl, CURLOPT_USERAGENT, 'Route53/php');
|
653 |
|
|
|
654 |
|
|
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, ($this->r53->verifyHost() ? 1 : 0));
|
655 |
|
|
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, ($this->r53->verifyPeer() ? 1 : 0));
|
656 |
|
|
|
657 |
|
|
curl_setopt($curl, CURLOPT_URL, $url);
|
658 |
|
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
|
659 |
|
|
curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback'));
|
660 |
|
|
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
|
661 |
|
|
|
662 |
|
|
// Request types
|
663 |
|
|
switch ($this->verb) {
|
664 |
|
|
case 'GET': break;
|
665 |
|
|
case 'POST':
|
666 |
|
|
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb);
|
667 |
|
|
if(strlen($this->data) > 0) {
|
668 |
|
|
curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data);
|
669 |
|
|
$headers[] = 'Content-Type: text/plain';
|
670 |
|
|
$headers[] = 'Content-Length: '.strlen($this->data);
|
671 |
|
|
}
|
672 |
|
|
break;
|
673 |
|
|
case 'DELETE':
|
674 |
|
|
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
|
675 |
|
|
break;
|
676 |
|
|
default: break;
|
677 |
|
|
}
|
678 |
|
|
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
|
679 |
|
|
curl_setopt($curl, CURLOPT_HEADER, false);
|
680 |
|
|
|
681 |
|
|
// Execute, grab errors
|
682 |
|
|
if (curl_exec($curl)) {
|
683 |
|
|
$this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
684 |
|
|
} else {
|
685 |
|
|
$this->response->error = array(
|
686 |
|
|
'curl' => true,
|
687 |
|
|
'code' => curl_errno($curl),
|
688 |
|
|
'message' => curl_error($curl),
|
689 |
|
|
'resource' => $this->resource
|
690 |
|
|
);
|
691 |
|
|
}
|
692 |
|
|
|
693 |
|
|
@curl_close($curl);
|
694 |
|
|
|
695 |
|
|
// Parse body into XML
|
696 |
|
|
if ($this->response->error === false && isset($this->response->body)) {
|
697 |
|
|
$this->response->body = simplexml_load_string($this->response->body);
|
698 |
|
|
|
699 |
|
|
// Grab Route53 errors
|
700 |
|
|
if (!in_array($this->response->code, array(200, 201, 202, 204))
|
701 |
|
|
&& isset($this->response->body->Error)) {
|
702 |
|
|
$error = $this->response->body->Error;
|
703 |
|
|
$output = array();
|
704 |
|
|
$output['curl'] = false;
|
705 |
|
|
$output['Error'] = array();
|
706 |
|
|
$output['Error']['Type'] = (string)$error->Type;
|
707 |
|
|
$output['Error']['Code'] = (string)$error->Code;
|
708 |
|
|
$output['Error']['Message'] = (string)$error->Message;
|
709 |
|
|
$output['RequestId'] = (string)$this->response->body->RequestId;
|
710 |
|
|
|
711 |
|
|
$this->response->error = $output;
|
712 |
|
|
unset($this->response->body);
|
713 |
|
|
}
|
714 |
|
|
}
|
715 |
|
|
|
716 |
|
|
return $this->response;
|
717 |
|
|
}
|
718 |
|
|
|
719 |
|
|
/**
|
720 |
|
|
* CURL write callback
|
721 |
|
|
*
|
722 |
|
|
* @param resource &$curl CURL resource
|
723 |
|
|
* @param string &$data Data
|
724 |
|
|
* @return integer
|
725 |
|
|
*/
|
726 |
|
|
private function __responseWriteCallback(&$curl, &$data) {
|
727 |
|
|
$this->response->body .= $data;
|
728 |
|
|
return strlen($data);
|
729 |
|
|
}
|
730 |
|
|
|
731 |
|
|
/**
|
732 |
|
|
* Contributed by afx114
|
733 |
|
|
* URL encode the parameters as per http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?Query_QueryAuth.html
|
734 |
|
|
* PHP's rawurlencode() follows RFC 1738, not RFC 3986 as required by Amazon. The only difference is the tilde (~), so convert it back after rawurlencode
|
735 |
|
|
* See: http://www.morganney.com/blog/API/AWS-Product-Advertising-API-Requires-a-Signed-Request.php
|
736 |
|
|
*
|
737 |
|
|
* @param string $var String to encode
|
738 |
|
|
* @return string
|
739 |
|
|
*/
|
740 |
|
|
private function __customUrlEncode($var) {
|
741 |
|
|
return str_replace('%7E', '~', rawurlencode($var));
|
742 |
|
|
}
|
743 |
|
|
|
744 |
|
|
/**
|
745 |
|
|
* Generate the auth string using Hmac-SHA256
|
746 |
|
|
*
|
747 |
|
|
* @internal Used by SimpleDBRequest::getResponse()
|
748 |
|
|
* @param string $string String to sign
|
749 |
|
|
* @return string
|
750 |
|
|
*/
|
751 |
|
|
private function __getSignature($string) {
|
752 |
|
|
return base64_encode(hash_hmac('sha256', $string, $this->r53->getSecretKey(), true));
|
753 |
|
|
}
|
754 |
|
|
}
|