HttpHeaderHandler.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. <?php
  2. namespace Aliyun\OTS\Handlers;
  3. use Aliyun\OTS;
  4. use Aliyun\OTS\OTSClientException as OTSClientException;
  5. class HttpHeaderHandler
  6. {
  7. private function makeHeaderString($headers)
  8. {
  9. $headerArray = array();
  10. foreach ($headers as $key => $value) {
  11. if (substr($key, 0, 5) == 'x-ots' && $key != 'x-ots-signature') {
  12. array_push($headerArray, "{$key}:{$value}");
  13. }
  14. }
  15. sort($headerArray);
  16. return implode("\n", $headerArray);
  17. }
  18. private function computeRequestSignature($context, $headers)
  19. {
  20. $canonicalHeaders = $this->makeHeaderString($headers);
  21. $stringToSign = "/" . $context->apiName . "\nPOST\n\n" . $canonicalHeaders . "\n";
  22. $signature = hash_hmac("sha1", $stringToSign, $context->clientConfig->getAccessKeySecret(), TRUE);
  23. return base64_encode($signature);
  24. }
  25. private function computeResponseSignature($context, $headers)
  26. {
  27. $canonicalHeaders = $this->makeHeaderString($headers);
  28. $stringToSign = $canonicalHeaders . "\n/" . $context->apiName;
  29. $signature = hash_hmac("sha1", $stringToSign, $context->clientConfig->getAccessKeySecret(), TRUE);
  30. return base64_encode($signature);
  31. }
  32. public function handleBefore($context)
  33. {
  34. // time() - date('Z') ensures UTC+0 timestamp in any environment
  35. // but we will still get the warning:
  36. // "date(): It is not safe to rely on the system's timezone settings."
  37. // it's up to the user to decide the timezone.
  38. $timestamp = gmdate('D, d M Y H:i:s \G\M\T');
  39. $headers = array(
  40. "x-ots-accesskeyid" => $context->clientConfig->getAccessKeyID(),
  41. "x-ots-apiversion" => "2014-08-08",
  42. "x-ots-contentmd5" => base64_encode(md5($context->requestBody, TRUE)),
  43. "x-ots-date" => $timestamp,
  44. "x-ots-instancename" => $context->clientConfig->getInstanceName(),
  45. "User-Agent" => "aliyun-sdk-php 1.0.0",
  46. );
  47. $signature = $this->computeRequestSignature($context, $headers);
  48. $headers["x-ots-signature"] = $signature;
  49. $context->requestHeaders = $headers;
  50. }
  51. private function checkOtherHeaders($context)
  52. {
  53. // Step 1, make sure we have all headers
  54. $headerNames = array(
  55. "x-ots-contentmd5",
  56. "x-ots-requestid",
  57. "x-ots-date",
  58. "x-ots-contenttype",
  59. );
  60. if ($context->responseHttpStatus >= 200 && $context->responseHttpStatus < 300) {
  61. foreach ($headerNames as $name) {
  62. if (!isset($context->responseHeaders[$name])) {
  63. throw new OTSClientException("$name is missing in response header.");
  64. }
  65. }
  66. }
  67. // Step 2, check md5
  68. if (isset($context->responseHeaders['x-ots-contentmd5'])) {
  69. $expectMD5 = base64_encode(md5($context->responseBody, TRUE));
  70. if ($expectMD5 != $context->responseHeaders['x-ots-contentmd5']) {
  71. throw new OTSClientException("MD5 mismatch in response.");
  72. }
  73. }
  74. // Step 3, check date
  75. if (isset($context->responseHeaders['x-ots-date'])) {
  76. $serverTimeStr = $context->responseHeaders['x-ots-date'];
  77. $serverTime = strtotime($serverTimeStr);
  78. if ($serverTime == false) {
  79. throw new OTSClientException("Invalid date format in response: $serverTimeStr");
  80. }
  81. $clientTime = time();
  82. if (abs($clientTime - $serverTime) > 15 * 60) {
  83. throw new OTSClientException("The difference between date in response and system time is more than 15 minutes.");
  84. }
  85. }
  86. }
  87. private function checkAuthorization($context)
  88. {
  89. // Step 1, Check if authorization header is there
  90. if (!isset($context->responseHeaders['Authorization'])) {
  91. if ($context->responseHttpStatus >= 200 && $context->responseHttpStatus < 300) {
  92. throw new OTSClientException("\"Authorization\" is missing in response header.");
  93. }
  94. }
  95. $authorization = $context->responseHeaders['Authorization'];
  96. // Step 2, check if authorization is valid
  97. if (substr($authorization, 0, 4) != "OTS ") {
  98. throw new OTSClientException("Invalid Authorization in response. Authorization: " . $authorization);
  99. }
  100. $splits = explode(":", substr($authorization, 4));
  101. if (count($splits) != 2) {
  102. throw new OTSClientException("Invalid Authorization in response.");
  103. }
  104. $accessKeyID = $splits[0];
  105. $signature = $splits[1];
  106. // Step 3, check accessKeyID
  107. if ($accessKeyID != $context->clientConfig->getAccessKeyID()) {
  108. throw new OTSClientException("Access Key ID mismatch in response.");
  109. }
  110. // Step 4, check signature
  111. if ($signature != $this->computeResponseSignature($context, $context->responseHeaders)) {
  112. throw new OTSClientException("Signature mismatch in response.");
  113. }
  114. }
  115. public function handleAfter($context)
  116. {
  117. try {
  118. $this->checkOtherHeaders($context);
  119. // header 'authorization' is not neccessarily available
  120. // when HttpStatus == 403 (Forbidden).
  121. // but if it is availabe, we still have to check it
  122. if ($context->responseHttpStatus != 403) {
  123. $this->checkAuthorization($context);
  124. }
  125. } catch (OTSClientException $e) {
  126. $errorLogger = $context->clientConfig->errorLogHandler;
  127. if ($errorLogger != null) {
  128. $errorLogger("$context->apiName HttpHeaders: " . json_encode($context->responseHeaders));
  129. }
  130. // re-throw the exception with additonal information
  131. throw new OTSClientException((string)$e . " Http Status: " . $context->responseHttpStatus);
  132. }
  133. }
  134. }