diff --git a/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns/r53.inc b/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns/r53.inc index d7ebc65fb..46a83a380 100644 --- a/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns/r53.inc +++ b/dns/dyndns/src/etc/inc/plugins.inc.d/dyndns/r53.inc @@ -45,709 +45,733 @@ */ class Route53 { - const API_VERSION = '2010-10-01'; - - protected $__accessKey; // AWS Access key - protected $__secretKey; // AWS Secret key - protected $__host; - - public function getAccessKey() { return $this->__accessKey; } - public function getSecretKey() { return $this->__secretKey; } - public function getHost() { return $this->__host; } - - protected $__verifyHost = 1; - protected $__verifyPeer = 1; - - // verifyHost and verifyPeer determine whether curl verifies ssl certificates. - // It may be necessary to disable these checks on certain systems. - // These only have an effect if SSL is enabled. - public function verifyHost() { return $this->__verifyHost; } - public function enableVerifyHost($enable = true) { $this->__verifyHost = $enable; } - - public function verifyPeer() { return $this->__verifyPeer; } - public function enableVerifyPeer($enable = true) { $this->__verifyPeer = $enable; } - - /** - * Constructor - * - * @param string $accessKey Access key - * @param string $secretKey Secret key - * @return void - */ - public function __construct($accessKey = null, $secretKey = null, $host = 'route53.amazonaws.com') { - if ($accessKey !== null && $secretKey !== null) { - $this->setAuth($accessKey, $secretKey); - } - $this->__host = $host; - } - - /** - * Set AWS access key and secret key - * - * @param string $accessKey Access key - * @param string $secretKey Secret key - * @return void - */ - public function setAuth($accessKey, $secretKey) { - $this->__accessKey = $accessKey; - $this->__secretKey = $secretKey; - } - - /** - * Lists the hosted zones on the account - * - * @param string marker A pagination marker returned by a previous truncated call - * @param int maxItems The maximum number of items per page. The service uses min($maxItems, 100). - * @return A list of hosted zones - */ - public function listHostedZones($marker = null, $maxItems = 100) { - $rest = new Route53Request($this, 'hostedzone', 'GET'); - - if($marker !== null) { - $rest->setParameter('marker', $marker); - } - if($maxItems !== 100) { - $rest->setParameter('maxitems', $maxItems); - } - - $rest = $rest->getResponse(); - if($rest->error === false && $rest->code !== 200) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('listHostedZones', $rest->error); - return false; - } - - $response = array(); - if (!isset($rest->body)) - { - return $response; - } - - $zones = array(); - foreach($rest->body->HostedZones->HostedZone as $z) - { - $zones[] = $this->parseHostedZone($z); - } - $response['HostedZone'] = $zones; - - if(isset($rest->body->MaxItems)) { - $response['MaxItems'] = (string)$rest->body->MaxItems; - } - - if(isset($rest->body->IsTruncated)) { - $response['IsTruncated'] = (string)$rest->body->IsTruncated; - if($response['IsTruncated'] == 'true') { - $response['NextMarker'] = (string)$rest->body->NextMarker; - } - } - - return $response; - } - - /** - * Retrieves information on a specified hosted zone - * - * @param string zoneId The id of the hosted zone, as returned by CreateHostedZoneResponse or ListHostedZoneResponse - * In other words, if ListHostedZoneResponse shows the zone's Id as '/hostedzone/Z1PA6795UKMFR9', - * then that full value should be passed here, including the '/hostedzone/' prefix. - * @return A data structure containing information about the specified zone - */ - public function getHostedZone($zoneId) { - // we'll strip off the leading forward slash, so we can use it as the action directly. - $zoneId = trim($zoneId, '/'); - - $rest = new Route53Request($this, $zoneId, 'GET'); - - $rest = $rest->getResponse(); - if($rest->error === false && $rest->code !== 200) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('getHostedZone', $rest->error); - return false; - } - - $response = array(); - if (!isset($rest->body)) - { - return $response; - } - - $response['HostedZone'] = $this->parseHostedZone($rest->body->HostedZone); - $response['NameServers'] = $this->parseDelegationSet($rest->body->DelegationSet); - - return $response; - } - - /** - * Creates a new hosted zone - * - * @param string name The name of the hosted zone (e.g. "example.com.") - * @param string reference A user-specified unique reference for this request - * @param string comment An optional user-specified comment to attach to the zone - * @return A data structure containing information about the newly created zone - */ - public function createHostedZone($name, $reference, $comment = '') { - // hosted zone names must end with a period, but people will forget this a lot... - if(strrpos($name, '.') != (strlen($name) - 1)) { - $name .= '.'; - } - - $data = "\n"; - $data .= '\n"; - $data .= ''.$name."\n"; - $data .= ''.$reference."\n"; - if(strlen($comment) > 0) { - $data .= "\n"; - $data .= ''.$comment."\n"; - $data .= "\n"; - } - $data .= "\n"; - - $rest = new Route53Request($this, 'hostedzone', 'POST', $data); - - $rest = $rest->getResponse(); - - if($rest->error === false && !in_array($rest->code, array(200, 201, 202, 204)) ) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('createHostedZone', $rest->error); - return false; - } - - $response = array(); - if (!isset($rest->body)) - { - return $response; - } - - $response['HostedZone'] = $this->parseHostedZone($rest->body->HostedZone); - $response['ChangeInfo'] = $this->parseChangeInfo($rest->body->ChangeInfo); - $response['NameServers'] = $this->parseDelegationSet($rest->body->DelegationSet); - - return $response; - } - - /** - * Retrieves information on a specified hosted zone - * - * @param string zoneId The id of the hosted zone, as returned by CreateHostedZoneResponse or ListHostedZoneResponse - * In other words, if ListHostedZoneResponse shows the zone's Id as '/hostedzone/Z1PA6795UKMFR9', - * then that full value should be passed here, including the '/hostedzone/' prefix. - * @return The change request data corresponding to this delete - */ - public function deleteHostedZone($zoneId) { - // we'll strip off the leading forward slash, so we can use it as the action directly. - $zoneId = trim($zoneId, '/'); - - $rest = new Route53Request($this, $zoneId, 'DELETE'); - - $rest = $rest->getResponse(); - if($rest->error === false && $rest->code !== 200) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('deleteHostedZone', $rest->error); - return false; - } - - if (!isset($rest->body)) - { - return array(); - } - - return $this->parseChangeInfo($rest->body->ChangeInfo); - } - - /** - * Retrieves a list of resource record sets for a given zone - * - * @param string zoneId The id of the hosted zone, as returned by CreateHostedZoneResponse or ListHostedZoneResponse - * In other words, if ListHostedZoneResponse shows the zone's Id as '/hostedzone/Z1PA6795UKMFR9', - * then that full value should be passed here, including the '/hostedzone/' prefix. - * @param string type The type of resource record set to begin listing from. If this is specified, $name must also be specified. - * Must be one of: A, AAAA, CNAME, MX, NS, PTR, SOA, SPF, SRV, TXT - * @param string name The name at which to begin listing resource records (in the lexographic order of records). - * @param int maxItems The maximum number of results to return. The service uses min($maxItems, 100). - * @return The list of matching resource record sets - */ - public function listResourceRecordSets($zoneId, $type = '', $name = '', $maxItems = 100) { - // we'll strip off the leading forward slash, so we can use it as the action directly. - $zoneId = trim($zoneId, '/'); - - $rest = new Route53Request($this, $zoneId.'/rrset', 'GET'); - - if(strlen($type) > 0) { - $rest->setParameter('type', $type); - } - if(strlen($name) > 0) { - $rest->setParameter('name', $name); - } - if($maxItems != 100) { - $rest->setParameter('maxitems', $maxItems); - } - - $rest = $rest->getResponse(); - if($rest->error === false && $rest->code !== 200) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('listResourceRecordSets', $rest->error); - return false; - } - - $response = array(); - if (!isset($rest->body)) - { - return $response; - } - - $recordSets = array(); - foreach($rest->body->ResourceRecordSets->ResourceRecordSet as $set) { - $recordSets[] = $this->parseResourceRecordSet($set); - } - - $response['ResourceRecordSets'] = $recordSets; - - if(isset($rest->body->MaxItems)) { - $response['MaxItems'] = (string)$rest->body->MaxItems; - } - - if(isset($rest->body->IsTruncated)) { - $response['IsTruncated'] = (string)$rest->body->IsTruncated; - if($response['IsTruncated'] == 'true') { - $response['NextRecordName'] = (string)$rest->body->NextRecordName; - $response['NextRecordType'] = (string)$rest->body->NextRecordType; - } - } - - return $response; - } - - /** - * Makes the specified resource record set changes (create or delete). - * - * @param string zoneId The id of the hosted zone, as returned by CreateHostedZoneResponse or ListHostedZoneResponse - * In other words, if ListHostedZoneResponse shows the zone's Id as '/hostedzone/Z1PA6795UKMFR9', - * then that full value should be passed here, including the '/hostedzone/' prefix. - * @param array changes An array of change objects, as they are returned by the prepareChange utility method. - * You may also pass a single change object. - * @param string comment An optional comment to attach to the change request - * @return The status of the change request - */ - public function changeResourceRecordSets($zoneId, $changes, $comment = '') { - // we'll strip off the leading forward slash, so we can use it as the action directly. - $zoneId = trim($zoneId, '/'); - - $data = "\n"; - $data .= '\n"; - $data .= "\n"; - - if(strlen($comment) > 0) { - $data .= ''.$comment."\n"; - } - - if(!is_array($changes)) { - $changes = array($changes); - } - - $data .= "\n"; - foreach($changes as $change) { - $data .= $change; - } - $data .= "\n"; - - $data .= "\n"; - $data .= "\n"; - - $rest = new Route53Request($this, $zoneId.'/rrset', 'POST', $data); - - $rest = $rest->getResponse(); - if($rest->error === false && !in_array($rest->code, array(200, 201, 202, 204))) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('changeResourceRecordSets', $rest->error); - return false; - } - - if (!isset($rest->body)) - { - return array(); - } - - return $this->parseChangeInfo($rest->body->ChangeInfo); - } - - /** - * Retrieves information on a specified change request - * - * @param string changeId The id of the change, as returned by CreateHostedZoneResponse or ChangeResourceRecordSets - * In other words, if CreateHostedZoneResponse showed the change's Id as '/change/C2682N5HXP0BZ4', - * then that full value should be passed here, including the '/change/' prefix. - * @return The status of the change request - */ - public function getChange($changeId) { - // we'll strip off the leading forward slash, so we can use it as the action directly. - $zoneId = trim($changeId, '/'); - - $rest = new Route53Request($this, $changeId, 'GET'); - - $rest = $rest->getResponse(); - if($rest->error === false && $rest->code !== 200) { - $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); - } - if($rest->error !== false) { - $this->__triggerError('getChange', $rest->error); - return false; - } - - if (!isset($rest->body)) - { - return array(); - } - - return $this->parseChangeInfo($rest->body->ChangeInfo); - } - - - - /** - * Utility function to parse a HostedZone tag structure - */ - private function parseHostedZone($tag) { - $zone = array(); - $zone['Id'] = (string)$tag->Id; - $zone['Name'] = (string)$tag->Name; - $zone['CallerReference'] = (string)$tag->CallerReference; - - // these might always be set, but check just in case, since - // their values are option on CreateHostedZone requests - if(isset($tag->Config) && isset($tag->Config->Comment)) { - $zone['Config'] = array('Comment' => (string)$tag->Config->Comment); - } - - return $zone; - } - - /** - * Utility function to parse a ChangeInfo tag structure - */ - private function parseChangeInfo($tag) { - $info = array(); - $info['Id'] = (string)$tag->Id; - $info['Status'] = (string)$tag->Status; - $info['SubmittedAt'] = (string)$tag->SubmittedAt; - return $info; - } - - /** - * Utility function to parse a DelegationSet tag structure - */ - private function parseDelegationSet($tag) { - $servers = array(); - foreach($tag->NameServers->NameServer as $ns) { - $servers[] = (string)$ns; - } - return $servers; - } - - /** - * Utility function to parse a ResourceRecordSet tag structure - */ - private function parseResourceRecordSet($tag) { - $rrs = array(); - $rrs['Name'] = (string)$tag->Name; - $rrs['Type'] = (string)$tag->Type; - $rrs['TTL'] = (string)$tag->TTL; - $rrs['ResourceRecords'] = array(); - foreach($tag->ResourceRecords->ResourceRecord as $rr) { - $rrs['ResourceRecords'][] = (string)$rr->Value; - } - return $rrs; - } - - /** - * Utility function to prepare a Change object for ChangeResourceRecordSets requests. - * All fields are required. - * - * @param string action The action to perform. One of: CREATE, DELETE - * @param string name The name to perform the action on. - * If it does not end with '.', then AWS treats the name as relative to the zone root. - * @param string type The type of record being modified. - * Must be one of: A, AAAA, CNAME, MX, NS, PTR, SOA, SPF, SRV, TXT - * @param int ttl The time-to-live value for this record, in seconds. - * @param array records An array of resource records to attach to this change. - * Each member of this array can either be a string, or an array of strings. - * Passing an array of strings will attach multiple values to a single resource record. - * If a single string is passed as $records instead of an array, - * it will be treated as a single-member array. - * @return object An opaque object containing the change request. - * Do not write code that depends on the contents of this object, as it may change at any time. - */ - public function prepareChange($action, $name, $type, $ttl, $records) { - $change = "\n"; - $change .= ''.$action."\n"; - $change .= "\n"; - $change .= ''.$name."\n"; - $change .= ''.$type."\n"; - $change .= ''.$ttl."\n"; - $change .= "\n"; - - if(!is_array($records)) { - $records = array($records); - } - - foreach($records as $record) { - $change .= "\n"; - if(is_array($record)) { - foreach($record as $value) { - $change .= ''.$value."\n"; - } - } - else { - $change .= ''.$record."\n"; - } - $change .= "\n"; - } - - $change .= "\n"; - $change .= "\n"; - $change .= "\n"; - - return $change; - } - - /** - * Trigger an error message - * - * @internal Used by member functions to output errors - * @param array $error Array containing error information - * @return string - */ - public function __triggerError($functionname, $error) - { - if($error == false) { - log_error(sprintf("Route53::%s(): Encountered an error, but no description given", $functionname)); - } - else if(isset($error['curl']) && $error['curl']) - { - log_error(sprintf("Route53::%s(): %s %s", $functionname, $error['code'], $error['message'])); - } - else if(isset($error['Error'])) - { - $e = $error['Error']; - $message = sprintf("Route53::%s(): %s - %s: %s\nRequest Id: %s\n", $functionname, $e['Type'], $e['Code'], $e['Message'], $error['RequestId']); - log_error($message); - } - } - - /** - * Callback handler for 503 retries. - * - * @internal Used by SimpleDBRequest to call the user-specified callback, if set - * @param $attempt The number of failed attempts so far - * @return The retry delay in microseconds, or 0 to stop retrying. - */ - public function __executeServiceTemporarilyUnavailableRetryDelay($attempt) - { - if(is_callable($this->__serviceUnavailableRetryDelayCallback)) { - $callback = $this->__serviceUnavailableRetryDelayCallback; - return $callback($attempt); - } - return 0; - } + const API_VERSION = '2010-10-01'; + + protected $__accessKey; // AWS Access key + protected $__secretKey; // AWS Secret key + protected $__host; + + public function getAccessKey() + { + return $this->__accessKey; + } + public function getSecretKey() + { + return $this->__secretKey; + } + public function getHost() + { + return $this->__host; + } + + protected $__verifyHost = 1; + protected $__verifyPeer = 1; + + // verifyHost and verifyPeer determine whether curl verifies ssl certificates. + // It may be necessary to disable these checks on certain systems. + // These only have an effect if SSL is enabled. + public function verifyHost() + { + return $this->__verifyHost; + } + public function enableVerifyHost($enable = true) + { + $this->__verifyHost = $enable; + } + + public function verifyPeer() + { + return $this->__verifyPeer; + } + public function enableVerifyPeer($enable = true) + { + $this->__verifyPeer = $enable; + } + + /** + * Constructor + * + * @param string $accessKey Access key + * @param string $secretKey Secret key + * @return void + */ + public function __construct($accessKey = null, $secretKey = null, $host = 'route53.amazonaws.com') + { + if ($accessKey !== null && $secretKey !== null) { + $this->setAuth($accessKey, $secretKey); + } + $this->__host = $host; + } + + /** + * Set AWS access key and secret key + * + * @param string $accessKey Access key + * @param string $secretKey Secret key + * @return void + */ + public function setAuth($accessKey, $secretKey) + { + $this->__accessKey = $accessKey; + $this->__secretKey = $secretKey; + } + + /** + * Lists the hosted zones on the account + * + * @param string marker A pagination marker returned by a previous truncated call + * @param int maxItems The maximum number of items per page. The service uses min($maxItems, 100). + * @return A list of hosted zones + */ + public function listHostedZones($marker = null, $maxItems = 100) + { + $rest = new Route53Request($this, 'hostedzone', 'GET'); + + if ($marker !== null) { + $rest->setParameter('marker', $marker); + } + if ($maxItems !== 100) { + $rest->setParameter('maxitems', $maxItems); + } + + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 200) { + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + } + if ($rest->error !== false) { + $this->__triggerError('listHostedZones', $rest->error); + return false; + } + + $response = array(); + if (!isset($rest->body)) { + return $response; + } + + $zones = array(); + foreach ($rest->body->HostedZones->HostedZone as $z) { + $zones[] = $this->parseHostedZone($z); + } + $response['HostedZone'] = $zones; + + if (isset($rest->body->MaxItems)) { + $response['MaxItems'] = (string)$rest->body->MaxItems; + } + + if (isset($rest->body->IsTruncated)) { + $response['IsTruncated'] = (string)$rest->body->IsTruncated; + if ($response['IsTruncated'] == 'true') { + $response['NextMarker'] = (string)$rest->body->NextMarker; + } + } + + return $response; + } + + /** + * Retrieves information on a specified hosted zone + * + * @param string zoneId The id of the hosted zone, as returned by CreateHostedZoneResponse or ListHostedZoneResponse + * In other words, if ListHostedZoneResponse shows the zone's Id as '/hostedzone/Z1PA6795UKMFR9', + * then that full value should be passed here, including the '/hostedzone/' prefix. + * @return A data structure containing information about the specified zone + */ + public function getHostedZone($zoneId) + { + // we'll strip off the leading forward slash, so we can use it as the action directly. + $zoneId = trim($zoneId, '/'); + + $rest = new Route53Request($this, $zoneId, 'GET'); + + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 200) { + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + } + if ($rest->error !== false) { + $this->__triggerError('getHostedZone', $rest->error); + return false; + } + + $response = array(); + if (!isset($rest->body)) { + return $response; + } + + $response['HostedZone'] = $this->parseHostedZone($rest->body->HostedZone); + $response['NameServers'] = $this->parseDelegationSet($rest->body->DelegationSet); + + return $response; + } + + /** + * Creates a new hosted zone + * + * @param string name The name of the hosted zone (e.g. "example.com.") + * @param string reference A user-specified unique reference for this request + * @param string comment An optional user-specified comment to attach to the zone + * @return A data structure containing information about the newly created zone + */ + public function createHostedZone($name, $reference, $comment = '') + { + // hosted zone names must end with a period, but people will forget this a lot... + if (strrpos($name, '.') != (strlen($name) - 1)) { + $name .= '.'; + } + + $data = "\n"; + $data .= '\n"; + $data .= '' . $name . "\n"; + $data .= '' . $reference . "\n"; + if (strlen($comment) > 0) { + $data .= "\n"; + $data .= '' . $comment . "\n"; + $data .= "\n"; + } + $data .= "\n"; + + $rest = new Route53Request($this, 'hostedzone', 'POST', $data); + + $rest = $rest->getResponse(); + + if ($rest->error === false && !in_array($rest->code, array(200, 201, 202, 204))) { + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + } + if ($rest->error !== false) { + $this->__triggerError('createHostedZone', $rest->error); + return false; + } + + $response = array(); + if (!isset($rest->body)) { + return $response; + } + + $response['HostedZone'] = $this->parseHostedZone($rest->body->HostedZone); + $response['ChangeInfo'] = $this->parseChangeInfo($rest->body->ChangeInfo); + $response['NameServers'] = $this->parseDelegationSet($rest->body->DelegationSet); + + return $response; + } + + /** + * Retrieves information on a specified hosted zone + * + * @param string zoneId The id of the hosted zone, as returned by CreateHostedZoneResponse or ListHostedZoneResponse + * In other words, if ListHostedZoneResponse shows the zone's Id as '/hostedzone/Z1PA6795UKMFR9', + * then that full value should be passed here, including the '/hostedzone/' prefix. + * @return The change request data corresponding to this delete + */ + public function deleteHostedZone($zoneId) + { + // we'll strip off the leading forward slash, so we can use it as the action directly. + $zoneId = trim($zoneId, '/'); + + $rest = new Route53Request($this, $zoneId, 'DELETE'); + + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 200) { + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + } + if ($rest->error !== false) { + $this->__triggerError('deleteHostedZone', $rest->error); + return false; + } + + if (!isset($rest->body)) { + return array(); + } + + return $this->parseChangeInfo($rest->body->ChangeInfo); + } + + /** + * Retrieves a list of resource record sets for a given zone + * + * @param string zoneId The id of the hosted zone, as returned by CreateHostedZoneResponse or ListHostedZoneResponse + * In other words, if ListHostedZoneResponse shows the zone's Id as '/hostedzone/Z1PA6795UKMFR9', + * then that full value should be passed here, including the '/hostedzone/' prefix. + * @param string type The type of resource record set to begin listing from. If this is specified, $name must also be specified. + * Must be one of: A, AAAA, CNAME, MX, NS, PTR, SOA, SPF, SRV, TXT + * @param string name The name at which to begin listing resource records (in the lexographic order of records). + * @param int maxItems The maximum number of results to return. The service uses min($maxItems, 100). + * @return The list of matching resource record sets + */ + public function listResourceRecordSets($zoneId, $type = '', $name = '', $maxItems = 100) + { + // we'll strip off the leading forward slash, so we can use it as the action directly. + $zoneId = trim($zoneId, '/'); + + $rest = new Route53Request($this, $zoneId . '/rrset', 'GET'); + + if (strlen($type) > 0) { + $rest->setParameter('type', $type); + } + if (strlen($name) > 0) { + $rest->setParameter('name', $name); + } + if ($maxItems != 100) { + $rest->setParameter('maxitems', $maxItems); + } + + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 200) { + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + } + if ($rest->error !== false) { + $this->__triggerError('listResourceRecordSets', $rest->error); + return false; + } + + $response = array(); + if (!isset($rest->body)) { + return $response; + } + + $recordSets = array(); + foreach ($rest->body->ResourceRecordSets->ResourceRecordSet as $set) { + $recordSets[] = $this->parseResourceRecordSet($set); + } + + $response['ResourceRecordSets'] = $recordSets; + + if (isset($rest->body->MaxItems)) { + $response['MaxItems'] = (string)$rest->body->MaxItems; + } + + if (isset($rest->body->IsTruncated)) { + $response['IsTruncated'] = (string)$rest->body->IsTruncated; + if ($response['IsTruncated'] == 'true') { + $response['NextRecordName'] = (string)$rest->body->NextRecordName; + $response['NextRecordType'] = (string)$rest->body->NextRecordType; + } + } + + return $response; + } + + /** + * Makes the specified resource record set changes (create or delete). + * + * @param string zoneId The id of the hosted zone, as returned by CreateHostedZoneResponse or ListHostedZoneResponse + * In other words, if ListHostedZoneResponse shows the zone's Id as '/hostedzone/Z1PA6795UKMFR9', + * then that full value should be passed here, including the '/hostedzone/' prefix. + * @param array changes An array of change objects, as they are returned by the prepareChange utility method. + * You may also pass a single change object. + * @param string comment An optional comment to attach to the change request + * @return The status of the change request + */ + public function changeResourceRecordSets($zoneId, $changes, $comment = '') + { + // we'll strip off the leading forward slash, so we can use it as the action directly. + $zoneId = trim($zoneId, '/'); + + $data = "\n"; + $data .= '\n"; + $data .= "\n"; + + if (strlen($comment) > 0) { + $data .= '' . $comment . "\n"; + } + + if (!is_array($changes)) { + $changes = array($changes); + } + + $data .= "\n"; + foreach ($changes as $change) { + $data .= $change; + } + $data .= "\n"; + + $data .= "\n"; + $data .= "\n"; + + $rest = new Route53Request($this, $zoneId . '/rrset', 'POST', $data); + + $rest = $rest->getResponse(); + if ($rest->error === false && !in_array($rest->code, array(200, 201, 202, 204))) { + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + } + if ($rest->error !== false) { + $this->__triggerError('changeResourceRecordSets', $rest->error); + return false; + } + + if (!isset($rest->body)) { + return array(); + } + + return $this->parseChangeInfo($rest->body->ChangeInfo); + } + + /** + * Retrieves information on a specified change request + * + * @param string changeId The id of the change, as returned by CreateHostedZoneResponse or ChangeResourceRecordSets + * In other words, if CreateHostedZoneResponse showed the change's Id as '/change/C2682N5HXP0BZ4', + * then that full value should be passed here, including the '/change/' prefix. + * @return The status of the change request + */ + public function getChange($changeId) + { + // we'll strip off the leading forward slash, so we can use it as the action directly. + $zoneId = trim($changeId, '/'); + + $rest = new Route53Request($this, $changeId, 'GET'); + + $rest = $rest->getResponse(); + if ($rest->error === false && $rest->code !== 200) { + $rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status'); + } + if ($rest->error !== false) { + $this->__triggerError('getChange', $rest->error); + return false; + } + + if (!isset($rest->body)) { + return array(); + } + + return $this->parseChangeInfo($rest->body->ChangeInfo); + } + + + + /** + * Utility function to parse a HostedZone tag structure + */ + private function parseHostedZone($tag) + { + $zone = array(); + $zone['Id'] = (string)$tag->Id; + $zone['Name'] = (string)$tag->Name; + $zone['CallerReference'] = (string)$tag->CallerReference; + + // these might always be set, but check just in case, since + // their values are option on CreateHostedZone requests + if (isset($tag->Config) && isset($tag->Config->Comment)) { + $zone['Config'] = array('Comment' => (string)$tag->Config->Comment); + } + + return $zone; + } + + /** + * Utility function to parse a ChangeInfo tag structure + */ + private function parseChangeInfo($tag) + { + $info = array(); + $info['Id'] = (string)$tag->Id; + $info['Status'] = (string)$tag->Status; + $info['SubmittedAt'] = (string)$tag->SubmittedAt; + return $info; + } + + /** + * Utility function to parse a DelegationSet tag structure + */ + private function parseDelegationSet($tag) + { + $servers = array(); + foreach ($tag->NameServers->NameServer as $ns) { + $servers[] = (string)$ns; + } + return $servers; + } + + /** + * Utility function to parse a ResourceRecordSet tag structure + */ + private function parseResourceRecordSet($tag) + { + $rrs = array(); + $rrs['Name'] = (string)$tag->Name; + $rrs['Type'] = (string)$tag->Type; + $rrs['TTL'] = (string)$tag->TTL; + $rrs['ResourceRecords'] = array(); + foreach ($tag->ResourceRecords->ResourceRecord as $rr) { + $rrs['ResourceRecords'][] = (string)$rr->Value; + } + return $rrs; + } + + /** + * Utility function to prepare a Change object for ChangeResourceRecordSets requests. + * All fields are required. + * + * @param string action The action to perform. One of: CREATE, DELETE + * @param string name The name to perform the action on. + * If it does not end with '.', then AWS treats the name as relative to the zone root. + * @param string type The type of record being modified. + * Must be one of: A, AAAA, CNAME, MX, NS, PTR, SOA, SPF, SRV, TXT + * @param int ttl The time-to-live value for this record, in seconds. + * @param array records An array of resource records to attach to this change. + * Each member of this array can either be a string, or an array of strings. + * Passing an array of strings will attach multiple values to a single resource record. + * If a single string is passed as $records instead of an array, + * it will be treated as a single-member array. + * @return object An opaque object containing the change request. + * Do not write code that depends on the contents of this object, as it may change at any time. + */ + public function prepareChange($action, $name, $type, $ttl, $records) + { + $change = "\n"; + $change .= '' . $action . "\n"; + $change .= "\n"; + $change .= '' . $name . "\n"; + $change .= '' . $type . "\n"; + $change .= '' . $ttl . "\n"; + $change .= "\n"; + + if (!is_array($records)) { + $records = array($records); + } + + foreach ($records as $record) { + $change .= "\n"; + if (is_array($record)) { + foreach ($record as $value) { + $change .= '' . $value . "\n"; + } + } else { + $change .= '' . $record . "\n"; + } + $change .= "\n"; + } + + $change .= "\n"; + $change .= "\n"; + $change .= "\n"; + + return $change; + } + + /** + * Trigger an error message + * + * @internal Used by member functions to output errors + * @param array $error Array containing error information + * @return string + */ + public function __triggerError($functionname, $error) + { + if ($error == false) { + log_error(sprintf("Route53::%s(): Encountered an error, but no description given", $functionname)); + } elseif (isset($error['curl']) && $error['curl']) { + log_error(sprintf("Route53::%s(): %s %s", $functionname, $error['code'], $error['message'])); + } elseif (isset($error['Error'])) { + $e = $error['Error']; + $message = sprintf("Route53::%s(): %s - %s: %s\nRequest Id: %s\n", $functionname, $e['Type'], $e['Code'], $e['Message'], $error['RequestId']); + log_error($message); + } + } + + /** + * Callback handler for 503 retries. + * + * @internal Used by SimpleDBRequest to call the user-specified callback, if set + * @param $attempt The number of failed attempts so far + * @return The retry delay in microseconds, or 0 to stop retrying. + */ + public function __executeServiceTemporarilyUnavailableRetryDelay($attempt) + { + if (is_callable($this->__serviceUnavailableRetryDelayCallback)) { + $callback = $this->__serviceUnavailableRetryDelayCallback; + return $callback($attempt); + } + return 0; + } } final class Route53Request { - private $r53, $action, $verb, $data, $parameters = array(); - public $response; + private $r53, $action, $verb, $data, $parameters = array(); + public $response; - /** - * Constructor - * - * @param string $r53 The Route53 object making this request - * @param string $action SimpleDB action - * @param string $verb HTTP verb - * @param string $data For POST requests, the data being posted (optional) - * @return mixed - */ - function __construct($r53, $action, $verb, $data = '') { - $this->r53 = $r53; - $this->action = $action; - $this->verb = $verb; - $this->data = $data; - $this->response = new STDClass; - $this->response->error = false; - } + /** + * Constructor + * + * @param string $r53 The Route53 object making this request + * @param string $action SimpleDB action + * @param string $verb HTTP verb + * @param string $data For POST requests, the data being posted (optional) + * @return mixed + */ + function __construct($r53, $action, $verb, $data = '') + { + $this->r53 = $r53; + $this->action = $action; + $this->verb = $verb; + $this->data = $data; + $this->response = new STDClass(); + $this->response->error = false; + } - /** - * Set request parameter - * - * @param string $key Key - * @param string $value Value - * @param boolean $replace Whether to replace the key if it already exists (default true) - * @return void - */ - public function setParameter($key, $value, $replace = true) { - if(!$replace && isset($this->parameters[$key])) - { - $temp = (array)($this->parameters[$key]); - $temp[] = $value; - $this->parameters[$key] = $temp; - } - else - { - $this->parameters[$key] = $value; - } - } + /** + * Set request parameter + * + * @param string $key Key + * @param string $value Value + * @param boolean $replace Whether to replace the key if it already exists (default true) + * @return void + */ + public function setParameter($key, $value, $replace = true) + { + if (!$replace && isset($this->parameters[$key])) { + $temp = (array)($this->parameters[$key]); + $temp[] = $value; + $this->parameters[$key] = $temp; + } else { + $this->parameters[$key] = $value; + } + } - /** - * Get the response - * - * @return object | false - */ - public function getResponse() { + /** + * Get the response + * + * @return object | false + */ + public function getResponse() + { - $params = array(); - foreach ($this->parameters as $var => $value) - { - if(is_array($value)) - { - foreach($value as $v) - { - $params[] = $var.'='.$this->__customUrlEncode($v); - } - } - else - { - $params[] = $var.'='.$this->__customUrlEncode($value); - } - } + $params = array(); + foreach ($this->parameters as $var => $value) { + if (is_array($value)) { + foreach ($value as $v) { + $params[] = $var . '=' . $this->__customUrlEncode($v); + } + } else { + $params[] = $var . '=' . $this->__customUrlEncode($value); + } + } - sort($params, SORT_STRING); + sort($params, SORT_STRING); - $query = implode('&', $params); + $query = implode('&', $params); - // must be in format 'Sun, 06 Nov 1994 08:49:37 GMT' - $date = gmdate('D, d M Y H:i:s e'); + // must be in format 'Sun, 06 Nov 1994 08:49:37 GMT' + $date = gmdate('D, d M Y H:i:s e'); - $headers = array(); - $headers[] = 'Date: '.$date; - $headers[] = 'Host: '.$this->r53->getHost(); + $headers = array(); + $headers[] = 'Date: ' . $date; + $headers[] = 'Host: ' . $this->r53->getHost(); - $auth = 'AWS3-HTTPS AWSAccessKeyId='.$this->r53->getAccessKey(); - $auth .= ',Algorithm=HmacSHA256,Signature='.$this->__getSignature($date); - $headers[] = 'X-Amzn-Authorization: '.$auth; + $auth = 'AWS3-HTTPS AWSAccessKeyId=' . $this->r53->getAccessKey(); + $auth .= ',Algorithm=HmacSHA256,Signature=' . $this->__getSignature($date); + $headers[] = 'X-Amzn-Authorization: ' . $auth; - $url = 'https://'.$this->r53->getHost().'/'.Route53::API_VERSION.'/'.$this->action.'?'.$query; + $url = 'https://' . $this->r53->getHost() . '/' . Route53::API_VERSION . '/' . $this->action . '?' . $query; - // Basic setup - $curl = curl_init(); - curl_setopt($curl, CURLOPT_USERAGENT, 'Route53/php'); + // Basic setup + $curl = curl_init(); + curl_setopt($curl, CURLOPT_USERAGENT, 'Route53/php'); - curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, ($this->r53->verifyHost() ? 1 : 0)); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, ($this->r53->verifyPeer() ? 1 : 0)); + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, ($this->r53->verifyHost() ? 1 : 0)); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, ($this->r53->verifyPeer() ? 1 : 0)); - curl_setopt($curl, CURLOPT_URL, $url); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, false); - curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback')); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, false); + curl_setopt($curl, CURLOPT_WRITEFUNCTION, array(&$this, '__responseWriteCallback')); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); - // Request types - switch ($this->verb) { - case 'GET': break; - case 'POST': - curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb); - if(strlen($this->data) > 0) { - curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data); - $headers[] = 'Content-Type: text/plain'; - $headers[] = 'Content-Length: '.strlen($this->data); - } - break; - case 'DELETE': - curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE'); - break; - default: break; - } - curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); - curl_setopt($curl, CURLOPT_HEADER, false); + // Request types + switch ($this->verb) { + case 'GET': + break; + case 'POST': + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->verb); + if (strlen($this->data) > 0) { + curl_setopt($curl, CURLOPT_POSTFIELDS, $this->data); + $headers[] = 'Content-Type: text/plain'; + $headers[] = 'Content-Length: ' . strlen($this->data); + } + break; + case 'DELETE': + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE'); + break; + default: + break; + } + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + curl_setopt($curl, CURLOPT_HEADER, false); - // Execute, grab errors - if (curl_exec($curl)) { - $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - } else { - $this->response->error = array( - 'curl' => true, - 'code' => curl_errno($curl), - 'message' => curl_error($curl), - 'resource' => $this->resource - ); - } + // Execute, grab errors + if (curl_exec($curl)) { + $this->response->code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + } else { + $this->response->error = array( + 'curl' => true, + 'code' => curl_errno($curl), + 'message' => curl_error($curl), + 'resource' => $this->resource + ); + } - @curl_close($curl); + @curl_close($curl); - // Parse body into XML - if ($this->response->error === false && isset($this->response->body)) { - $this->response->body = simplexml_load_string($this->response->body); + // Parse body into XML + if ($this->response->error === false && isset($this->response->body)) { + $this->response->body = simplexml_load_string($this->response->body); - // Grab Route53 errors - if (!in_array($this->response->code, array(200, 201, 202, 204)) - && isset($this->response->body->Error)) { - $error = $this->response->body->Error; - $output = array(); - $output['curl'] = false; - $output['Error'] = array(); - $output['Error']['Type'] = (string)$error->Type; - $output['Error']['Code'] = (string)$error->Code; - $output['Error']['Message'] = (string)$error->Message; - $output['RequestId'] = (string)$this->response->body->RequestId; + // Grab Route53 errors + if ( + !in_array($this->response->code, array(200, 201, 202, 204)) + && isset($this->response->body->Error) + ) { + $error = $this->response->body->Error; + $output = array(); + $output['curl'] = false; + $output['Error'] = array(); + $output['Error']['Type'] = (string)$error->Type; + $output['Error']['Code'] = (string)$error->Code; + $output['Error']['Message'] = (string)$error->Message; + $output['RequestId'] = (string)$this->response->body->RequestId; - $this->response->error = $output; - unset($this->response->body); - } - } + $this->response->error = $output; + unset($this->response->body); + } + } - return $this->response; - } + return $this->response; + } - /** - * CURL write callback - * - * @param resource &$curl CURL resource - * @param string &$data Data - * @return integer - */ - private function __responseWriteCallback(&$curl, &$data) { - $this->response->body .= $data; - return strlen($data); - } + /** + * CURL write callback + * + * @param resource &$curl CURL resource + * @param string &$data Data + * @return integer + */ + private function __responseWriteCallback(&$curl, &$data) + { + $this->response->body .= $data; + return strlen($data); + } - /** - * Contributed by afx114 - * URL encode the parameters as per http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?Query_QueryAuth.html - * 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 - * See: http://www.morganney.com/blog/API/AWS-Product-Advertising-API-Requires-a-Signed-Request.php - * - * @param string $var String to encode - * @return string - */ - private function __customUrlEncode($var) { - return str_replace('%7E', '~', rawurlencode($var)); - } + /** + * Contributed by afx114 + * URL encode the parameters as per http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?Query_QueryAuth.html + * 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 + * See: http://www.morganney.com/blog/API/AWS-Product-Advertising-API-Requires-a-Signed-Request.php + * + * @param string $var String to encode + * @return string + */ + private function __customUrlEncode($var) + { + return str_replace('%7E', '~', rawurlencode($var)); + } - /** - * Generate the auth string using Hmac-SHA256 - * - * @internal Used by SimpleDBRequest::getResponse() - * @param string $string String to sign - * @return string - */ - private function __getSignature($string) { - return base64_encode(hash_hmac('sha256', $string, $this->r53->getSecretKey(), true)); - } + /** + * Generate the auth string using Hmac-SHA256 + * + * @internal Used by SimpleDBRequest::getResponse() + * @param string $string String to sign + * @return string + */ + private function __getSignature($string) + { + return base64_encode(hash_hmac('sha256', $string, $this->r53->getSecretKey(), true)); + } }