Installing Rockbox on Sansa Clip+ for Non-ASCII Support

The Sansa Clip+ is a portable music player with expandable storage (microSD), support for FLAC and Ogg Vorbis, and decent sound quality.

However, although it can play music files with filenames that contain non-ASCII characters, having such filenames in custom playlists would not work. The player simply ignores those files in the playlist. Using the M3U8 playlist extension does not work either.

One way to overcome this would be to install the Rockbox firmware on the Sansa Clip+.

Download Files

1. Download the original firmware, bootloader and the Rockbox build from http://www.rockbox.org/wiki/SansaAMS/. (Look for those files corresponding to Clip+.)

2. Download the mkamsboot tool from http://download.rockbox.org/bootloader/sandisk-sansa/mkamsboot/.

Create Patched Firmware

3. Extract the downloaded original firmware (clppa.bin).

4. Patch the Rockbox bootloader (bootloader-clipplus.sansa) into the original firmware (clppa.bin).

$ ./mkamsboot clppa.bin bootloader-clipplus.sansa patched.bin

Copy Files into Music Player

5. Set the USB mode of the Sansa Clip+ to MSC (mass-storage device class).

6. Copy the patched firmware into the root directory of the Sansa Clip+ internal storage, renaming the file as clppa.bin.

$ cp patched.bin /media/0123-4567/clppa.bin

where /media/0123-4567 is the mount point for the Sansa Clip+ internal storage.

7. Extract the Rockbox build (which should just be the .rockbox directory) into the root directory of the Sansa Clip+ internal storage.

$ unzip rockbox-sansaclipplus.zip -d /media/0123-4567

where rockbox-sansaclipplus.zip is the downloaded Rockbox build, and /media/0123-4567 is the mount point for the Sansa Clip+ internal storage.

8. The firmware will be updated when the device is unmounted.

Install Additional Fonts for Unicode Support

9. Download the latest font pack from http://www.rockbox.org/download/byhand.cgi.

10. Extract the 14-Rockbox-Mix.fnt and 16-GNU-Unifont.fnt font files into the .rockbox/fonts directory of the Sansa Clip+ internal storage.

$ unzip rockbox-fonts-3.13.zip .rockbox/fonts/14-Rockbox-Mix.fnt .rockbox/fonts/16-GNU-Unifont.fnt -d /media/0123-4567

where rockbox-fonts-3.13.zip is the downloaded font pack, and /media/0123-4567 is the mount point for the Sansa Clip+ internal storage.

11. Change the Rockbox settings to use one of these two fonts.

Settings > Theme Settings > Font

References

[1] http://www.sandisk.com/products/music-video-players/clip-plus-mp3-player/
[2] http://forums.sandisk.com/t5/Sansa-Clip-Sansa-Clip/Playlist-for-Songs-With-Non-ASCII-Filename-Characters/td-p/299106
[3] http://en.wikipedia.org/wiki/M3U
[4] http://www.rockbox.org/wiki/SansaAMS/

Adding EXIF Data Through Command Line

It is also possible to add EXIF data to an image through the command line. exiftool is a command line perl utility for manipulating EXIF data.

Usage Examples

$ exiftool -list
Available tags:
  3DRenderingType A100DataOffset AAFManufacturerID ACoordOfBottomRightCorner
  ACoordOfTopRightCorner AEAperture AEBAutoCancel AEBBracketValue AEBSequence
  AEBSequenceAutoCancel AEBShotCount AEBXv AEExposureTime AEFlags AELButton
  ...
  ...
  ...

Print a (very long) list of EXIF tags that can be read or written using the tool.

$ exiftool filename.jpg
ExifTool Version Number         : 8.60
File Name                       : filename.jpg
Directory                       : .
File Size                       : 357 kB
File Modification Date/Time     : 2013:08:23 23:43:42+08:00
...

View all the EXIF tags for an image file (in this case, filename.jpg).

$ exiftool -Title="Photo Title" filename.jpg 
    1 image files updated

Add the title (“Photo Title“) to the image file filename.jpg.

$ exiftool -Description="This is a description of the photo." filename.jpg 
    1 image files updated

Add the description (“This is a description of the photo.“) to the image file filename.jpg.

