From 461599fb23a151b4e8d27042cb5a1db4ca7f5ae3 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Wed, 1 Jun 2011 16:38:00 -0400
Subject: [PATCH 01/17] #1173692: Add settings for content protection.

---
 includes/jplayer.admin.inc |   21 +++++++++++++++++++++
 1 files changed, 21 insertions(+), 0 deletions(-)

diff --git a/includes/jplayer.admin.inc b/includes/jplayer.admin.inc
index 1ab75b7..b15333a 100644
--- a/includes/jplayer.admin.inc
+++ b/includes/jplayer.admin.inc
@@ -31,6 +31,19 @@ function jplayer_settings_form() {
     '#default_value' => variable_get('jplayer_autoplay', ''),
   );
 
+  $form['options']['jplayer_protected'] = array(
+    '#title' => t('Protect audio files from direct downloads'),
+    '#type' => 'checkbox',
+    '#default_value' => variable_get('jplayer_protected', FALSE),
+  );
+  $form['options']['jplayer_access_time'] = array(
+    '#title' => t('Access delay'),
+    '#type' => 'textfield',
+    '#default_value' => variable_get('jplayer_access_time', 5),
+    '#size' => 5,
+    '#description' => t('The number of seconds that a client will have access to download a protected file after it is requested by jPlayer'),
+  );
+
   $form = system_settings_form($form);
   $form['#validate'][] = 'jplayer_settings_form_validate';
   $form['#submit'][] = 'jplayer_settings_form_submit';
@@ -45,6 +58,14 @@ function jplayer_settings_form_validate($form, &$form_state) {
   if (!$form_state['jplayer_version']) {
     form_error($form['jplayer_directory'], t('The directory specified does not seem to contain the jPlayer library. Check to make sure that the jquery.player.min.js file is located within this directory.'));
   }
+
+  $time = $form_state['values']['jplayer_access_time'];
+  if ($form_state['values']['jplayer_protected'] && !is_numeric($time)) {
+    form_error($form['options']['jplayer_access_time'], t('Access time must be a value in seconds.'));
+  }
+  if (intval($time) < 0) {
+    form_error($form['options']['jplayer_access_time'], t('Access time must be at least 0 seconds.'));
+  }
 }
 
 /**
-- 
1.7.5.4


From 0238216742c4436095a4387346a534fdeb0c09e7 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Thu, 2 Jun 2011 11:17:12 -0400
Subject: [PATCH 02/17] #1173692: Implement basic content protection for
 filefields using jPlayer.

---
 jplayer.module   |  109 +++++++++++++++++++++++++++++++++++++++++++++++
 theme/jplayer.js |  124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 233 insertions(+), 0 deletions(-)

diff --git a/jplayer.module b/jplayer.module
index c8e5bcf..fa7251a 100644
--- a/jplayer.module
+++ b/jplayer.module
@@ -23,10 +23,119 @@ function jplayer_menu() {
     'file' => 'includes/jplayer.admin.inc',
   );
 
+  $items['jplayer/authorize'] = array(
+    'title' => 'jPlayer content authorization',
+    'page callback' => 'jplayer_authorize',
+    'access arguments' => array('access content'),
+    'description' => 'jPlayer callback to authorize a sound file to be accessed.',
+    'type' => MENU_CALLBACK,
+  );
+
   return $items;
 }
 
 /**
+ * Menu callback to authorize access to a file.
+ */
+function jplayer_authorize($filepath, $timestamp) {
+  $filepath = base64_decode($filepath);
+  $timestamp = base64_decode($timestamp);
+
+  if (!isset($_SESSION['jplayer_protect'])) {
+    $_SESSION['jplayer_protect'] = array();
+  }
+  $_SESSION['jplayer_protect'] = array();
+
+  $_SESSION['jplayer_protect'][$filepath] = (int)$timestamp;
+
+  drupal_json(TRUE);
+}
+
+/**
+ * Implementation of hook_file_download().
+ */
+function jplayer_file_download($filepath) {
+  if (!variable_get('jplayer_protected', FALSE)) {
+    return NULL;
+  }
+
+  // We need to determine if we are responsible for this file.
+  $filepath = file_create_path($filepath);
+  $result = db_query("SELECT * FROM {files} WHERE filepath = '%s'", $filepath);
+
+  // Ensure case-sensitivity of uploaded file names.
+  while ($file = db_fetch_object($result)) {
+    if (strcmp($file->filepath, $filepath) == 0) {
+      break;
+    }
+  }
+
+  // If the file is not found in the database, we're not responsible for it.
+  if (empty($file)) {
+    return NULL;
+  }
+
+  // Find out if any file field contains this file, and if so, which field
+  // and node it belongs to. Required for later access checking.
+  $cck_files = array();
+  foreach (content_fields() as $field) {
+    if ($field['type'] == 'filefield' || $field['type'] == 'image') {
+      $db_info = content_database_info($field);
+      $table = $db_info['table'];
+      $fid_column = $db_info['columns']['fid']['column'];
+
+      $columns = array('vid', 'nid');
+      foreach ($db_info['columns'] as $property_name => $column_info) {
+        $columns[] = $column_info['column'] .' AS '. $property_name;
+      }
+      $result = db_query("SELECT ". implode(', ', $columns) ."
+                          FROM {". $table ."}
+                          WHERE ". $fid_column ." = %d", $file->fid);
+
+      while ($content = db_fetch_array($result)) {
+        $content['field'] = $field;
+        $cck_files[$field['field_name']][$content['vid']] = $content;
+      }
+    }
+  }
+
+  // If any of the displays for this field are for jPlayer, then we need to
+  // protect the file.
+  foreach ($cck_files as $field_name => $field_files) {
+    foreach ($field_files as $revision_id => $content) {
+      $teaser_format = $content['field']['display_settings']['teaser']['format'];
+      $full_format = $content['field']['display_settings']['full']['format'];
+      // Neither the teaser or the full formatter for this field is a jPlayer
+      // display.
+      if (!($teaser_format == 'single' || $teaser_format == 'playlist' || $full_format == 'single' || $full_format == 'playlist')) {
+        return NULL;
+      }
+    }
+  }
+
+  // We need to figure out how the browser would have URL-encoded the file
+  // name.
+  $encoded = str_replace($file->filename, rawurlencode($file->filename), $filepath);
+  $encoded = str_replace('sites/default/files', 'system/files', $encoded);
+  // For some reason ampersands are encoded twice by the browser.
+  $encoded = str_replace("%26", "%2526", $encoded);
+  $started = (int)$_SESSION['jplayer_protect'][$GLOBALS['base_url'] . '/' . $encoded];
+
+  // Now we know that content protection is enabled, at least one display for
+  // the field uses jPlayer, and we know when the player last started to access
+  // the file.
+  if ($started) {
+    if (time() <= $started + variable_get('jplayer_access_time', 5)) {
+      // Allow access.
+      return NULL;
+    }
+  }
+
+  // Otherwise, deny access as the last played time is too far in the past.
+  return -1;
+}
+
+/**
  * Implementation of hook_theme().
  */
 function jplayer_theme() {
diff --git a/theme/jplayer.js b/theme/jplayer.js
index 955e7cd..d898dc6 100644
--- a/theme/jplayer.js
+++ b/theme/jplayer.js
@@ -63,6 +63,20 @@ Drupal.behaviors.jPlayer = function(context) {
       });
     }
 
