diff --git README.txt README.txt index c7229ff..31ca4c6 100644 --- README.txt +++ README.txt @@ -56,6 +56,17 @@ To learn more about parallelizing downloads by using subdomains (or CDNs!), see http://drupal.org/project/parallel. +Module compatibility +-------------------- +This module has been verified to be compatible with the following modules: +- CSS Gzip + (This module is obsolete when using Origin Pull mode with the Far Future + expiration setting enabled.) +- ImageCache + (This module requires one of the patches that ships with the CDN module.) +- Javascript Aggregator + + Installation ------------ 1) Note: skip this step if you want to rely on the fallback mechanism! diff --git cdn.admin.inc cdn.admin.inc index c424457..77f07c6 100644 --- cdn.admin.inc +++ cdn.admin.inc @@ -152,7 +152,7 @@ function cdn_admin_details_form(&$form_state) { t("@format-url is the CDN URL that should be used. E.g.: 'http://cdn-a.com'.", $format_variables), t("@format-extensions is an optional - setting to limit which files should be served from a CDN. E.g.: + setting to limit which file types should be served from a CDN. E.g.: '.css .jpg .jpeg .png'.", $format_variables), ); $example_list_items = array( @@ -181,6 +181,114 @@ function cdn_admin_details_form(&$form_state) { '#dependency' => array('radio:' . CDN_MODE_VARIABLE => array(CDN_MODE_BASIC)), ); + $farfuture_extensions = variable_get(CDN_BASIC_FARFUTURE_EXTENSIONS_VARIABLE, CDN_BASIC_FARFUTURE_EXTENSIONS_DEFAULT); + $farfuture_variables = array( + '!extensions' => "." . implode(", .", explode("\n", $farfuture_extensions)) . "", + '!extensions-compressed' => "." . implode(", .", explode("\n", CDN_BASIC_FARFUTURE_GZIP_EXTENSIONS)) . "", + ); + $form['settings'][CDN_BASIC_FARFUTURE_VARIABLE] = array( + '#type' => 'checkbox', + '#title' => t('Far Future expiration'), + '#description' => t('Recommended for maximum performance boost! +
+ Mark all files served from the CDN to expire in the + far future (decades from now). This is the same as + telling browsers to always use the cached + files. This can significantly speed up page loads. + Files are also automatically compressed (when the + browser supports it), to speed up page loads even + further. +
+ Of course, you still want visitors to immediately get + new versions of files when they change. That is why + unique filenames are generated automatically. +
+ For the experts: the following HTTP + headers are set: Expires, + Cache-Control, + Last-Modified, Vary, for + files with one of the following extensions: !extensions + and of these extensions, some will also be + automatically compressed: !extensions-compressed.', $farfuture_variables + ), + '#default_value' => variable_get(CDN_BASIC_FARFUTURE_VARIABLE, CDN_BASIC_FARFUTURE_DEFAULT), + '#process' => array('ctools_dependent_process'), + '#dependency' => array('radio:' . CDN_MODE_VARIABLE => array(CDN_MODE_BASIC)), + ); + + $format_variables = array( + '@format-directory' => '<' . t('directory') . '>', + '@format-extensions' => '<' . t('extensions') . '>', + '@format-unique-identifier-method' => '<' . t('unique identifier method') . '>', + ); + + $methods = array(); + $ufi_info = module_invoke_all('cdn_unique_file_identifier_info'); + foreach ($ufi_info as $ufi) { + $methods[] = $ufi['label'] + . ' (' . $ufi['machine_name'] . '): ' + . $ufi['description']; + } + $format_variables['!format-available-unique-identifier-methods'] = theme('item_list', $methods); + // '@format-available-unique-identifier-methods' + + + $format_explanation_list_items = array( + t("@format-directory is the directory (may include + wildcards) to which a unique identifier method will be applied. Multiple + directories may be listed, separated with semi-colons + (:). E.g.: + 'sites/*/modules/*:sites/*/themes/*'.", $format_variables), + t("@format-extensions is an optional + setting to limit which file types should use this unique identifier + method. E.g.: + '.css .jpg .jpeg .png'.", $format_variables), + t("@format-unique-identifier-method sets the unique + identifier method that should be applied to the aforementioned + directories, and only to (optionally) the listed file types. Available + methods are: !format-available-unique-identifier-methods.", + $format_variables), + ); + $example_list_items = array( + t("This would generate a unique identifier for Drupal core files based on + the Drupal core version, files in the site directory would get unique + identifiers based on the last time they were modified and movie files + would not receive a unique identifier (they're so large browser can't + cache them anyway): +
misc/*:modules/*:themes/*|drupal_version\nsites/*|mtime\nsites/*|.avi .m4v .mov .mp4 .wmv .flv|perpetual
"), + t("In this second example, we're dealing with a more high-traffic website, + where it is too costly to access the filesystem for every served file. + Therefor, this site defines a CDN_DEPLOYMENT_ID constant + somewhere in its codebase. This constant changes whenever a module or + theme changes. This is therefor far more efficient. See the last line: +
misc/*:modules/*:themes/*|drupal_version\nsites/*|mtime\nsites/*|.avi .m4v .mov .mp4 .wmv .flv|perpetual\nsites/*/modules/*:sites/*/themes/*|deployment_id
"), + ); + $form['settings'][CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_VARIABLE] = array( + '#type' => 'textarea', + '#title' => t('Unique file identifier generation'), + '#description' => t("Define how unique file identifiers are generated.
+
+ Enter one rule per line, in the format @format-directory[|@format-extensions]|@format-unique-identifier-method:
+ !format-explanation-list +

+ Note that if no unique file identifier generation + method is specified for a file because none of the + above rules apply to it, the CDN module will fall + back to the !default-ufi-method method. +

+ Sample mappings: + !example-list", $format_variables + array( + '!format-explanation-list' => theme('item_list', $format_explanation_list_items), + '!example-list' => theme('item_list', $example_list_items), + '!default-ufi-method' => '' . CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_DEFAULT . '', + )), + '#size' => 35, + '#default_value' => variable_get(CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_VARIABLE, CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_DEFAULT), + '#process' => array('ctools_dependent_process'), + '#dependency' => array('edit-cdn-farfuture-status' => array('1')), + ); + + // // Advanced mode settings. // @@ -243,7 +351,6 @@ function cdn_admin_other_settings_form(&$form_state) { '#type' => 'checkbox', '#title' => t('CDN supports HTTPS'), '#default_value' => variable_get(CDN_HTTPS_SUPPORT_VARIABLE, FALSE), - '#process' => array('ctools_dependent_process'), ); $path_explanation = t( diff --git cdn.basic.farfuture.inc cdn.basic.farfuture.inc new file mode 100644 index 0000000..6bd1e13 --- /dev/null +++ cdn.basic.farfuture.inc @@ -0,0 +1,1238 @@ +=5.3 + if (function_exists('header_remove')) { + header_remove($header); + } + else { + // In PHP <5.3, we cannot remove headers. At least shorten them to save + // every byte possible and to stop leaking information needlessly. + drupal_set_header($header . ':'); + } + } + } + + // Remove all previously set Cache-Control headers, because we're going to + // override it. Since multiple Cache-Control headers might have been set, + // simply setting a new, overriding header isn't enough: that would only + // override the *last* Cache-Control header. Yay for PHP! + if (function_exists('header_remove')) { + header_remove('Cache-Control'); + } + else { + drupal_set_header("Cache-Control:"); + drupal_set_header("Cache-Control:"); + } + + // Default caching rules: no caching/immediate expiration. + drupal_set_header("Cache-Control: private, must-revalidate, proxy-revalidate"); + drupal_set_header("Expires: " . gmdate("D, d M Y H:i:s", time() - 86400) . "GMT"); + + // Instead of being powered by PHP, tell the world this resource was powered + // by the CDN module! + drupal_set_header("X-Powered-By: Drupal CDN module"); + // Instruct intermediate HTTP caches to store both a compressed (gzipped) + // and uncompressed version of the resource. + drupal_set_header("Vary: Accept-Encoding"); + // Determine the content type. + drupal_set_header("Content-Type: " . _cdn_basic_farfuture_get_mimetype(basename($path))); + // Support partial content requests. + drupal_set_header("Accept-Ranges: bytes"); + + // If the extension of the file that's being served is one of the far future + // extensions (by default: images, fonts and flash content), then cache it + // in the far future. + $farfuture_extensions = variable_get(CDN_BASIC_FARFUTURE_EXTENSIONS_VARIABLE, CDN_BASIC_FARFUTURE_EXTENSIONS_DEFAULT); + $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + if (in_array($extension, explode("\n", $farfuture_extensions))) { + // Remove all previously set Cache-Control headers, because we're going to + // override it. Since multiple Cache-Control headers might have been set, + // simply setting a new, overriding header isn't enough: that would only + // override the *last* Cache-Control header. Yay for PHP! + if (function_exists('header_remove')) { + header_remove('Cache-Control'); + } + else { + drupal_set_header("Cache-Control:"); + drupal_set_header("Cache-Control:"); + } + // Set a far future Cache-Control header (480 weeks), which prevents + // intermediate caches from transforming the data and allows any + // intermediate cache to cache it, since it's marked as a public resource. + drupal_set_header("Cache-Control: max-age=290304000, no-transform, public"); + // Set a far future Expires header. The maximum UNIX timestamp is somewhere + // in 2038. Set it to a date in 2037, just to be safe. + drupal_set_header("Expires: Tue, 20 Jan 2037 04:20:42 GMT"); + // Pretend the file was last modified a long time ago in the past, this will + // prevent browsers that don't support Cache-Control nor Expires headers to + // still request a new version too soon (these browsers calculate a + // heuristic to determine when to request a new version, based on the last + // time the resource has been modified). + // Also see http://code.google.com/speed/page-speed/docs/caching.html. + drupal_set_header("Last-Modified: Wed, 20 Jan 1988 04:20:42 GMT"); + } + + // GET requests with an "Accept-Encoding" header that lists "gzip". + if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE) { + // Only send gzipped files for some file extensions (it doesn't make sense + // to gzip images, for example). + if (in_array($extension, explode("\n", CDN_BASIC_FARFUTURE_GZIP_EXTENSIONS))) { + // Ensure a gzipped version of the file is stored on disk, instead of + // gzipping the file on every request. + $gzip_path = file_directory_path() . '/' . CDN_BASIC_FARFUTURE_GZIP_DIRECTORY . "/$path.$mtime.gz"; + if (!file_exists($gzip_path)) { + _cdn_basic_farfuture_create_directory_structure(dirname($gzip_path)); + file_put_contents($gzip_path, gzencode(file_get_contents($path), 9)); + } + // Make sure zlib.output_compression does not gzip our gzipped output. + ini_set('zlib.output_compression', '0'); + // Prepare for gzipped output. + drupal_set_header("Content-Encoding: gzip"); + $path = $gzip_path; + } + } + + // Conditional GET requests (i.e. with If-Modified-Since header). + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { + // All files served by this function are designed to expire in the far + // future. Hence we can simply always tell the client the requested file + // was not modified. + drupal_set_header("HTTP/1.1 304 Not Modified"); + } + // "Normal" GET requests. + else { + _cdn_transfer_file($path); + } + + exit(); +} + + +//---------------------------------------------------------------------------- +// Public functions. + +/** + * Gets the servers on which a file is available when basic mode is enabled. + * + * @param $path + * The path to get the servers for. + */ +function cdn_basic_farfuture_get_identifier($path) { + static $mapping; + static $ufi_info; + $servers = array(); + + // Gather all unique file identifier info. + if (!isset($ufi_info)) { + $ufi_info = module_invoke_all('cdn_unique_file_identifier_info'); + } + + // We only need to parse the textual CDN mapping once into a lookup table. + if (!isset($mapping)) { + $mapping = _cdn_basic_farfuture_parse_raw_mapping(variable_get(CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_VARIABLE, CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_DEFAULT)); + } + + // Determine which UFI method should be used. Note that we keep on trying to + // find another method until the end: the order of rules matters! + $ufi_method = FALSE; + foreach (array_keys($mapping) as $directory) { + if (drupal_match_path($path, $directory)) { + // Parse the file extension from the given path; convert it to lower case. + $file_extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + + // Based on the file extension, determine which key should be used to find + // the CDN URLs in the mapping lookup table, if any. + if (array_key_exists($file_extension, $mapping[$directory])) { + $key = $file_extension; + } + elseif (array_key_exists('*', $mapping[$directory])) { + $key = '*'; + } + else { + $key = NULL; + } + + $ufi_method = $mapping[$directory][$key]; + } + } + + // Fall back to the default UFI method in case no UFI method is defined by + // the user. + if ($ufi_method === FALSE) { + $ufi_method = CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_DEFAULT; + } + + $prefix = $ufi_info[$ufi_method]['prefix']; + if (isset($ufi_info[$ufi_method]['value'])) { + $value = $ufi_info[$ufi_method]['value']; + } + else { + $callback = $ufi_info[$ufi_method]['callback']; + $value = call_user_func_array($callback, array($path)); + } + + return "$prefix:$value"; +} + + +//---------------------------------------------------------------------------- +// Private functions. + +/** + * Parse the raw (textual) mapping into a lookup table, where the key is the + * file extension and the value is a list of CDN URLs that serve the file. + * + * @param $mapping_raw + * A raw (textual) mapping. + * @return + * The corresponding mapping lookup table. + */ +function _cdn_basic_farfuture_parse_raw_mapping($mapping_raw) { + $mapping = array(); + + if (!empty($mapping_raw)) { + $lines = preg_split("/[\n\r]+/", $mapping_raw, -1, PREG_SPLIT_NO_EMPTY); + foreach ($lines as $line) { + // Parse this line. It may or may not limit the CDN URL to a list of + // file extensions. + $parts = explode('|', $line); + $directories = explode(':', $parts[0]); + + // There may be 2 or 3 parts: + // - part 1: directories + // - part 2: file extensions (optional) + // - part 3: unique file identifier method + if (count($parts) == 2) { + $extensions = array('*'); // Use the asterisk as a wildcard. + $ufi_method = strtolower(trim($parts[1])); + } + else if (count($parts) == 3) { + // Convert to lower case, remove periods, whitespace and split on ' '. + $extensions = explode(' ', trim(str_replace('.', '', strtolower($parts[1])))); + $ufi_method = strtolower(trim($parts[2])); + } + + // Create the mapping lookup table. + foreach ($directories as $directory) { + foreach ($extensions as $extension) { + $mapping[$directory][$extension] = $ufi_method; + } + } + } + } + + return $mapping; +} + +/** + * Variant of Drupal's file_transfer(), based on + * http://www.thomthom.net/blog/2007/09/php-resumable-download-server/ + * to support ranged requests as well. + * + * Note: ranged requests that request multiple ranges are not supported. They + * are responded to with a 416. See + * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html + */ +function _cdn_transfer_file($path) { + $fp = @fopen($path, 'rb'); + + $size = filesize($path); // File size + $length = $size; // Content length + $start = 0; // Start byte + $end = $size - 1; // End byte + + // In case of a range request, seek within the file to the correct location. + if (isset($_SERVER['HTTP_RANGE'])) { + $c_start = $start; + $c_end = $end; + + // Extract the string containing the requested range. + list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2); + + // If the client requested multiple ranges, repond with a 416. + if (strpos($range, ',') !== FALSE) { + header('HTTP/1.1 416 Requested Range Not Satisfiable'); + header("Content-Range: bytes $start-$end/$size"); + exit(); + } + + // Case "Range: -n": final n bytes are requested. + if ($range[0] == '-') { + $c_start = $size - substr($range, 1); + } + // Case "Range: m-n": bytes m through n are requested. When n is empty or + // non-numeric, n is the last byte. + else { + $range = explode('-', $range); + $c_start = intval($range[0]); + $c_end = (isset($range[1]) && is_numeric($range[1])) ? intval($range[1]) : $size; + } + // Minor normalization: end bytes can not be larger than $end. + $c_end = ($c_end > $end) ? $end : $c_end; + + // If the requested range is not valid, respond with a 416. + if ($c_start > $c_end || $c_start > $end || $c_end > $end) { + header('HTTP/1.1 416 Requested Range Not Satisfiable'); + header("Content-Range: bytes $start-$end/$size"); + exit(); + } + + $start = $c_start; + $end = $c_end; + $length = $end - $start + 1; + fseek($fp, $start); + + // The ranged request is valid and will be performed, respond with a 206. + header('HTTP/1.1 206 Partial Content'); + header("Content-Range: bytes $start-$end/$size"); + } + header("Content-Length: $length"); + + // Start buffered download. Prevent reading too far for a ranged request. + $buffer = 1024 * 8; + while (!feof($fp) && ($p = ftell($fp)) <= $end) { + if ($p + $buffer > $end) { + $buffer = $end - $p + 1; + } + set_time_limit(0); // Reset time limit for big files. + echo fread($fp, $buffer); + flush(); // Free up memory, to prevent triggering PHP's memory limit. + } + fclose($fp); +} + +/** + * Determine an Internet Media Type, or MIME type from a filename. + * Borrowed from Drupal 7. + * + * @param $path + * A string containing the file path. + * @return + * The internet media type registered for the extension or + * application/octet-stream for unknown extensions. + * + * @see file_default_mimetype_mapping() + */ +function _cdn_basic_farfuture_get_mimetype($path) { + static $mapping; + + if (!isset($mapping)) { + $mapping = _cdn_basic_farfuture_mimetype_mapping();; + } + + $extension = ''; + $file_parts = explode('.', basename($path)); + + // Remove the first part: a full filename should not match an extension. + array_shift($file_parts); + + // Iterate over the file parts, trying to find a match. + // For my.awesome.image.jpeg, we try: + // - jpeg + // - image.jpeg, and + // - awesome.image.jpeg + while ($additional_part = array_pop($file_parts)) { + $extension = strtolower($additional_part . ($extension ? '.' . $extension : '')); + if (isset($mapping['extensions'][$extension])) { + return $mapping['mimetypes'][$mapping['extensions'][$extension]]; + } + } + + return 'application/octet-stream'; +} + +/** + * Default MIME extension mapping. + * Borrowed from Drupal 7. + * + * @return + * Array of mimetypes correlated to the extensions that relate to them. + */ +function _cdn_basic_farfuture_mimetype_mapping() { + return array( + 'mimetypes' => array( + 0 => 'application/andrew-inset', + 1 => 'application/atom', + 2 => 'application/atomcat+xml', + 3 => 'application/atomserv+xml', + 4 => 'application/cap', + 5 => 'application/cu-seeme', + 6 => 'application/dsptype', + 7 => 'application/hta', + 8 => 'application/java-archive', + 9 => 'application/java-serialized-object', + 10 => 'application/java-vm', + 11 => 'application/mac-binhex40', + 12 => 'application/mathematica', + 13 => 'application/msaccess', + 14 => 'application/msword', + 15 => 'application/octet-stream', + 16 => 'application/oda', + 17 => 'application/ogg', + 18 => 'application/pdf', + 19 => 'application/pgp-keys', + 20 => 'application/pgp-signature', + 21 => 'application/pics-rules', + 22 => 'application/postscript', + 23 => 'application/rar', + 24 => 'application/rdf+xml', + 25 => 'application/rss+xml', + 26 => 'application/rtf', + 27 => 'application/smil', + 28 => 'application/vnd.cinderella', + 29 => 'application/vnd.google-earth.kml+xml', + 30 => 'application/vnd.google-earth.kmz', + 31 => 'application/vnd.mozilla.xul+xml', + 32 => 'application/vnd.ms-excel', + 33 => 'application/vnd.ms-excel.addin.macroEnabled.12', + 34 => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 35 => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 36 => 'application/vnd.ms-excel.template.macroEnabled.12', + 37 => 'application/vnd.ms-pki.seccat', + 38 => 'application/vnd.ms-pki.stl', + 39 => 'application/vnd.ms-powerpoint', + 40 => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 41 => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 42 => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + 43 => 'application/vnd.ms-powerpoint.template.macroEnabled.12', + 44 => 'application/vnd.ms-word.document.macroEnabled.12', + 45 => 'application/vnd.ms-word.template.macroEnabled.12', + 46 => 'application/vnd.ms-xpsdocument', + 47 => 'application/vnd.oasis.opendocument.chart', + 48 => 'application/vnd.oasis.opendocument.database', + 49 => 'application/vnd.oasis.opendocument.formula', + 50 => 'application/vnd.oasis.opendocument.graphics', + 51 => 'application/vnd.oasis.opendocument.graphics-template', + 52 => 'application/vnd.oasis.opendocument.image', + 53 => 'application/vnd.oasis.opendocument.presentation', + 54 => 'application/vnd.oasis.opendocument.presentation-template', + 55 => 'application/vnd.oasis.opendocument.spreadsheet', + 56 => 'application/vnd.oasis.opendocument.spreadsheet-template', + 57 => 'application/vnd.oasis.opendocument.text', + 58 => 'application/vnd.oasis.opendocument.text-master', + 59 => 'application/vnd.oasis.opendocument.text-template', + 60 => 'application/vnd.oasis.opendocument.text-web', + 61 => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 62 => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 63 => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 64 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 65 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 66 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 67 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 68 => 'application/vnd.rim.cod', + 69 => 'application/vnd.smaf', + 70 => 'application/vnd.stardivision.calc', + 71 => 'application/vnd.stardivision.chart', + 72 => 'application/vnd.stardivision.draw', + 73 => 'application/vnd.stardivision.impress', + 74 => 'application/vnd.stardivision.math', + 75 => 'application/vnd.stardivision.writer', + 76 => 'application/vnd.stardivision.writer-global', + 77 => 'application/vnd.sun.xml.calc', + 78 => 'application/vnd.sun.xml.calc.template', + 79 => 'application/vnd.sun.xml.draw', + 80 => 'application/vnd.sun.xml.draw.template', + 81 => 'application/vnd.sun.xml.impress', + 82 => 'application/vnd.sun.xml.impress.template', + 83 => 'application/vnd.sun.xml.math', + 84 => 'application/vnd.sun.xml.writer', + 85 => 'application/vnd.sun.xml.writer.global', + 86 => 'application/vnd.sun.xml.writer.template', + 87 => 'application/vnd.symbian.install', + 88 => 'application/vnd.visio', + 89 => 'application/vnd.wap.wbxml', + 90 => 'application/vnd.wap.wmlc', + 91 => 'application/vnd.wap.wmlscriptc', + 92 => 'application/wordperfect', + 93 => 'application/wordperfect5.1', + 94 => 'application/x-123', + 95 => 'application/x-7z-compressed', + 96 => 'application/x-abiword', + 97 => 'application/x-apple-diskimage', + 98 => 'application/x-bcpio', + 99 => 'application/x-bittorrent', + 100 => 'application/x-cab', + 101 => 'application/x-cbr', + 102 => 'application/x-cbz', + 103 => 'application/x-cdf', + 104 => 'application/x-cdlink', + 105 => 'application/x-chess-pgn', + 106 => 'application/x-cpio', + 107 => 'application/x-debian-package', + 108 => 'application/x-director', + 109 => 'application/x-dms', + 110 => 'application/x-doom', + 111 => 'application/x-dvi', + 112 => 'application/x-flac', + 113 => 'application/x-font', + 114 => 'application/x-freemind', + 115 => 'application/x-futuresplash', + 116 => 'application/x-gnumeric', + 117 => 'application/x-go-sgf', + 118 => 'application/x-graphing-calculator', + 119 => 'application/x-gtar', + 120 => 'application/x-hdf', + 121 => 'application/x-httpd-eruby', + 122 => 'application/x-httpd-php', + 123 => 'application/x-httpd-php-source', + 124 => 'application/x-httpd-php3', + 125 => 'application/x-httpd-php3-preprocessed', + 126 => 'application/x-httpd-php4', + 127 => 'application/x-ica', + 128 => 'application/x-internet-signup', + 129 => 'application/x-iphone', + 130 => 'application/x-iso9660-image', + 131 => 'application/x-java-jnlp-file', + 132 => 'application/x-javascript', + 133 => 'application/x-jmol', + 134 => 'application/x-kchart', + 135 => 'application/x-killustrator', + 136 => 'application/x-koan', + 137 => 'application/x-kpresenter', + 138 => 'application/x-kspread', + 139 => 'application/x-kword', + 140 => 'application/x-latex', + 141 => 'application/x-lha', + 142 => 'application/x-lyx', + 143 => 'application/x-lzh', + 144 => 'application/x-lzx', + 145 => 'application/x-maker', + 146 => 'application/x-mif', + 147 => 'application/x-ms-wmd', + 148 => 'application/x-ms-wmz', + 149 => 'application/x-msdos-program', + 150 => 'application/x-msi', + 151 => 'application/x-netcdf', + 152 => 'application/x-ns-proxy-autoconfig', + 153 => 'application/x-nwc', + 154 => 'application/x-object', + 155 => 'application/x-oz-application', + 156 => 'application/x-pkcs7-certreqresp', + 157 => 'application/x-pkcs7-crl', + 158 => 'application/x-python-code', + 159 => 'application/x-quicktimeplayer', + 160 => 'application/x-redhat-package-manager', + 161 => 'application/x-shar', + 162 => 'application/x-shockwave-flash', + 163 => 'application/x-stuffit', + 164 => 'application/x-sv4cpio', + 165 => 'application/x-sv4crc', + 166 => 'application/x-tar', + 167 => 'application/x-tcl', + 168 => 'application/x-tex-gf', + 169 => 'application/x-tex-pk', + 170 => 'application/x-texinfo', + 171 => 'application/x-trash', + 172 => 'application/x-troff', + 173 => 'application/x-troff-man', + 174 => 'application/x-troff-me', + 175 => 'application/x-troff-ms', + 176 => 'application/x-ustar', + 177 => 'application/x-wais-source', + 178 => 'application/x-wingz', + 179 => 'application/x-x509-ca-cert', + 180 => 'application/x-xcf', + 181 => 'application/x-xfig', + 182 => 'application/x-xpinstall', + 183 => 'application/xhtml+xml', + 184 => 'application/xml', + 185 => 'application/zip', + 186 => 'audio/basic', + 187 => 'audio/midi', + 346 => 'audio/mp4', + 188 => 'audio/mpeg', + 189 => 'audio/ogg', + 190 => 'audio/prs.sid', + 191 => 'audio/x-aiff', + 192 => 'audio/x-gsm', + 193 => 'audio/x-mpegurl', + 194 => 'audio/x-ms-wax', + 195 => 'audio/x-ms-wma', + 196 => 'audio/x-pn-realaudio', + 197 => 'audio/x-realaudio', + 198 => 'audio/x-scpls', + 199 => 'audio/x-sd2', + 200 => 'audio/x-wav', + 201 => 'chemical/x-alchemy', + 202 => 'chemical/x-cache', + 203 => 'chemical/x-cache-csf', + 204 => 'chemical/x-cactvs-binary', + 205 => 'chemical/x-cdx', + 206 => 'chemical/x-cerius', + 207 => 'chemical/x-chem3d', + 208 => 'chemical/x-chemdraw', + 209 => 'chemical/x-cif', + 210 => 'chemical/x-cmdf', + 211 => 'chemical/x-cml', + 212 => 'chemical/x-compass', + 213 => 'chemical/x-crossfire', + 214 => 'chemical/x-csml', + 215 => 'chemical/x-ctx', + 216 => 'chemical/x-cxf', + 217 => 'chemical/x-embl-dl-nucleotide', + 218 => 'chemical/x-galactic-spc', + 219 => 'chemical/x-gamess-input', + 220 => 'chemical/x-gaussian-checkpoint', + 221 => 'chemical/x-gaussian-cube', + 222 => 'chemical/x-gaussian-input', + 223 => 'chemical/x-gaussian-log', + 224 => 'chemical/x-gcg8-sequence', + 225 => 'chemical/x-genbank', + 226 => 'chemical/x-hin', + 227 => 'chemical/x-isostar', + 228 => 'chemical/x-jcamp-dx', + 229 => 'chemical/x-kinemage', + 230 => 'chemical/x-macmolecule', + 231 => 'chemical/x-macromodel-input', + 232 => 'chemical/x-mdl-molfile', + 233 => 'chemical/x-mdl-rdfile', + 234 => 'chemical/x-mdl-rxnfile', + 235 => 'chemical/x-mdl-sdfile', + 236 => 'chemical/x-mdl-tgf', + 237 => 'chemical/x-mmcif', + 238 => 'chemical/x-mol2', + 239 => 'chemical/x-molconn-Z', + 240 => 'chemical/x-mopac-graph', + 241 => 'chemical/x-mopac-input', + 242 => 'chemical/x-mopac-out', + 243 => 'chemical/x-mopac-vib', + 244 => 'chemical/x-ncbi-asn1-ascii', + 245 => 'chemical/x-ncbi-asn1-binary', + 246 => 'chemical/x-ncbi-asn1-spec', + 247 => 'chemical/x-pdb', + 248 => 'chemical/x-rosdal', + 249 => 'chemical/x-swissprot', + 250 => 'chemical/x-vamas-iso14976', + 251 => 'chemical/x-vmd', + 252 => 'chemical/x-xtel', + 253 => 'chemical/x-xyz', + 254 => 'image/gif', + 255 => 'image/ief', + 256 => 'image/jpeg', + 257 => 'image/pcx', + 258 => 'image/png', + 259 => 'image/svg+xml', + 260 => 'image/tiff', + 261 => 'image/vnd.djvu', + 262 => 'image/vnd.microsoft.icon', + 263 => 'image/vnd.wap.wbmp', + 264 => 'image/x-cmu-raster', + 265 => 'image/x-coreldraw', + 266 => 'image/x-coreldrawpattern', + 267 => 'image/x-coreldrawtemplate', + 268 => 'image/x-corelphotopaint', + 269 => 'image/x-jg', + 270 => 'image/x-jng', + 271 => 'image/x-ms-bmp', + 272 => 'image/x-photoshop', + 273 => 'image/x-portable-anymap', + 274 => 'image/x-portable-bitmap', + 275 => 'image/x-portable-graymap', + 276 => 'image/x-portable-pixmap', + 277 => 'image/x-rgb', + 278 => 'image/x-xbitmap', + 279 => 'image/x-xpixmap', + 280 => 'image/x-xwindowdump', + 281 => 'message/rfc822', + 282 => 'model/iges', + 283 => 'model/mesh', + 284 => 'model/vrml', + 285 => 'text/calendar', + 286 => 'text/css', + 287 => 'text/csv', + 288 => 'text/h323', + 289 => 'text/html', + 290 => 'text/iuls', + 291 => 'text/mathml', + 292 => 'text/plain', + 293 => 'text/richtext', + 294 => 'text/scriptlet', + 295 => 'text/tab-separated-values', + 296 => 'text/texmacs', + 297 => 'text/vnd.sun.j2me.app-descriptor', + 298 => 'text/vnd.wap.wml', + 299 => 'text/vnd.wap.wmlscript', + 300 => 'text/x-bibtex', + 301 => 'text/x-boo', + 302 => 'text/x-c++hdr', + 303 => 'text/x-c++src', + 304 => 'text/x-chdr', + 305 => 'text/x-component', + 306 => 'text/x-csh', + 307 => 'text/x-csrc', + 308 => 'text/x-diff', + 309 => 'text/x-dsrc', + 310 => 'text/x-haskell', + 311 => 'text/x-java', + 312 => 'text/x-literate-haskell', + 313 => 'text/x-moc', + 314 => 'text/x-pascal', + 315 => 'text/x-pcs-gcd', + 316 => 'text/x-perl', + 317 => 'text/x-python', + 318 => 'text/x-setext', + 319 => 'text/x-sh', + 320 => 'text/x-tcl', + 321 => 'text/x-tex', + 322 => 'text/x-vcalendar', + 323 => 'text/x-vcard', + 324 => 'video/3gpp', + 325 => 'video/dl', + 326 => 'video/dv', + 327 => 'video/fli', + 328 => 'video/gl', + 329 => 'video/mp4', + 330 => 'video/mpeg', + 331 => 'video/ogg', + 332 => 'video/quicktime', + 333 => 'video/vnd.mpegurl', + 347 => 'video/x-flv', + 334 => 'video/x-la-asf', + 335 => 'video/x-mng', + 336 => 'video/x-ms-asf', + 337 => 'video/x-ms-wm', + 338 => 'video/x-ms-wmv', + 339 => 'video/x-ms-wmx', + 340 => 'video/x-ms-wvx', + 341 => 'video/x-msvideo', + 342 => 'video/x-sgi-movie', + 343 => 'x-conference/x-cooltalk', + 344 => 'x-epoc/x-sisx-app', + 345 => 'x-world/x-vrml', + ), + + // Extensions added to this list MUST be lower-case. + 'extensions' => array( + 'ez' => 0, + 'atom' => 1, + 'atomcat' => 2, + 'atomsrv' => 3, + 'cap' => 4, + 'pcap' => 4, + 'cu' => 5, + 'tsp' => 6, + 'hta' => 7, + 'jar' => 8, + 'ser' => 9, + 'class' => 10, + 'hqx' => 11, + 'nb' => 12, + 'mdb' => 13, + 'dot' => 14, + 'doc' => 14, + 'bin' => 15, + 'oda' => 16, + 'ogg' => 17, + 'ogx' => 17, + 'pdf' => 18, + 'key' => 19, + 'pgp' => 20, + 'prf' => 21, + 'eps' => 22, + 'ai' => 22, + 'ps' => 22, + 'rar' => 23, + 'rdf' => 24, + 'rss' => 25, + 'rtf' => 26, + 'smi' => 27, + 'smil' => 27, + 'cdy' => 28, + 'kml' => 29, + 'kmz' => 30, + 'xul' => 31, + 'xlb' => 32, + 'xlt' => 32, + 'xls' => 32, + 'xlam' => 33, + 'xlsb' => 34, + 'xlsm' => 35, + 'xltm' => 36, + 'cat' => 37, + 'stl' => 38, + 'pps' => 39, + 'ppt' => 39, + 'ppam' => 40, + 'pptm' => 41, + 'ppsm' => 42, + 'potm' => 43, + 'docm' => 44, + 'dotm' => 45, + 'xps' => 46, + 'odc' => 47, + 'odb' => 48, + 'odf' => 49, + 'odg' => 50, + 'otg' => 51, + 'odi' => 52, + 'odp' => 53, + 'otp' => 54, + 'ods' => 55, + 'ots' => 56, + 'odt' => 57, + 'odm' => 58, + 'ott' => 59, + 'oth' => 60, + 'pptx' => 61, + 'ppsx' => 62, + 'potx' => 63, + 'xlsx' => 64, + 'xltx' => 65, + 'docx' => 66, + 'dotx' => 67, + 'cod' => 68, + 'mmf' => 69, + 'sdc' => 70, + 'sds' => 71, + 'sda' => 72, + 'sdd' => 73, + 'sdw' => 75, + 'sgl' => 76, + 'sxc' => 77, + 'stc' => 78, + 'sxd' => 79, + 'std' => 80, + 'sxi' => 81, + 'sti' => 82, + 'sxm' => 83, + 'sxw' => 84, + 'sxg' => 85, + 'stw' => 86, + 'sis' => 87, + 'vsd' => 88, + 'wbxml' => 89, + 'wmlc' => 90, + 'wmlsc' => 91, + 'wpd' => 92, + 'wp5' => 93, + 'wk' => 94, + '7z' => 95, + 'abw' => 96, + 'dmg' => 97, + 'bcpio' => 98, + 'torrent' => 99, + 'cab' => 100, + 'cbr' => 101, + 'cbz' => 102, + 'cdf' => 103, + 'vcd' => 104, + 'pgn' => 105, + 'cpio' => 106, + 'udeb' => 107, + 'deb' => 107, + 'dir' => 108, + 'dxr' => 108, + 'dcr' => 108, + 'dms' => 109, + 'wad' => 110, + 'dvi' => 111, + 'flac' => 112, + 'pfa' => 113, + 'pfb' => 113, + 'pcf' => 113, + 'gsf' => 113, + 'pcf.z' => 113, + 'mm' => 114, + 'spl' => 115, + 'gnumeric' => 116, + 'sgf' => 117, + 'gcf' => 118, + 'taz' => 119, + 'gtar' => 119, + 'tgz' => 119, + 'hdf' => 120, + 'rhtml' => 121, + 'phtml' => 122, + 'pht' => 122, + 'php' => 122, + 'phps' => 123, + 'php3' => 124, + 'php3p' => 125, + 'php4' => 126, + 'ica' => 127, + 'ins' => 128, + 'isp' => 128, + 'iii' => 129, + 'iso' => 130, + 'jnlp' => 131, + 'js' => 132, + 'jmz' => 133, + 'chrt' => 134, + 'kil' => 135, + 'skp' => 136, + 'skd' => 136, + 'skm' => 136, + 'skt' => 136, + 'kpr' => 137, + 'kpt' => 137, + 'ksp' => 138, + 'kwd' => 139, + 'kwt' => 139, + 'latex' => 140, + 'lha' => 141, + 'lyx' => 142, + 'lzh' => 143, + 'lzx' => 144, + 'maker' => 145, + 'frm' => 145, + 'frame' => 145, + 'fm' => 145, + 'book' => 145, + 'fb' => 145, + 'fbdoc' => 145, + 'mif' => 146, + 'wmd' => 147, + 'wmz' => 148, + 'dll' => 149, + 'bat' => 149, + 'exe' => 149, + 'com' => 149, + 'msi' => 150, + 'nc' => 151, + 'pac' => 152, + 'nwc' => 153, + 'o' => 154, + 'oza' => 155, + 'p7r' => 156, + 'crl' => 157, + 'pyo' => 158, + 'pyc' => 158, + 'qtl' => 159, + 'rpm' => 160, + 'shar' => 161, + 'swf' => 162, + 'swfl' => 162, + 'sitx' => 163, + 'sit' => 163, + 'sv4cpio' => 164, + 'sv4crc' => 165, + 'tar' => 166, + 'gf' => 168, + 'pk' => 169, + 'texi' => 170, + 'texinfo' => 170, + 'sik' => 171, + '~' => 171, + 'bak' => 171, + '%' => 171, + 'old' => 171, + 't' => 172, + 'roff' => 172, + 'tr' => 172, + 'man' => 173, + 'me' => 174, + 'ms' => 175, + 'ustar' => 176, + 'src' => 177, + 'wz' => 178, + 'crt' => 179, + 'xcf' => 180, + 'fig' => 181, + 'xpi' => 182, + 'xht' => 183, + 'xhtml' => 183, + 'xml' => 184, + 'xsl' => 184, + 'zip' => 185, + 'au' => 186, + 'snd' => 186, + 'mid' => 187, + 'midi' => 187, + 'kar' => 187, + 'mpega' => 188, + 'mpga' => 188, + 'm4a' => 188, + 'mp3' => 188, + 'mp2' => 188, + 'oga' => 189, + 'spx' => 189, + 'sid' => 190, + 'aif' => 191, + 'aiff' => 191, + 'aifc' => 191, + 'gsm' => 192, + 'm3u' => 193, + 'wax' => 194, + 'wma' => 195, + 'rm' => 196, + 'ram' => 196, + 'ra' => 197, + 'pls' => 198, + 'sd2' => 199, + 'wav' => 200, + 'alc' => 201, + 'cac' => 202, + 'cache' => 202, + 'csf' => 203, + 'cascii' => 204, + 'cbin' => 204, + 'ctab' => 204, + 'cdx' => 205, + 'cer' => 206, + 'c3d' => 207, + 'chm' => 208, + 'cif' => 209, + 'cmdf' => 210, + 'cml' => 211, + 'cpa' => 212, + 'bsd' => 213, + 'csml' => 214, + 'csm' => 214, + 'ctx' => 215, + 'cxf' => 216, + 'cef' => 216, + 'emb' => 217, + 'embl' => 217, + 'spc' => 218, + 'gam' => 219, + 'inp' => 219, + 'gamin' => 219, + 'fchk' => 220, + 'fch' => 220, + 'cub' => 221, + 'gau' => 222, + 'gjf' => 222, + 'gjc' => 222, + 'gal' => 223, + 'gcg' => 224, + 'gen' => 225, + 'hin' => 226, + 'istr' => 227, + 'ist' => 227, + 'dx' => 228, + 'jdx' => 228, + 'kin' => 229, + 'mcm' => 230, + 'mmd' => 231, + 'mmod' => 231, + 'mol' => 232, + 'rd' => 233, + 'rxn' => 234, + 'sdf' => 235, + 'sd' => 235, + 'tgf' => 236, + 'mcif' => 237, + 'mol2' => 238, + 'b' => 239, + 'gpt' => 240, + 'mopcrt' => 241, + 'zmt' => 241, + 'mpc' => 241, + 'dat' => 241, + 'mop' => 241, + 'moo' => 242, + 'mvb' => 243, + 'prt' => 244, + 'aso' => 245, + 'val' => 245, + 'asn' => 246, + 'ent' => 247, + 'pdb' => 247, + 'ros' => 248, + 'sw' => 249, + 'vms' => 250, + 'vmd' => 251, + 'xtel' => 252, + 'xyz' => 253, + 'gif' => 254, + 'ief' => 255, + 'jpeg' => 256, + 'jpe' => 256, + 'jpg' => 256, + 'pcx' => 257, + 'png' => 258, + 'svgz' => 259, + 'svg' => 259, + 'tif' => 260, + 'tiff' => 260, + 'djvu' => 261, + 'djv' => 261, + 'ico' => 262, + 'wbmp' => 263, + 'ras' => 264, + 'cdr' => 265, + 'pat' => 266, + 'cdt' => 267, + 'cpt' => 268, + 'art' => 269, + 'jng' => 270, + 'bmp' => 271, + 'psd' => 272, + 'pnm' => 273, + 'pbm' => 274, + 'pgm' => 275, + 'ppm' => 276, + 'rgb' => 277, + 'xbm' => 278, + 'xpm' => 279, + 'xwd' => 280, + 'eml' => 281, + 'igs' => 282, + 'iges' => 282, + 'silo' => 283, + 'msh' => 283, + 'mesh' => 283, + 'icz' => 285, + 'ics' => 285, + 'css' => 286, + 'csv' => 287, + '323' => 288, + 'html' => 289, + 'htm' => 289, + 'shtml' => 289, + 'uls' => 290, + 'mml' => 291, + 'txt' => 292, + 'pot' => 292, + 'text' => 292, + 'asc' => 292, + 'rtx' => 293, + 'wsc' => 294, + 'sct' => 294, + 'tsv' => 295, + 'ts' => 296, + 'tm' => 296, + 'jad' => 297, + 'wml' => 298, + 'wmls' => 299, + 'bib' => 300, + 'boo' => 301, + 'hpp' => 302, + 'hh' => 302, + 'h++' => 302, + 'hxx' => 302, + 'cxx' => 303, + 'cc' => 303, + 'cpp' => 303, + 'c++' => 303, + 'h' => 304, + 'htc' => 305, + 'csh' => 306, + 'c' => 307, + 'patch' => 308, + 'diff' => 308, + 'd' => 309, + 'hs' => 310, + 'java' => 311, + 'lhs' => 312, + 'moc' => 313, + 'pas' => 314, + 'p' => 314, + 'gcd' => 315, + 'pm' => 316, + 'pl' => 316, + 'py' => 317, + 'etx' => 318, + 'sh' => 319, + 'tk' => 320, + 'tcl' => 320, + 'cls' => 321, + 'ltx' => 321, + 'sty' => 321, + 'tex' => 321, + 'vcs' => 322, + 'vcf' => 323, + '3gp' => 324, + 'dl' => 325, + 'dif' => 326, + 'dv' => 326, + 'fli' => 327, + 'gl' => 328, + 'mp4' => 329, + 'f4v' => 329, + 'f4p' => 329, + 'mpe' => 330, + 'mpeg' => 330, + 'mpg' => 330, + 'ogv' => 331, + 'qt' => 332, + 'mov' => 332, + 'mxu' => 333, + 'lsf' => 334, + 'lsx' => 334, + 'mng' => 335, + 'asx' => 336, + 'asf' => 336, + 'wm' => 337, + 'wmv' => 338, + 'wmx' => 339, + 'wvx' => 340, + 'avi' => 341, + 'movie' => 342, + 'ice' => 343, + 'sisx' => 344, + 'wrl' => 345, + 'vrm' => 345, + 'vrml' => 345, + 'f4a' => 346, + 'f4b' => 346, + 'flv' => 347, + ), + ); +} + +/** + * file_check_directory() doesn't support creating directory trees. + */ +function _cdn_basic_farfuture_create_directory_structure($path) { + // Create the directory structure in which the file will be stored. Because + // it's nested, file_check_directory() can't do this in one run. + $parts = explode('/', $path); + for ($i = 0; $i < count($parts); $i++) { + $directory = implode('/', array_slice($parts, 0, $i + 1)); + file_check_directory(file_create_path($directory), FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); + } +} diff --git cdn.module cdn.module index e5a018d..f2b205f 100644 --- cdn.module +++ cdn.module @@ -5,7 +5,6 @@ * Implementation of the core hooks, defines, public and private functions. */ - define('CDN_DISABLED', 0); define('CDN_TESTING', 1); define('CDN_ENABLED', 2); @@ -38,6 +37,16 @@ define('CDN_EXCEPTION_AUTH_USERS_BLACKLIST_DEFAULT', ''); // Variables for basic mode. define('CDN_BASIC_MAPPING_VARIABLE', 'cdn_basic_mapping'); +define('CDN_BASIC_FARFUTURE_VARIABLE', 'cdn_farfuture_status'); +define('CDN_BASIC_FARFUTURE_DEFAULT', TRUE); +define('CDN_BASIC_FARFUTURE_EXTENSIONS_VARIABLE', "cdn_farfuture_extensions"); +define('CDN_BASIC_FARFUTURE_EXTENSIONS_DEFAULT', "css\njs\nsvg\nico\ngif\njpg\njpeg\npng\notf\nttf\neot\nwoff\nflv\nswf"); +define('CDN_BASIC_FARFUTURE_GZIP_EXTENSIONS', "css\njs\nico\nsvg\neot\notf\nttf"); +define('CDN_BASIC_FARFUTURE_GZIP_DIRECTORY', "cdn/farfuture/gzip"); +define('CDN_BASIC_FARFUTURE_REMOVE_HEADERS', "Set-Cookie"); +define('CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_VARIABLE', 'cdn_farfuture_unique_identifier_mapping'); +define('CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_DEFAULT', "misc/*:modules/*:themes/*|drupal_version\nsites/*|mtime\nsites/*|.avi .m4v .mov .mp4 .wmv .flv|perpetual"); +define('CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_DEFAULT', 'mtime'); // Variables for advanced mode. define('CDN_ADVANCED_SYNCED_FILES_DB_VARIABLE', 'cdn_advanced_synced_files_db'); @@ -58,6 +67,7 @@ define('CDN_DAEMON_PID_FILE', 'daemon.pid'); function cdn_file_url_alter(&$path) { $status = variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED); $mode = variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC); + $farfuture = variable_get(CDN_BASIC_FARFUTURE_VARIABLE, CDN_BASIC_FARFUTURE_DEFAULT); $stats = variable_get(CDN_STATS_VARIABLE, FALSE) && user_access(CDN_PERM_ACCESS_STATS); $file_path_blacklist = variable_get(CDN_EXCEPTION_FILE_PATH_BLACKLIST_VARIABLE, CDN_EXCEPTION_FILE_PATH_BLACKLIST_DEFAULT); $file_path_whitelist = variable_get(CDN_EXCEPTION_FILE_PATH_WHITELIST_VARIABLE, CDN_EXCEPTION_FILE_PATH_WHITELIST_DEFAULT); @@ -89,7 +99,7 @@ function cdn_file_url_alter(&$path) { return; } } - + // If the current Drupal path matches one of the blacklisted Drupal paths, // return immediately. if (drupal_match_path($_GET['q'], $drupal_path_blacklist)) { @@ -106,6 +116,24 @@ function cdn_file_url_alter(&$path) { $start = microtime(); } + // Alter the file path when using Origin Pull mode and using that mode's + // Far Future setting. + if ($mode == CDN_MODE_BASIC && $farfuture) { + $path = urldecode($path); + // If the file does not yet exist, perform a normal HTTP request to this + // file, to generate it. (E.g. when ImageCache is used, this will + // generate the derivative file.) + if (!file_exists($path)) { + drupal_http_request(url($path, array('absolute' => TRUE))); + } + // Generate a unique file identifier (UFI). + cdn_load_include('basic.farfuture'); + $ufi = cdn_basic_farfuture_get_identifier($path); + // Generate the new path. + $path_before_farfuture = $path; + $path = "cdn/farfuture/$ufi/$path"; + } + // Load the include file that contains the logic for the mode that's // currently enabled. cdn_load_include(($mode == CDN_MODE_BASIC) ? 'basic' : 'advanced'); @@ -151,7 +179,8 @@ function cdn_file_url_alter(&$path) { // If the user can access it, add this to the per-page statistics. if ($stats) { $end = microtime(); - _cdn_devel_page_stats($path, $cdn_url, $server, $end - $start); + $source_path = ($mode == CDN_MODE_BASIC && $farfuture) ? $path_before_farfuture : $path; + _cdn_devel_page_stats($source_path, $cdn_url, $server, $end - $start); } // Override the path with the corresponding CDN URL, *if* the file is @@ -159,10 +188,75 @@ function cdn_file_url_alter(&$path) { if ($cdn_url !== FALSE) { $path = $cdn_url; } + else { + // If the file is not available on the CDN and Origin Pull mode's Far + // Future setting is enabled, then don't serve it using CDN module's + // non-scalable Far Future functionality, but using Drupal's default. + if ($mode == CDN_MODE_BASIC && $farfuture) { + $path = $path_before_farfuture; + } + } } } /** + * Implementation of hook_cdn_unique_file_identifier_info(). + */ +function cdn_cdn_unique_file_identifier_info() { + return array( + 'md5_hash' => array( + 'label' => t('MD5 hash'), + 'machine_name' => 'md5_hash', + 'prefix' => 'md5', + 'description' => t('MD5 hash of the file.'), + 'filesystem' => TRUE, + 'callback' => 'md5_file', + ), + 'mtime' => array( + 'label' => t('Last modification time'), + 'machine_name' => 'mtime', + 'prefix' => 'mtime', + 'description' => t('Last modification time of the file.'), + 'filesystem' => TRUE, + 'callback' => 'filemtime', + ), + 'perpetual' => array( + 'label' => t('Perpetual'), + 'machine_name' => 'perpetual', + 'prefix' => 'perpetual', + 'description' => t('Perpetual files never change (or are never cached + by the browser, e.g. video files).'), + 'filesystem' => FALSE, + 'value' => 'forever', + ), + 'drupal_version' => array( + 'label' => t('Drupal version'), + 'machine_name' => 'drupal_version', + 'prefix' => 'drupal', + 'description' => t('Drupal core version — this should only be applied + to files that ship with Drupal core.'), + 'filesystem' => FALSE, + 'value' => VERSION, + ), + 'deployment_id' => array( + 'label' => t('Deployment ID'), + 'machine_name' => 'deployment_id', + 'prefix' => 'deployment', + 'description' => t('A developer-defined deployment ID. Can be an + arbitrary string or number, as long as it uniquely + identifies deployments and therefore the affected + files.
+ Define this deployment ID in any enabled module or + in settings.php as the + CDN_DEPLOYMENT_ID + constant, and it will be picked up instantaneously.'), + 'filesystem' => FALSE, + 'callback' => '_cdn_ufi_deployment_id', + ), + ); +} + +/** * Implementation of hook_menu(). */ function cdn_menu() { @@ -214,6 +308,14 @@ function cdn_menu() { 'file' => 'cdn.stats.inc', ); + $items['cdn/farfuture'] = array( + 'title' => 'Download a far futured file', + 'access callback' => TRUE, + 'page callback' => 'cdn_basic_farfuture_download', + 'type' => MENU_CALLBACK, + 'file' => 'cdn.basic.farfuture.inc', + ); + return $items; } @@ -251,7 +353,6 @@ function cdn_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) { } } - /** * Implementation of hook_theme(). */ @@ -422,6 +523,16 @@ function cdn_load_include($basename) { // Private functions. /** + * Callback for generating a unique file identifier. + * + * @param $path + * The file path to the file for which to generate a unique identifier. + */ +function _cdn_ufi_deployment_id($path) { + return CDN_DEPLOYMENT_ID; +} + +/** * Helper function to evaluate CDN_PICK_SERVER_PHP_CODE_VARIABLE, when that is * being used instead of an actual cdn_pick_server() function. *