$ exiftool -XPKeywords="First Tag;Second Tag" filename.jpg
    1 image files updated

Add the tags (“First Tag” and “Second Tag“) to the image file filename.jpg.

$ exiftool -GPSLatitude=13.37 -GPSLatitudeRef=N -GPSLongitude=1.337 -GPSLongitudeRef=E filename.jpg
    1 image files updated

Add geolocation information (latitude 13.37 degree north and longitude 1.337 degree east) to the image file filename.jpg.

$ exiftool -Title -Description -XPKeywords -GPSLatitude -GPSLatitudeRef -GPSLongitude -GPSLongitudeRef filename.jpg
Title                           : Photo Title
Description                     : This is a description of the photo.
XP Keywords                     : First Tag;Second Tag
GPS Latitude                    : 13 deg 22' 12.00" N
GPS Latitude Ref                : North
GPS Longitude                   : 1 deg 20' 13.20" E
GPS Longitude Ref               : East

View the previously added EXIF information (title, description, tags and geolocation) from the image file filename.jpg.

Note: libimage-exiftool-perl is the package that contains the exiftool utility, and may not be present in a default installation. It can be installed through the package manager.

References

[1] http://linux.die.net/man/1/exiftool
[2] http://www.wikipedia.org/wiki/Exchangeable_image_file_format

Adding EXIF Data Using PHP

A simple PHP script to add image description and geolocation EXIF data to an image. The script makes use of the PHP EXIF Library (PEL) by Isolesen, which can be obtained from http://lsolesen.github.io/pel/.

PHP Code

<?php
set_include_path('lsolesen-pel-9f52634' . PATH_SEPARATOR . get_include_path());
require_once('src/PelJpeg.php');

$pelJpeg = new PelJpeg('in_filename.jpg');

$pelExif = $pelJpeg->getExif();
if ($pelExif == null) {
    $pelExif = new PelExif();
    $pelJpeg->setExif($pelExif);
}

$pelTiff = $pelExif->getTiff();
if ($pelTiff == null) {
    $pelTiff = new PelTiff();
    $pelExif->setTiff($pelTiff);
}

$pelIfd0 = $pelTiff->getIfd();
if ($pelIfd0 == null) {
    $pelIfd0 = new PelIfd(PelIfd::IFD0);
    $pelTiff->setIfd($pelIfd0);
}

$pelIfd0->addEntry(new PelEntryAscii(
        PelTag::IMAGE_DESCRIPTION, 'This is the image description'));

$pelSubIfdGps = new PelIfd(PelIfd::GPS);
$pelIfd0->addSubIfd($pelSubIfdGps);

setGeolocation($pelSubIfdGps, 13.37, 1.337);

$pelJpeg->saveFile('out_filename.jpg');

function setGeolocation(
        $pelSubIfdGps, $latitudeDegreeDecimal, $longitudeDegreeDecimal) {
    $latitudeRef = ($latitudeDegreeDecimal >= 0) ? 'N' : 'S';
    $latitudeDegreeMinuteSecond
            = degreeDecimalToDegreeMinuteSecond(abs($latitudeDegreeDecimal));
    $longitudeRef= ($longitudeDegreeDecimal >= 0) ? 'E' : 'W';
    $longitudeDegreeMinuteSecond
            = degreeDecimalToDegreeMinuteSecond(abs($longitudeDegreeDecimal));

    $pelSubIfdGps->addEntry(new PelEntryAscii(
            PelTag::GPS_LATITUDE_REF, $latitudeRef));
    $pelSubIfdGps->addEntry(new PelEntryRational(
            PelTag::GPS_LATITUDE, 
            array($latitudeDegreeMinuteSecond['degree'], 1), 
            array($latitudeDegreeMinuteSecond['minute'], 1), 
            array(round($latitudeDegreeMinuteSecond['second'] * 1000), 1000)));
    $pelSubIfdGps->addEntry(new PelEntryAscii(
            PelTag::GPS_LONGITUDE_REF, $longitudeRef));
    $pelSubIfdGps->addEntry(new PelEntryRational(
            PelTag::GPS_LONGITUDE, 
            array($longitudeDegreeMinuteSecond['degree'], 1), 
            array($longitudeDegreeMinuteSecond['minute'], 1), 
            array(round($longitudeDegreeMinuteSecond['second'] * 1000), 1000)));
}

