PHP Code to Sign any Amazon API Requests

Starting next month, any requests to the Amazon Product Advertising API need to be cryptographically signed. Amazon has given about three months notice and the deadline is quickly approaching. I use the Amazon web services on several sites and came up a fairly generic way to convert an existing URL to a signed URL. I’ve tested with several sites and a variety of functions, and this is working well for me so far:

function signAmazonUrl($url, $secret_key)
{
    $original_url = $url;

    // Decode anything already encoded
    $url = urldecode($url);

    // Parse the URL into $urlparts
    $urlparts       = parse_url($url);

    // Build $params with each name/value pair
    foreach (split('&', $urlparts['query']) as $part) {
        if (strpos($part, '=')) {
            list($name, $value) = split('=', $part, 2);
        } else {
            $name = $part;
            $value = '';
        }
        $params[$name] = $value;
    }

    // Include a timestamp if none was provided
    if (empty($params['Timestamp'])) {
        $params['Timestamp'] = gmdate('Y-m-d\TH:i:s\Z');
    }

    // Sort the array by key
    ksort($params);

    // Build the canonical query string
    $canonical       = '';
    foreach ($params as $key => $val) {
        $canonical  .= "$key=".rawurlencode(utf8_encode($val))."&";
    }
    // Remove the trailing ampersand
    $canonical       = preg_replace("/&$/", '', $canonical);

    // Some common replacements and ones that Amazon specifically mentions
    $canonical       = str_replace(array(' ', '+', ',', ';'), array('%20', '%20', urlencode(','), urlencode(':')), $canonical);

    // Build the sign
    $string_to_sign             = "GET\n{$urlparts['host']}\n{$urlparts['path']}\n$canonical";
    // Calculate our actual signature and base64 encode it
    $signature            = base64_encode(hash_hmac('sha256', $string_to_sign, $secret_key, true));

    // Finally re-build the URL with the proper string and include the Signature
    $url = "{$urlparts['scheme']}://{$urlparts['host']}{$urlparts['path']}?$canonical&Signature=".rawurlencode($signature);
    return $url;
}

To use it, just wrap your Amazon URL with the signAmazonUrl() function and pass it your original string and secret key as arguments. As an example:

$xml = file_get_contents('http://webservices.amazon.com/onca/xml?some-parameters');

becomes

$xml = file_get_contents(signAmazonUrl('http://webservices.amazon.com/onca/xml?some-parameters', $secret_key));

Like most all of the variations of this, it does require the hash functions be installed to use the hash_hmac() function. That function is generally available in PHP 5.1+. Older versions will need to install it with Pecl. I tried using a couple of versions that try to create the Hash in pure PHP code, but none worked and installing it via Pecl was pretty simple.

(Note that I’ve slightly revised this code a couple of times to fix small issues that have been noticed)

MRTG Script to Graph the Current Outdoor Temperature

I’m graphing a few things around my home and wanted to graph the current outdoor temperature. I wrote this quick script to grab the current temperature or my zip code from weatherunderground.com and have it graphed with MRTG:

#!/usr/bin/php
<?php

$zipcode = $argv[1];
$page = file_get_contents("http://www.wunderground.com/cgi-bin/findweather/getForecast?query=$zipcode&wuSelect=WEATHER");

$current_temp = 0;
if (preg_match('#pwsvariable="tempf" english="&deg;F" metric="&deg;C" value="([0-9\.]+)">#', $page, $matches)) {
    $current_temp = $matches[1];
}

if (preg_match('#Heat Index:</td>.*?<span class="nobr"><span class="b">([0-9\.]+)</span>#s', $page, $matches)) {
    $heat_index = $matches[1];
}

echo "$current_temp\n$heat_index\non\non\n";

?>

And add it to your MRTG config with something like this (Note that you need to specify your zip code in the ‘Target’ line):

Target[temp]: `/etc/mrtg/temperature.php 30605`
Options[temp]: nopercent,growright,nobanner,nolegend,noinfo,integer,gauge
Title[temp]: Outdoor Temperature
PageTop[temp]: <h3>Outdoor Temperature</h3>
YLegend[temp]: Degrees Farenheight
ShortLegend[temp]: &nbsp;&deg;F
LegendI[temp]: Temperature &deg;F&nbsp;
LegendO[temp]: Heat Index &deg;F&nbsp;

outdoor-temperatur