+    $(wrapper).find('a.jp-play').click(function() {
+      // Generate the authorization URL to ping.
+      var time = new Date;
+      var authorize_url = Drupal.settings.basePath + 'jplayer/authorize/' + base64_encode($(player).attr('rel')) + '/' + base64_encode(parseInt(time.getTime() / 1000).toString());
+
+      // Ping the authorization URL. We need to disable async so that this
+      // command finishes before thisandler returns.
+      $.ajax({
+        url: authorize_url,
+        async: false,
+      });
+      return false;
+    });
+
     // Actually initialize the player.
     $(player).jPlayer({
       ready: function() { 
@@ -140,4 +154,114 @@ Drupal.jPlayer.previous = function(wrapper, player, playlist, current) {
   return index;
 }
 
+Drupal.jPlayer.base64Encode = function(data) {
+  // From http://phpjs.org/functions/base64_encode:358 where it is
+  // dual licensed under GPL/MIT.
+  //
+  // http://kevin.vanzonneveld.net
+  // +   original by: Tyler Akins (http://rumkin.com)
+  // +   improved by: Bayron Guevara
+  // +   improved by: Thunder.m
+  // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+  // +   bugfixed by: Pellentesque Malesuada
+  // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+  // -    depends on: utf8_encode
+  // *     example 1: base64_encode('Kevin van Zonneveld');
+  // *     returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA=='
+  var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+  var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
+      ac = 0,
+      enc = "",
+      tmp_arr = [];
+
+  if (!data) {
+      return data;
+  }
+
+  data = utf8_encode(data + '');
+
+  do { // pack three octets into four hexets
+      o1 = data.charCodeAt(i++);
+      o2 = data.charCodeAt(i++);
+      o3 = data.charCodeAt(i++);
+
+      bits = o1 << 16 | o2 << 8 | o3;
+
+      h1 = bits >> 18 & 0x3f;
+      h2 = bits >> 12 & 0x3f;
+      h3 = bits >> 6 & 0x3f;
+      h4 = bits & 0x3f;
+
+      // use hexets to index into b64, and append result to encoded string
+      tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
+  } while (i < data.length);
+
+  enc = tmp_arr.join('');
+
+  switch (data.length % 3) {
+  case 1:
+      enc = enc.slice(0, -2) + '==';
+      break;
+  case 2:
+      enc = enc.slice(0, -1) + '=';
+      break;
+  }
+
+  return enc;
+};
+
+Drupal.jPlayer.utf8_encode = function(argString) {
+  // From http://phpjs.org/functions/utf8_encode:577 where it is dual-licensed
+  // under GPL/MIT.
+  // http://kevin.vanzonneveld.net
+  // +   original by: Webtoolkit.info (http://www.webtoolkit.info/)
+  // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+  // +   improved by: sowberry
+  // +    tweaked by: Jack
+  // +   bugfixed by: Onno Marsman
+  // +   improved by: Yves Sucaet
+  // +   bugfixed by: Onno Marsman
+  // +   bugfixed by: Ulrich
+  // +   bugfixed by: Rafal Kukawski
+  // *     example 1: utf8_encode('Kevin van Zonneveld');
+  // *     returns 1: 'Kevin van Zonneveld'
+
+  if (argString === null || typeof argString === "undefined") {
+      return "";
+  }
+
+  var string = (argString + ''); // .replace(/\r\n/g, "\n").replace(/\r/g, "\n");
+  var utftext = "",
+      start, end, stringl = 0;
+
+  start = end = 0;
+  stringl = string.length;
+  for (var n = 0; n < stringl; n++) {
+      var c1 = string.charCodeAt(n);
+      var enc = null;
+
+      if (c1 < 128) {
+          end++;
+      } else if (c1 > 127 && c1 < 2048) {
+          enc = String.fromCharCode((c1 >> 6) | 192) + String.fromCharCode((c1 & 63) | 128);
+      } else {
+          enc = String.fromCharCode((c1 >> 12) | 224) + String.fromCharCode(((c1 >> 6) & 63) | 128) + String.fromCharCode((c1 & 63) | 128);
+      }
+      if (enc !== null) {
+          if (end > start) {
+              utftext += string.slice(start, end);
+          }
+          utftext += enc;
+          start = end = n + 1;
+      }
+  }
+
+  if (end > start) {
+      utftext += string.slice(start, stringl);
+  }
+
+  return utftext;
+};
+
 })(jQuery);
+
-- 
1.7.5.4


From 65cec78566e7ff32770b1e08b48518acbb691dd8 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Thu, 2 Jun 2011 11:17:48 -0400
Subject: [PATCH 03/17] #1173692: Add missing semicolons in javascript.

---
 theme/jplayer.js |   10 +++++-----
 1 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/theme/jplayer.js b/theme/jplayer.js
index d898dc6..55cab98 100644
--- a/theme/jplayer.js
+++ b/theme/jplayer.js
@@ -128,31 +128,31 @@ Drupal.jPlayer.setActive = function(wrapper, player, playlist, index) {
 Drupal.jPlayer.play = function(wrapper, player) {
   $(player).jPlayer('play');
   Drupal.jPlayer.active = true;
-}
+};
 
 Drupal.jPlayer.pause = function(wrapper, player) {
   $(player).jPlayer('pause');
   Drupal.jPlayer.active = false;
-}
+};
 
 Drupal.jPlayer.stop = function(wrapper, player) {
   $(player).jPlayer('stop');
   Drupal.jPlayer.active = false;
-}
+};
 
 Drupal.jPlayer.next = function(wrapper, player, playlist, current) {
   var index = (current + 1 < playlist.length) ? current + 1 : 0;
   Drupal.jPlayer.setActive(wrapper, player, playlist, index);
   Drupal.jPlayer.play(wrapper, player);
   return index;
-}
+};
 
 Drupal.jPlayer.previous = function(wrapper, player, playlist, current) {
   var index = (current - 1 >= 0) ? current - 1 : playlist.length - 1;
   Drupal.jPlayer.setActive(wrapper, player, playlist, index);
   Drupal.jPlayer.play(wrapper, player);
   return index;
-}
+};
 
 Drupal.jPlayer.base64Encode = function(data) {
   // From http://phpjs.org/functions/base64_encode:358 where it is
-- 
1.7.5.4


From 11d9c27ac925e5ec6a88e4bf8f38952c349baec2 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Thu, 2 Jun 2011 11:26:07 -0400
Subject: [PATCH 04/17] #1173692: Only add the content protection handler if
 content protection is enabled.

---
 jplayer.module   |    1 +
 theme/jplayer.js |   24 +++++++++++++-----------
 2 files changed, 14 insertions(+), 11 deletions(-)

diff --git a/jplayer.module b/jplayer.module
index fa7251a..a12e1f8 100644
--- a/jplayer.module
+++ b/jplayer.module
@@ -223,6 +223,7 @@ function jplayer_add($add = TRUE) {
     $settings = array('jPlayer' => array(
       'swfPath' => base_path() . variable_get('jplayer_directory', 'sites/all/libraries/jplayer'),
       'autoPlay' => (int) variable_get('jplayer_autoplay', ''),
+      'protected' => variable_get('jplayer_protected', ''),
     ));
     if ($add) {
       drupal_add_js($filepath);
diff --git a/theme/jplayer.js b/theme/jplayer.js
index 55cab98..ceb0fd8 100644
--- a/theme/jplayer.js
+++ b/theme/jplayer.js
@@ -63,19 +63,21 @@ Drupal.behaviors.jPlayer = function(context) {
       });
     }
 
-    $(wrapper).find('a.jp-play').click(function() {
-      // Generate the authorization URL to ping.
-      var time = new Date;
-      var authorize_url = Drupal.settings.basePath + 'jplayer/authorize/' + base64_encode($(player).attr('rel')) + '/' + base64_encode(parseInt(time.getTime() / 1000).toString());
-
-      // Ping the authorization URL. We need to disable async so that this
-      // command finishes before thisandler returns.
-      $.ajax({
-        url: authorize_url,
-        async: false,
+    if (Drupal.settings.jPlayer.protected) {
+      $(wrapper).find('a.jp-play').click(function() {
+        // Generate the authorization URL to ping.
+        var time = new Date;
+        var authorize_url = Drupal.settings.basePath + 'jplayer/authorize/' + base64_encode($(player).attr('rel')) + '/' + base64_encode(parseInt(time.getTime() / 1000).toString());
+
+        // Ping the authorization URL. We need to disable async so that this
+        // command finishes before thisandler returns.
+        $.ajax({
+          url: authorize_url,
+          async: false,
+        });
+        return false;
       });
-      return false;
-    });
+    }
 
     // Actually initialize the player.
     $(player).jPlayer({
-- 
1.7.5.4


From aa22fa1ac2cb1a82f985088e24d02f06fb970777 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Thu, 2 Jun 2011 11:26:29 -0400
Subject: [PATCH 05/17] #1173692: Fix renaming of the base64 function.

---
 theme/jplayer.js |    6 +++---
 1 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/theme/jplayer.js b/theme/jplayer.js
index ceb0fd8..55c1906 100644
--- a/theme/jplayer.js
+++ b/theme/jplayer.js
@@ -67,7 +67,7 @@ Drupal.behaviors.jPlayer = function(context) {
       $(wrapper).find('a.jp-play').click(function() {
         // Generate the authorization URL to ping.
         var time = new Date;
-        var authorize_url = Drupal.settings.basePath + 'jplayer/authorize/' + base64_encode($(player).attr('rel')) + '/' + base64_encode(parseInt(time.getTime() / 1000).toString());
+        var authorize_url = Drupal.settings.basePath + 'jplayer/authorize/' + Drupal.jPlayer.base64Encode($(player).attr('rel')) + '/' + Drupal.jPlayer.base64Encode(parseInt(time.getTime() / 1000).toString());
 
         // Ping the authorization URL. We need to disable async so that this
         // command finishes before thisandler returns.
@@ -180,7 +180,7 @@ Drupal.jPlayer.base64Encode = function(data) {
       return data;
   }
 
-  data = utf8_encode(data + '');
+  data = Drupal.jPlayer.utf8Encode(data + '');
 
   do { // pack three octets into four hexets
       o1 = data.charCodeAt(i++);
@@ -212,7 +212,7 @@ Drupal.jPlayer.base64Encode = function(data) {
   return enc;
 };
 
-Drupal.jPlayer.utf8_encode = function(argString) {
+Drupal.jPlayer.utf8Encode = function(argString) {
   // From http://phpjs.org/functions/utf8_encode:577 where it is dual-licensed
   // under GPL/MIT.
   // http://kevin.vanzonneveld.net
-- 
1.7.5.4


From 4c825f09ce9284acf4a649ba8abf74f743811e16 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Mon, 6 Jun 2011 12:50:54 -0400
Subject: [PATCH 06/17] Issue #1173692: Prevent clients from setting the
 expiry to be too far in the future, and show a
 message if jPlayer is denied access to the file.

---
 jplayer.module   |   11 ++++++-----
 theme/jplayer.js |   20 +++++++++++++++++++-
 2 files changed, 25 insertions(+), 6 deletions(-)

diff --git a/jplayer.module b/jplayer.module
index a12e1f8..0e6cc08 100644
--- a/jplayer.module
+++ b/jplayer.module
@@ -39,16 +39,17 @@ function jplayer_menu() {
  */
 function jplayer_authorize($filepath, $timestamp) {
   $filepath = base64_decode($filepath);
-  $timestamp = base64_decode($timestamp);
+  $timestamp = (int)base64_decode($timestamp);
 
   if (!isset($_SESSION['jplayer_protect'])) {
     $_SESSION['jplayer_protect'] = array();
   }
-  $_SESSION['jplayer_protect'] = array();
 
-  $_SESSION['jplayer_protect'][$filepath] = (int)$timestamp;
+  if ($timestamp < (time() + variable_get('jplayer_access_time', 5))) {
+    $_SESSION['jplayer_protect'][$filepath] = $timestamp;
+  }
 
-  drupal_json(TRUE);
+  drupal_json(time() + variable_get('jplayer_access_time', 5));
 }
 
 /**
@@ -125,7 +126,7 @@ function jplayer_file_download($filepath) {
   // the field uses jPlayer, and we know when the player last started to access
   // the file.
   if ($started) {
-    if (time() <= $started + variable_get('jplayer_access_time', 5)) {
+    if (time() <= ($started + variable_get('jplayer_access_time', 5))) {
       // Allow access.
       return NULL;
     }
diff --git a/theme/jplayer.js b/theme/jplayer.js
index 55c1906..a85ed56 100644
--- a/theme/jplayer.js
+++ b/theme/jplayer.js
@@ -66,13 +66,31 @@ Drupal.behaviors.jPlayer = function(context) {
     if (Drupal.settings.jPlayer.protected) {
       $(wrapper).find('a.jp-play').click(function() {
         // Generate the authorization URL to ping.
-        var time = new Date;
+        var time = new Date();
         var authorize_url = Drupal.settings.basePath + 'jplayer/authorize/' + Drupal.jPlayer.base64Encode($(player).attr('rel')) + '/' + Drupal.jPlayer.base64Encode(parseInt(time.getTime() / 1000).toString());
 
         // Ping the authorization URL. We need to disable async so that this
         // command finishes before thisandler returns.
         $.ajax({
           url: authorize_url,
+          success: function(data) {
+            // Check to see if the access has expired. This could happen due to
+            // clock sync differences between the server and the client.
+            var seconds = parseInt(data);
+            var expires = new Date(seconds * 1000);
+            if ($('#jplayer-message').size() == 0) {
+              $(wrapper).parent().prepend('<div id="jplayer-message" class="messages error"></div>');
+              $('#jplayer-message').hide();
+            }
+            if (expires < time) {
+              var message = Drupal.t('There was an error downloading the audio. Try <a href="@url">reloading the page</a>. If the error persists, check that your computer\'s clock is accurate.', {"@url" : window.location});
+              $('#jplayer-message').fadeOut('fast').html("<ul><li>" + message + "</li></ul>").fadeIn('fast');
+              $(wrapper).hide();
+            }
+            else {
+              $('#jplayer-message').fadeOut('fast');
+            }
+          },
           async: false,
         });
         return false;
-- 
1.7.5.4


From a5072d33b807217ec08b9cbc2e0026a13c313e72 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Mon, 6 Jun 2011 14:06:04 -0400
Subject: [PATCH 07/17] Issue #1173692: By default, allow access to files for
 30 seconds.

---
 jplayer.module |    8 ++++----
 1 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/jplayer.module b/jplayer.module
index 0e6cc08..38b016c 100644
--- a/jplayer.module
+++ b/jplayer.module
@@ -45,11 +45,11 @@ function jplayer_authorize($filepath, $timestamp) {
     $_SESSION['jplayer_protect'] = array();
   }
 
-  if ($timestamp < (time() + variable_get('jplayer_access_time', 5))) {
+  if ($timestamp < (time() + variable_get('jplayer_access_time', 30))) {
     $_SESSION['jplayer_protect'][$filepath] = $timestamp;
   }
 
-  drupal_json(time() + variable_get('jplayer_access_time', 5));
+  drupal_json(time() + variable_get('jplayer_access_time', 30));
 }
 
 /**
@@ -126,8 +126,8 @@ function jplayer_file_download($filepath) {
   // the field uses jPlayer, and we know when the player last started to access
   // the file.
   if ($started) {
-    if (time() <= ($started + variable_get('jplayer_access_time', 5))) {
-      // Allow access.
+    if (time() <= ($started + variable_get('jplayer_access_time', 30))) {
+      // Allow access, and immediately expire access to the file.
       return NULL;
     }
   }
-- 
1.7.5.4


From e7066913f532c0516d1844d6873d794eab855b65 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Mon, 6 Jun 2011 14:06:46 -0400
Subject: [PATCH 08/17] Issue #1173692: Only allow accessing a protected file
 once.

---
 jplayer.module   |    6 +++++-
 theme/jplayer.js |    1 +
 2 files changed, 6 insertions(+), 1 deletions(-)

diff --git a/jplayer.module b/jplayer.module
index 38b016c..ac3d62a 100644
--- a/jplayer.module
+++ b/jplayer.module
@@ -120,7 +120,10 @@ function jplayer_file_download($filepath) {
   $encoded = str_replace('sites/default/files', 'system/files', $encoded);
   // For some reason ampersands are encoded twice by the browser.
   $encoded = str_replace("%26", "%2526", $encoded);
-  $started = (int)$_SESSION['jplayer_protect'][$GLOBALS['base_url'] . '/' . $encoded];
+  $access_key = $GLOBALS['base_url'] . '/' . $encoded;
+  if (isset($_SESSION['jplayer_protect'][$access_key])) {
+    $started = (int)$_SESSION['jplayer_protect'][$access_key];
+  }
 
   // Now we know that content protection is enabled, at least one display for
   // the field uses jPlayer, and we know when the player last started to access
@@ -128,6 +131,7 @@ function jplayer_file_download($filepath) {
   if ($started) {
     if (time() <= ($started + variable_get('jplayer_access_time', 30))) {
       // Allow access, and immediately expire access to the file.
+      unset($_SESSION['jplayer_protect'][$access_key]);
       return NULL;
     }
   }
diff --git a/theme/jplayer.js b/theme/jplayer.js
index a85ed56..1e4f0c9 100644
--- a/theme/jplayer.js
+++ b/theme/jplayer.js
@@ -63,6 +63,7 @@ Drupal.behaviors.jPlayer = function(context) {
       });
     }
 
+    // Handle pinging the authorization URL if needed.
     if (Drupal.settings.jPlayer.protected) {
       $(wrapper).find('a.jp-play').click(function() {
         // Generate the authorization URL to ping.
-- 
1.7.5.4


From e6d1192857d51cd7e50e96f663cea6bc23b3ef83 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Mon, 6 Jun 2011 16:31:32 -0400
Subject: [PATCH 09/17] Issue #1173692: Fix the multifile player when
 accessing protected files.

---
 theme/jplayer.js |   73 ++++++++++++++++++++++++++++++++---------------------
 1 files changed, 44 insertions(+), 29 deletions(-)

diff --git a/theme/jplayer.js b/theme/jplayer.js
index 1e4f0c9..e920f8a 100644
--- a/theme/jplayer.js
+++ b/theme/jplayer.js
@@ -31,6 +31,9 @@ Drupal.behaviors.jPlayer = function(context) {
         $(this).click(function() {
           active = n;
           Drupal.jPlayer.setActive(wrapper, player, playlist, n);
+          if (Drupal.settings.jPlayer.protected) {
+            Drupal.jPlayer.authorize(wrapper, player);
+          }
           Drupal.jPlayer.play(wrapper, player);
           return false;
         });
@@ -38,6 +41,9 @@ Drupal.behaviors.jPlayer = function(context) {
 
       // Enable play, pause, and stop buttons.
       $(wrapper).find('a.jp-play').click(function() {
+        if (Drupal.settings.jPlayer.protected) {
+          Drupal.jPlayer.authorize(wrapper, player);
+        }
         Drupal.jPlayer.play(wrapper, player);
         return false;
       });
@@ -64,36 +70,9 @@ Drupal.behaviors.jPlayer = function(context) {
     }
 
     // Handle pinging the authorization URL if needed.
-    if (Drupal.settings.jPlayer.protected) {
+    if (playerType != 'playlist' && Drupal.settings.jPlayer.protected) {
       $(wrapper).find('a.jp-play').click(function() {
-        // Generate the authorization URL to ping.
-        var time = new Date();
-        var authorize_url = Drupal.settings.basePath + 'jplayer/authorize/' + Drupal.jPlayer.base64Encode($(player).attr('rel')) + '/' + Drupal.jPlayer.base64Encode(parseInt(time.getTime() / 1000).toString());
-
-        // Ping the authorization URL. We need to disable async so that this
-        // command finishes before thisandler returns.
-        $.ajax({
-          url: authorize_url,
-          success: function(data) {
-            // Check to see if the access has expired. This could happen due to
-            // clock sync differences between the server and the client.
-            var seconds = parseInt(data);
-            var expires = new Date(seconds * 1000);
-            if ($('#jplayer-message').size() == 0) {
-              $(wrapper).parent().prepend('<div id="jplayer-message" class="messages error"></div>');
-              $('#jplayer-message').hide();
-            }
-            if (expires < time) {
-              var message = Drupal.t('There was an error downloading the audio. Try <a href="@url">reloading the page</a>. If the error persists, check that your computer\'s clock is accurate.', {"@url" : window.location});
-              $('#jplayer-message').fadeOut('fast').html("<ul><li>" + message + "</li></ul>").fadeIn('fast');
-              $(wrapper).hide();
-            }
-            else {
-              $('#jplayer-message').fadeOut('fast');
-            }
-          },
-          async: false,
-        });
+        Drupal.jPlayer.authorize(wrapper, player);
         return false;
       });
     }
@@ -175,6 +154,42 @@ Drupal.jPlayer.previous = function(wrapper, player, playlist, current) {
   return index;
 };
 
+/**
+ * Ping the authorization URL to gain access to protected files.
+ */
+Drupal.jPlayer.authorize = function(wrapper, player) {
+  // Generate the authorization URL to ping.
+  var time = new Date();
+  var authorize_url = Drupal.settings.basePath + 'jplayer/authorize/' + Drupal.jPlayer.base64Encode($(player).attr('rel')) + '/' + Drupal.jPlayer.base64Encode(parseInt(time.getTime() / 1000).toString());
+
+  // Ping the authorization URL. We need to disable async so that this
+  // command finishes before thisandler returns.
+
+  $.ajax({
+    url: authorize_url,
+    success: function(data) {
+      // Check to see if the access has expired. This could happen due to
+      // clock sync differences between the server and the client.
+      var seconds = parseInt(data);
+      var expires = new Date(seconds * 1000);
+      if ($('#jplayer-message').size() == 0) {
+        $(wrapper).parent().prepend('<div id="jplayer-message" class="messages error"></div>');
+        $('#jplayer-message').hide();
+      }
+      if (expires < time) {
+        var message = Drupal.t('There was an error downloading the audio. Try <a href="@url">reloading the page</a>. If the error persists, check that your computer\'s clock is accurate.', {"@url" : window.location});
+        $('#jplayer-message').fadeOut('fast').html("<ul><li>" + message + "</li></ul>").fadeIn('fast');
+        $(wrapper).hide();
+      }
+      else {
+        $('#jplayer-message').fadeOut('fast');
+      }
+    },
+    async: false,
+  });
+  return false;
+};
+
 Drupal.jPlayer.base64Encode = function(data) {
   // From http://phpjs.org/functions/base64_encode:358 where it is
   // dual licensed under GPL/MIT.
-- 
1.7.5.4


From edf4342265bfb982095bcb3d0a28cb982462b95f Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Mon, 6 Jun 2011 16:34:45 -0400
Subject: [PATCH 10/17] Issue #1173692: Authorize access to protected files
 when autoplay is enabled.

---
 theme/jplayer.js |    3 +++
 1 files changed, 3 insertions(+), 0 deletions(-)

diff --git a/theme/jplayer.js b/theme/jplayer.js
index e920f8a..d45c566 100644
--- a/theme/jplayer.js
+++ b/theme/jplayer.js
@@ -85,6 +85,9 @@ Drupal.behaviors.jPlayer = function(context) {
           if (playerType == 'playlist') {
             Drupal.jPlayer.setActive(wrapper, player, playlist, active);
           }
+          if (Drupal.settings.jPlayer.protected) {
+            Drupal.jPlayer.authorize(wrapper, player);
+          }
           Drupal.jPlayer.play(wrapper, player);
         }
       },
-- 
1.7.5.4


From 55fa2ac1e1a4d6f75885e4779cf3282e94c460a1 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Tue, 14 Jun 2011 16:42:59 -0400
Subject: [PATCH 11/17] Issue #1173692: Only show the content protection
 settings if the site is set to private file
 downloads.

---
 includes/jplayer.admin.inc |   31 +++++++++++++++++++------------
 1 files changed, 19 insertions(+), 12 deletions(-)

diff --git a/includes/jplayer.admin.inc b/includes/jplayer.admin.inc
index b15333a..e5e5636 100644
--- a/includes/jplayer.admin.inc
+++ b/includes/jplayer.admin.inc
@@ -31,18 +31,25 @@ function jplayer_settings_form() {
     '#default_value' => variable_get('jplayer_autoplay', ''),
   );
 
-  $form['options']['jplayer_protected'] = array(
-    '#title' => t('Protect audio files from direct downloads'),
-    '#type' => 'checkbox',
-    '#default_value' => variable_get('jplayer_protected', FALSE),
-  );
-  $form['options']['jplayer_access_time'] = array(
-    '#title' => t('Access delay'),
-    '#type' => 'textfield',
-    '#default_value' => variable_get('jplayer_access_time', 5),
-    '#size' => 5,
-    '#description' => t('The number of seconds that a client will have access to download a protected file after it is requested by jPlayer'),
-  );
+  if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PRIVATE) {
+    $form['options']['jplayer_protected'] = array(
+      '#title' => t('Protect audio files from direct downloads'),
+      '#type' => 'checkbox',
+      '#default_value' => variable_get('jplayer_protected', FALSE),
+    );
+    $form['options']['jplayer_access_time'] = array(
+      '#title' => t('Access delay'),
+      '#type' => 'textfield',
+      '#default_value' => variable_get('jplayer_access_time', 30),
+      '#size' => 5,
+      '#description' => t('The number of seconds that a client will have access to download a protected file after it is requested by jPlayer.'),
+    );
+  }
+  else {
+    $form['options']['jplayer_protected'] = array(
+      '#value' => t('To enable file download protection, first <a href="@file-system-settings">set the site download method to Private</a>.', array('@file-system-settings' => url('admin/settings/file-system', array('query' => drupal_get_destination())))),
+    );
+  }
 
   $form = system_settings_form($form);
   $form['#validate'][] = 'jplayer_settings_form_validate';
-- 
1.7.5.4


From 82e4c0ecb5f0aea7aa5ffc5789e7ac6a3efc548c Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Tue, 14 Jun 2011 16:43:42 -0400
Subject: [PATCH 12/17] Issue #1173692: Handle browsers requesting a file
 multiple times to fetch metadata.

---
 jplayer.module |    8 ++++++--
 1 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/jplayer.module b/jplayer.module
index ac3d62a..061d8ba 100644
--- a/jplayer.module
+++ b/jplayer.module
@@ -130,8 +130,12 @@ function jplayer_file_download($filepath) {
   // the file.
   if ($started) {
     if (time() <= ($started + variable_get('jplayer_access_time', 30))) {
-      // Allow access, and immediately expire access to the file.
-      unset($_SESSION['jplayer_protect'][$access_key]);
+      // Allow access, and immediately expire access to the file. Some browsers
+      // (such as Chrome) send multiple HTTP requests for an <audio> element,
+      // so if the RANGE header is set we continue to allow access.
+      if (!isset($_SERVER['HTTP_RANGE'])) {
+        unset($_SESSION['jplayer_protect'][$access_key]);
+      }
       return NULL;
     }
   }
-- 
1.7.5.4


From 09084687d3da7cc1848e8b633584f64d6ba96e13 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Thu, 16 Jun 2011 09:26:47 -0400
Subject: [PATCH 13/17] Issue #1173692: Add a table to log when jPlayer denies
 access to a file.

---
 jplayer.install |   94 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 jplayer.module  |   14 ++++++++
 2 files changed, 108 insertions(+), 0 deletions(-)

diff --git a/jplayer.install b/jplayer.install
index d8c9520..e686feb 100644
--- a/jplayer.install
+++ b/jplayer.install
@@ -6,6 +6,51 @@
  */
 
 /**
+ * Implementation of hook_schema().
+ */
+function jplayer_schema() {
+  $schema = array();
+  $schema['jplayer_denied'] = array(
+    'description' => 'Contains user statistics for when a user is blocked from downloading a file.',
+    'fields' => array(
+      'uid' => array(
+        'description' => 'The user ID of the user.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'fid' => array(
+        'description' => 'The file ID that was denied access.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'timestamp' => array(
+        'description' => 'The last time this user was denied access.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'hostname' => array(
+        'description' => 'The hostname of the user that was denied access.',
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+    ),
+    'primary key' => array('uid', 'fid', 'timestamp'),
+    'indexes' => array(
+      'uid' => array('uid'),
+      'fid' => array('fid'),
+      'timestamp' => array('timestamp'),
+    ),
+  );
+
+  return $schema;
+}
+
+/**
  * Implementation of hook_requirements().
  */
 function jplayer_requirements($phase) {
@@ -33,3 +78,52 @@ function jplayer_requirements($phase) {
 function jplayer_uninstall() {
   //variable_del('jplayer_compression_type');
 }
+
+/**
+ * Add a table to track when direct file downloads are denied.
+ */
+function jplayer_update_6001() {
+  $ret = array();
+  $schema = array();
+  $schema['jplayer_denied'] = array(
+    'description' => 'Contains user statistics for when a user is blocked from downloading a file.',
+    'fields' => array(
+      'uid' => array(
+        'description' => 'The user ID of the user.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'fid' => array(
+        'description' => 'The file ID that was denied access.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'hostname' => array(
+        'description' => 'The hostname of the user that was denied access.',
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'timestamp' => array(
+        'description' => 'The last time this user was denied access.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array('uid', 'fid', 'timestamp'),
+    'indexes' => array(
+      'uid' => array('uid'),
+      'fid' => array('fid'),
+      'hostname' => array('hostname'),
+      'timestamp' => array('timestamp'),
+    ),
+  );
+
+  db_create_table($ret, 'jplayer_denied', $schema['jplayer_denied']);
+  return $ret;
+}
+
diff --git a/jplayer.module b/jplayer.module
index 061d8ba..10947c1 100644
--- a/jplayer.module
+++ b/jplayer.module
@@ -53,6 +53,14 @@ function jplayer_authorize($filepath, $timestamp) {
 }
 
 /**
+ * Implementation of hook_cron().
+ */
+function jplayer_cron() {
+  // Delete records older than a week.
+  db_query("DELETE FROM {jplayer_denied} WHERE timestamp < %d", time() - 604800);
+}
+
+/**
  * Implementation of hook_file_download().
  */
 function jplayer_file_download($filepath) {
@@ -141,6 +149,12 @@ function jplayer_file_download($filepath) {
   }
 
   // Otherwise, deny access as the last played time is too far in the past.
+  $denied = new stdClass();
+  $denied->uid = $GLOBALS['user']->uid;
+  $denied->fid = $file->fid;
+  $denied->hostname = ip_address();
+  $denied->timestamp = time();
+  drupal_write_record('jplayer_denied', $denied);
   return -1;
 }
 
-- 
1.7.5.4


From e8ce49761d347c1fe35f08920ae86c7798f54432 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Thu, 16 Jun 2011 09:27:21 -0400
Subject: [PATCH 14/17] Issue #1173692: Add a page showing the top 50 users
 who have been denied access to jPlayer files.

---
 includes/jplayer.admin.inc |   50 ++++++++++++++++++++++++++++++++++++++++++++
 jplayer.module             |    9 ++++++++
 2 files changed, 59 insertions(+), 0 deletions(-)

diff --git a/includes/jplayer.admin.inc b/includes/jplayer.admin.inc
index e5e5636..b548331 100644
--- a/includes/jplayer.admin.inc
+++ b/includes/jplayer.admin.inc
@@ -81,3 +81,53 @@ function jplayer_settings_form_validate($form, &$form_state) {
 function jplayer_settings_form_submit($form, &$form_state) {
   drupal_set_message(t('The jPlayer library (version @version) successfully found in the %directory directory.', array('@version' => $form_state['jplayer_version'], '%directory' => $form_state['values']['jplayer_directory'])));
 }
+
+/**
+ * Page callback for the jPlayer protection statistics page. This page is
+ * useful in determining if a browser is misbehaving and blocking legitimiate
+ * file accesses, or if a user is trying to download a protected file.
+ */
+function jplayer_protection_statistics() {
+  if (!variable_get('jplayer_protected', FALSE)) {
+    drupal_set_message(t('<a href="@jplayer-settings">jPlayer content protection</a> is not currently enabled.', array('@jplayer-settings' => url('admin/settings/jplayer', array('query' => drupal_get_destination())))));
+  }
+
+  $output = '<p>' . t('This table shows the 50 top users who have been denied access to direct downloads of jPlayer files.') . '</p>';
+
+  $result = db_query("SELECT COUNT(1) as total, uid as user, MAX(timestamp) as timestamp FROM {jplayer_denied} GROUP BY uid ORDER BY total DESC, timestamp DESC LIMIT 50;");
+  $rows = array();
+  while ($denied = db_fetch_array($result)) {
+    // Format data from the query.
+    $uid = $denied['user'];
+    $denied['user'] = theme('username', user_load($denied['user']));
+    $denied['timestamp'] = format_date($denied['timestamp']);
+
+    // Find the top-denied file for the user.
+    $top_file = db_fetch_object(db_query("SELECT COUNT(fid) as fid_count, fid FROM {jplayer_denied} WHERE uid = %d GROUP BY fid ORDER BY fid_count DESC LIMIT 1", $uid));
+    $top_file = db_fetch_object(db_query("SELECT * FROM {files} WHERE fid = %d", $top_file->fid));
+    $denied['file'] = $top_file->filepath;
+
+    // Find the top hostname for the user.
+    $top_hostname = db_fetch_object(db_query("SELECT COUNT(hostname) as hostname_count, hostname FROM {jplayer_denied} WHERE uid = %d GROUP BY hostname ORDER BY hostname_count DESC LIMIT 1", $uid));
+    $denied['hostname'] = $top_hostname->hostname;
+
+    $rows[] = $denied;
+  }
+
+  $header = array(
+    t('Accesses denied'),
+    t('User'),
+    t('Last denied'),
+    t('Top Denied File'),
+    t('Top Hostname'),
+  );
+
+  if (!empty($rows)) {
+    $output .= theme('table', $header, $rows);
+  }
+  else {
+    $output .= '<p>' . t('There have been no files that have been denied access within the last week.') . '</p>';
+  }
+  return $output;
+}
+
diff --git a/jplayer.module b/jplayer.module
index 10947c1..07b25fa 100644
--- a/jplayer.module
+++ b/jplayer.module
@@ -23,6 +23,15 @@ function jplayer_menu() {
     'file' => 'includes/jplayer.admin.inc',
   );
 
+  $items['admin/reports/jplayer'] = array(
+    'title' => 'jPlayer content protection statistics',
+    'page callback' => 'jplayer_protection_statistics',
+    'access arguments' => array('access site reports'),
+    'description' => 'View statistics related to content protection for jPlayer.',
+    'file' => 'jplayer.admin.inc',
+    'file path' => drupal_get_path('module', 'jplayer') . '/includes',
+  );
+
   $items['jplayer/authorize'] = array(
     'title' => 'jPlayer content authorization',
     'page callback' => 'jplayer_authorize',
-- 
1.7.5.4


From 188c685e54f61698a4c03cff1eec599fc0356fe6 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Thu, 16 Jun 2011 15:33:50 -0400
Subject: [PATCH 15/17] Issue #1173692: Allow setting protected access for
 media in case the site has .htaccess or other
 configuration enforcing private files for the media.

---
 includes/jplayer.admin.inc |   35 +++++++++++++++++------------------
 1 files changed, 17 insertions(+), 18 deletions(-)

diff --git a/includes/jplayer.admin.inc b/includes/jplayer.admin.inc
index b548331..2b307ec 100644
--- a/includes/jplayer.admin.inc
+++ b/includes/jplayer.admin.inc
@@ -31,26 +31,25 @@ function jplayer_settings_form() {
     '#default_value' => variable_get('jplayer_autoplay', ''),
   );
 
-  if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PRIVATE) {
-    $form['options']['jplayer_protected'] = array(
-      '#title' => t('Protect audio files from direct downloads'),
-      '#type' => 'checkbox',
-      '#default_value' => variable_get('jplayer_protected', FALSE),
-    );
-    $form['options']['jplayer_access_time'] = array(
-      '#title' => t('Access delay'),
-      '#type' => 'textfield',
-      '#default_value' => variable_get('jplayer_access_time', 30),
-      '#size' => 5,
-      '#description' => t('The number of seconds that a client will have access to download a protected file after it is requested by jPlayer.'),
-    );
-  }
-  else {
-    $form['options']['jplayer_protected'] = array(
-      '#value' => t('To enable file download protection, first <a href="@file-system-settings">set the site download method to Private</a>.', array('@file-system-settings' => url('admin/settings/file-system', array('query' => drupal_get_destination())))),
-    );
+
+  $form['options']['jplayer_protected'] = array(
+    '#title' => t('Protect audio files from direct downloads'),
+    '#type' => 'checkbox',
+    '#default_value' => variable_get('jplayer_protected', FALSE),
+  );
+
+  if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC) {
+    $form['options']['jplayer_protected']['#description'] = t('To enable file download protection, first <a href="@file-system-settings">set the site download method to Private</a>.', array('@file-system-settings' => url('admin/settings/file-system', array('query' => drupal_get_destination()))));
   }
 
+  $form['options']['jplayer_access_time'] = array(
+    '#title' => t('Access delay'),
+    '#type' => 'textfield',
+    '#default_value' => variable_get('jplayer_access_time', 30),
+    '#size' => 5,
+    '#description' => t('The number of seconds that a client will have access to download a protected file after it is requested by jPlayer.'),
+  );
+
   $form = system_settings_form($form);
   $form['#validate'][] = 'jplayer_settings_form_validate';
   $form['#submit'][] = 'jplayer_settings_form_submit';
-- 
1.7.5.4


From 9fe3901215ccb63f5167d81d7be73590337885f6 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Thu, 16 Jun 2011 15:34:34 -0400
Subject: [PATCH 16/17] Issue #1173692: Check for unencoded URLs as
 mod_rewrite will decode URL entities.

---
 jplayer.module |   24 +++++++++++++++++-------
 1 files changed, 17 insertions(+), 7 deletions(-)

diff --git a/jplayer.module b/jplayer.module
index 07b25fa..8cc1829 100644
--- a/jplayer.module
+++ b/jplayer.module
@@ -131,16 +131,26 @@ function jplayer_file_download($filepath) {
     }
   }
 
-  // We need to figure out how the browser would have URL-encoded the file
-  // name.
-  $encoded = str_replace($file->filename, rawurlencode($file->filename), $filepath);
-  $encoded = str_replace('sites/default/files', 'system/files', $encoded);
-  // For some reason ampersands are encoded twice by the browser.
-  $encoded = str_replace("%26", "%2526", $encoded);
-  $access_key = $GLOBALS['base_url'] . '/' . $encoded;
+  $access_key = $GLOBALS['base_url'] . '/' . $filepath;
+
   if (isset($_SESSION['jplayer_protect'][$access_key])) {
     $started = (int)$_SESSION['jplayer_protect'][$access_key];
   }
+  else {
+    // We need to figure out how the browser would have URL-encoded the file
+    // name. If mod_rewrite is modifying the URL, it will decode URL-encoded
+    // characters, so we need to check both.
+    $encoded = str_replace($file->filename, rawurlencode($file->filename), $filepath);
+    $encoded = str_replace('sites/default/files', 'system/files', $encoded);
+    // For some reason ampersands are encoded twice by the browser.
+    $encoded = str_replace("%26", "%2526", $encoded);
+    $encoded_access_key = $GLOBALS['base_url'] . '/' . $encoded;
+
+    if (isset($_SESSION['jplayer_protect'][$encoded_access_key])) {
+      $access_key = $encoded_access_key;
+      $started = (int)$_SESSION['jplayer_protect'][$access_key];
+    }
+  }
 
   // Now we know that content protection is enabled, at least one display for
   // the field uses jPlayer, and we know when the player last started to access
-- 
1.7.5.4


From 36fa929ceb37e276a9eb95366f23d6612ae7c12c Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Fri, 17 Jun 2011 10:35:28 -0400
Subject: [PATCH 17/17] Issue #1173692: Implement hook_install() and
 hook_uninstall() for the new jplayer_protected table.

---
 jplayer.install |   24 +++++++++++++++++-------
 1 files changed, 17 insertions(+), 7 deletions(-)

diff --git a/jplayer.install b/jplayer.install
index e686feb..a2a7218 100644
--- a/jplayer.install
+++ b/jplayer.install
@@ -25,12 +25,6 @@ function jplayer_schema() {
         'not null' => TRUE,
         'default' => 0,
       ),
-      'timestamp' => array(
-        'description' => 'The last time this user was denied access.',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
       'hostname' => array(
         'description' => 'The hostname of the user that was denied access.',
         'type' => 'varchar',
@@ -38,11 +32,18 @@ function jplayer_schema() {
         'not null' => TRUE,
         'default' => '',
       ),
+      'timestamp' => array(
+        'description' => 'The last time this user was denied access.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
     ),
     'primary key' => array('uid', 'fid', 'timestamp'),
     'indexes' => array(
       'uid' => array('uid'),
       'fid' => array('fid'),
+      'hostname' => array('hostname'),
       'timestamp' => array('timestamp'),
     ),
   );
@@ -51,6 +52,13 @@ function jplayer_schema() {
 }
 
 /**
+ * Implementation of hook_install().
+ */
+function jplayer_install() {
+  drupal_install_schema('jplayer');
+}
+
+/**
  * Implementation of hook_requirements().
  */
 function jplayer_requirements($phase) {
@@ -76,7 +84,9 @@ function jplayer_requirements($phase) {
  * Implementation of hook_uninstall().
  */
 function jplayer_uninstall() {
-  //variable_del('jplayer_compression_type');
+  drupal_uninstall_schema('jplayer');
+  variable_del('jplayer_protected');
+  variable_del('jplayer_access_time');
 }
 
 /**
-- 
1.7.5.4