function degreeDecimalToDegreeMinuteSecond($degreeDecimal) {
    $degree = floor($degreeDecimal);
    $remainder = $degreeDecimal - $degree;
    $minute = floor($remainder * 60);
    $remainder = ($remainder * 60) - $minute;
    $second = $remainder * 60;
    return array('degree' => $degree, 'minute' => $minute, 'second' => $second);
}
?>

Explanations

set_include_path('lsolesen-pel-9f52634' . PATH_SEPARATOR . get_include_path());
require_once('src/PelJpeg.php');

Import the necessary code. For this script, only PelJpeg.php from the Isolesen PEL library is needed.

$pelJpeg = new PelJpeg('in_filename.jpg');

Load the image file to be editied, in this case, in_filename.jpg.

$pelExif = $pelJpeg->getExif();
if ($pelExif == null) {
    $pelExif = new PelExif();
    $pelJpeg->setExif($pelExif);
}

$pelTiff = $pelExif->getTiff();
if ($pelTiff == null) {
    $pelTiff = new PelTiff();
    $pelExif->setTiff($pelTiff);
}

$pelIfd0 = $pelTiff->getIfd();
if ($pelIfd0 == null) {
    $pelIfd0 = new PelIfd(PelIfd::IFD0);
    $pelTiff->setIfd($pelIfd0);
}

Get the EXIF and the root image file directory (IFD) from the image.

$pelIfd0->addEntry(new PelEntryAscii(
        PelTag::IMAGE_DESCRIPTION, 'This is the image description'));

Add the description (in this case, the description is “This is the image description“.)

$pelSubIfdGps = new PelIfd(PelIfd::GPS);
$pelIfd0->addSubIfd($pelSubIfdGps);

Add a sub-IFD for the geolocation data.

setGeolocation($pelSubIfdGps, 13.37, 1.337);

Set the geolocation data (in this case, with a latitude of 13.37 and a longitude of 1.337). The setGeolocation function is explained below.

$pelJpeg->saveFile('out_filename.jpg');

Save the image which contains the additional EXIF data. In this case, the image is saved to out_filename.jpg.

function setGeolocation(
        $pelSubIfdGps, $latitudeDegreeDecimal, $longitudeDegreeDecimal) {
    $latitudeRef = ($latitudeDegreeDecimal >= 0) ? 'N' : 'S';
    $latitudeDegreeMinuteSecond
            = degreeDecimalToDegreeMinuteSecond(abs($latitudeDegreeDecimal));
    $longitudeRef= ($longitudeDegreeDecimal >= 0) ? 'E' : 'W';
    $longitudeDegreeMinuteSecond
            = degreeDecimalToDegreeMinuteSecond(abs($longitudeDegreeDecimal));

    $pelSubIfdGps->addEntry(new PelEntryAscii(
            PelTag::GPS_LATITUDE_REF, $latitudeRef));
    $pelSubIfdGps->addEntry(new PelEntryRational(
            PelTag::GPS_LATITUDE, 
            array($latitudeDegreeMinuteSecond['degree'], 1), 
            array($latitudeDegreeMinuteSecond['minute'], 1), 
            array(round($latitudeDegreeMinuteSecond['second'] * 1000), 1000)));
    $pelSubIfdGps->addEntry(new PelEntryAscii(
            PelTag::GPS_LONGITUDE_REF, $longitudeRef));
    $pelSubIfdGps->addEntry(new PelEntryRational(
            PelTag::GPS_LONGITUDE, 
            array($longitudeDegreeMinuteSecond['degree'], 1), 
            array($longitudeDegreeMinuteSecond['minute'], 1), 
            array(round($longitudeDegreeMinuteSecond['second'] * 1000), 1000)));
}

The function for setting the geolocation data. This function is needed because the library uses the degree-minute-second format for the latitude and longitude values, so the input decimal values have to be converted beforehand. Also, the library uses positive latitude and longitude values with a direction reference (N or S for latitude, and E or W for longitude), instead of positive and negative values.

