From 461599fb23a151b4e8d27042cb5a1db4ca7f5ae3 Mon Sep 17 00:00:00 2001 From: Andrew Berry Date: Wed, 1 Jun 2011 16:38:00 -0400 Subject: [PATCH 01/14] #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 Date: Thu, 2 Jun 2011 11:17:12 -0400 Subject: [PATCH 02/14] #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 Date: Thu, 2 Jun 2011 11:17:48 -0400 Subject: [PATCH 03/14] #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 Date: Thu, 2 Jun 2011 11:26:07 -0400 Subject: [PATCH 04/14] #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 Date: Thu, 2 Jun 2011 11:26:29 -0400 Subject: [PATCH 05/14] #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 Date: Mon, 6 Jun 2011 12:50:54 -0400 Subject: [PATCH 06/14] 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('
'); + $('#jplayer-message').hide(); + } + if (expires < time) { + var message = Drupal.t('There was an error downloading the audio. Try reloading the page. If the error persists, check that your computer\'s clock is accurate.', {"@url" : window.location}); + $('#jplayer-message').fadeOut('fast').html("
  • " + message + "
").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 Date: Mon, 6 Jun 2011 14:06:04 -0400 Subject: [PATCH 07/14] 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 Date: Mon, 6 Jun 2011 14:06:46 -0400 Subject: [PATCH 08/14] 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 Date: Mon, 6 Jun 2011 16:31:32 -0400 Subject: [PATCH 09/14] 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('
'); - $('#jplayer-message').hide(); - } - if (expires < time) { - var message = Drupal.t('There was an error downloading the audio. Try reloading the page. If the error persists, check that your computer\'s clock is accurate.', {"@url" : window.location}); - $('#jplayer-message').fadeOut('fast').html("
  • " + message + "
").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('
'); + $('#jplayer-message').hide(); + } + if (expires < time) { + var message = Drupal.t('There was an error downloading the audio. Try reloading the page. If the error persists, check that your computer\'s clock is accurate.', {"@url" : window.location}); + $('#jplayer-message').fadeOut('fast').html("
  • " + message + "
").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 Date: Mon, 6 Jun 2011 16:34:45 -0400 Subject: [PATCH 10/14] 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 Date: Tue, 14 Jun 2011 16:42:59 -0400 Subject: [PATCH 11/14] 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 set the site download method to Private.', 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 Date: Tue, 14 Jun 2011 16:43:42 -0400 Subject: [PATCH 12/14] 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