Projet

Général

Profil

Télécharger (23,9 ko) Statistiques
| Branche: | Tag: | Révision:

univnautes / etc / inc / r53.class @ c650b2f7

1
<?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
}
(45-45/68)