function degreeDecimalToDegreeMinuteSecond($degreeDecimal) {
    $degree = floor($degreeDecimal);
    $remainder = $degreeDecimal - $degree;
    $minute = floor($remainder * 60);
    $remainder = ($remainder * 60) - $minute;
    $second = $remainder * 60;
}

The function for converting a decimal degree value to the degree-minute-second format.

Note: php is needed to run the script, and it may not  be present in a default installation. It can be installed through the package manager.

References

[1] http://lsolesen.github.io/pel/
[2] http://www.wikipedia.org/wiki/Exchangeable_image_file_format

ln -s “How to Get Click to Play Working in Firefox 23 or Newer” .

http://www.ghacks.net/2013/08/07/how-to-get-click-to-play-working-in-firefox-23-or-newer/

Previously, Firefox can be configured to run plugins on demand by changing a setting in about:config (plugins.click_to_play). In Firefox 23, plugins appear to run by default even with this setting. This article teaches how to get the run-on-demand behaviour back.

Using Flickr API to Get Photo Information

A simple PHP script to get a list of a user’s uploaded photos through the Flickr REST API. For each photo, the script gets its title, description, original photo URL and geolocation.

To use the Flickr API, an API Key (Consumer Key and Consumer Secret) and an OAuth Token (and OAuth Token Secret) are needed. An API Key is obtained by registering a new application (http://www.flickr.com/services/apps/create/apply/), and an OAuth Token is obtained by authenticating the user and granting access to the application (http://www.flickr.com/services/api/auth.oauth.html).

PHP Code

<?php
$page = 1;
$hasMorePhotos = true;
while ($hasMorePhotos) {
    $timestamp = time();

    $url = 'http://ycpi.api.flickr.com/services/rest/';
    $parameters
            = 'extras=description%2Cgeo%2Coriginal_format'
                    . '&format=json'
                    . '&method=flickr.people.getPhotos'
                    . '&nojsoncallback=1'
                    . '&oauth_consumer_key=<consumer_key>'
                    . '&oauth_nonce=0'
                    . '&oauth_signature_method=HMAC-SHA1'
                    . '&oauth_timestamp=' . ((string) $timestamp)
                    . '&oauth_token=<oauth_token>'
                    . '&oauth_version=1.0'
                    . '&page=' . ((string) $page)
                    . '&per_page=500'
                    . '&user_id=me';

    $signatureText = 'GET&' . rawurlencode($url) . '&' . rawurlencode($parameters);
    $signatureKey = '<consumer_secret>&<oauth_token_secret>';
    $signature = rawurlencode(base64_encode(hash_hmac('sha1', $signatureText, $signatureKey, true)));

    $parameters .= ('&oauth_signature=' . $signature);

    $curl = curl_init();
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_URL, ($url . '?' . $parameters));
    $result = curl_exec($curl);

    $resultJson = json_decode($result, true);

    if (count($resultJson['photos']['photo']) > 0) {    
        foreach ($resultJson['photos']['photo'] as $photo) {
            echo 'ID: ' . $photo['id'] . "\n";
            echo '    TITLE: ' . $photo['title'] . "\n";
            echo '    DESCRIPTION: ' . $photo['description']['_content'] . "\n";
            echo '    URL: ' 
                    . 'http://farm' . $photo['farm'] . '.staticflickr.com/'
                    . $photo['server'] . '/' 
                    . $photo['id'] . '_' . $photo['originalsecret'] . '_o.' . $photo['originalformat'] . "\n";
            echo '    GEOLOCATION: ' . $photo['latitude'] . ', ' . $photo['longitude'] . "\n";
        }
        $page++;
    } else {
        $hasMorePhotos = false;
    }
}
?>

where <consumer_key>, <consumer_secret>, <oauth_token> and <oauth_token_secret> are obtained through the application registration and user authentication process.

Explanations

while ($hasMorePhotos) {
...
}

Each of API call returns a “page” of photo information, which is a list of at most 500 photos. Hence, a loop is needed to retrieve all pages.

    $url = 'http://ycpi.api.flickr.com/services/rest/';

