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.+ 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.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.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.
*
diff --git cdn.basic.farfuture.inc cdn.basic.farfuture.inc
new file mode 100644
index 0000000..6df4369
--- /dev/null
+++ cdn.basic.farfuture.inc
@@ -0,0 +1,418 @@
+=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!
+ // However, specificity also matters. The directory pattern "foo/bar/*"
+ // should *always* override the less specific pattern "foo/*".
+ $ufi_method = FALSE;
+ $current_specificity = 0;
+ 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.
+ $extension = NULL;
+ if (array_key_exists($file_extension, $mapping[$directory])) {
+ $key = $file_extension;
+ }
+ elseif (array_key_exists('*', $mapping[$directory])) {
+ $extension = '*';
+ }
+
+ // If a matching extension was found, assign the corresponding UFI method.
+ if (isset($extension)) {
+ $specificity = $mapping[$directory][$extension]['specificity'];
+ if ($specificity > $current_specificity) {
+ $ufi_method = $mapping[$directory][$extension]['ufi method'];
+ $current_specificity = $specificity;
+ }
+ }
+ }
+ }
+
+ // 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]);
+ $specificity = 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) {
+ $directory_specificity = 10 * count(split('/', $directory));
+ foreach ($extensions as $extension) {
+ $extension_specificity = ($extension == '*') ? 0 : 1;
+
+ $mapping[$directory][$extension] = array(
+ 'ufi method' => $ufi_method,
+ 'specificity' => $directory_specificity + $extension_specificity,
+ );
+ }
+ }
+ }
+ }
+
+ 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.
+ */
+function _cdn_basic_farfuture_get_mimetype($path) {
+ cdn_load_include('mimetypes');
+ $mapping = cdn_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';
+}
+
+/**
+ * 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.mimetypes.inc cdn.mimetypes.inc
new file mode 100644
index 0000000..d786bef
--- /dev/null
+++ cdn.mimetypes.inc
@@ -0,0 +1,862 @@
+ 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,
+ '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,
+ 'ogg' => 189,
+ '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,
+ ),
+ );
+}