The URL for the API call.

    $parameters
            = 'extras=description%2Cgeo%2Coriginal_format'
                    . '&format=json'
                    . '&method=flickr.people.getPhotos'
                    . '&nojsoncallback=1'
                    . '&oauth_consumer_key=<consumer_key>'
                    . '&oauth_nonce=0'
                    . '&oauth_signature_method=HMAC-SHA1'
                    . '&oauth_timestamp=' . ((string) $timestamp)
                    . '&oauth_token=<oauth_token>'
                    . '&oauth_version=1.0'
                    . '&page=' . ((string) $page)
                    . '&per_page=500'
                    . '&user_id=me';

Form the parameters needed for the API call.

    $signatureText = 'GET&' . rawurlencode($url) . '&' . rawurlencode($parameters);
    $signatureKey = '<consumer_secret>&<oauth_token_secret>';
    $signature = rawurlencode(base64_encode(hash_hmac('sha1', $signatureText, $signatureKey, true)));

The OAuth Signature is generated by hashing the URL and parameters (in lexicographical order), using the Consumer Secret and OAuth Token Secret as hash keys.

    $parameters .= ('&oauth_signature=' . $signature);

Append the generated signature to the request parameters.

    $curl = curl_init();
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_URL, ($url . '?' . $parameters));
    $result = curl_exec($curl);

Use Curl to make the API request and get the response.

    $resultJson = json_decode($result, true);

Parse the JSON response.

        foreach ($resultJson['photos']['photo'] as $photo) {
            echo 'ID: ' . $photo['id'] . "\n";
            echo '    TITLE: ' . $photo['title'] . "\n";
            echo '    DESCRIPTION: ' . $photo['description']['_content'] . "\n";
            echo '    URL: ' 
                    . 'http://farm' . $photo['farm'] . '.staticflickr.com/'
                    . $photo['server'] . '/' 
                    . $photo['id'] . '_' . $photo['originalsecret'] . '_o.' . $photo['originalformat'] . "\n";
            echo '    GEOLOCATION: ' . $photo['latitude'] . ', ' . $photo['longitude'] . "\n";
        }

Iterate through and print out the returned photo information.

Note: php and php-curl are needed to run the script, and they may not  be present in a default installation. They can be installed through the package manager

References

[1] http://www.flickr.com/services/apps/create/apply/
[2] http://www.flickr.com/services/api/auth.oauth.html
[3] http://www.flickr.com/services/api/flickr.people.getPhotos.html

Converting a Wave File to a FLAC File

A simple Linux command to convert a wave file to a FLAC file:

$ flac -0 --tag=ALBUM="Some Album" --tag=ARTIST="Some Artist" --tag=GENRE="Rock" --tag=TITLE="Song Title" --tag=TRACKNUMBER="3" filename.wav

where filename.wav is the name of the input wave file. The output FLAC file will have the name filename.flac.

Explanation of the command arguments:

-0

Compression level (in this case, 0). Valid compression levels are from 0 to 8.

--tag=ALBUM="Some Album"

Add the ALBUM tag to the FLAC file (in this case, with the value Some Album)

--tag=ARTIST="Some Artist"

Add the ARTIST tag to the FLAC file (in this case, with the value Some Artist)

--tag=GENRE="Rock"

Add the GENRE tag to the FLAC file (in this case, with the value Rock)

--tag=TITLE="Song Title"

Add the TITLE tag to the FLAC file (in this case, with the value Song Title)

--tag=TRACKNUMBER="3"

Add the TRACKNUMBER tag to the FLAC file (in this case, with the value 3)

Note: Valid tags that can be added are TITLE, VERSION, ALBUM, TRACKNUMBER, ARTIST, PERFORMER, COPYRIGHT, LICENSE, ORGANIZATION, DESCRIPTION, GENRE, DATE, LOCATION, CONTACT and ISRC.

Note: flac may not be present in a default installation. It can be installed through the package manager.

References

[1] http://linux.die.net/man/1/flac
[2] https://www.xiph.org/vorbis/doc/v-comment.html

ln -s “Everything You Wanted to Know About SQL Injection (But Were Afraid to Ask)” .

http://www.troyhunt.com/2013/07/everything-you-wanted-to-know-about-sql.html

This is not an in-depth instruction manual on SQL injection, nor an introduction of cutting edge exploitation techniques. Nevertheless, it is a very good primer on the concepts and basics of SQL injection.