Only in cdn: .DS_Store
Only in cdn: .svn
diff -up /Users/bend/Downloads/cdn/README.txt cdn/README.txt
--- /Users/bend/Downloads/cdn/README.txt	2010-11-19 00:31:23.000000000 +0000
+++ cdn/README.txt	2011-03-03 17:20:36.000000000 +0000
@@ -176,7 +176,30 @@ Theme developers: avoid using base_path(
   base_path() . path_to_theme() .'/fix-ie.css"
 You should do this:
   file_create_url(path_to_theme() .'/fix-ie.css')
-
+  
+  
+Using object identifers for cache invalidation
+----------------------------------------------
+
+These need to be added to your .htaccess file:
+
+  #Allow for revisioned assets
+  RewriteRule ^cdn/([0-9a-z])*/(.*) /$2 [L]
+  
+  #Prevent people browsing the site via the CDN reference domains *origin.domain-name.com
+  #Allows files with acceptable files extensions or the files in the specified dirs (sites etc)
+  RewriteCond %{HTTP_HOST} origin.domain-name.com$ [NC]
+  RewriteCond %{REQUEST_URI} !^/(cdn/[0-9a-z]*/)?(sites|modules|misc|themes|external)(.*)
+  RewriteCond %{REQUEST_URI} !\.(css|js|gif|jpeg|jpg|png|xml|mp4|mov|ico|ttf|swf)$
+  RewriteRule ^(.*)$ / [NC,L,F]
+  
+  # Redirect non existing files back to the root domain
+  # This allows image cache to create the images which it will only do on the original domain
+  # Assumes that origin.* is a alias of the main domain
+  RewriteCond %{HTTP_HOST} origin.domain-name.com$ [NC]
+  RewriteCond %{SERVER_NAME} !origin.domain-name.com$ [NC]
+  RewriteCond %{REQUEST_FILENAME} !-f
+  RewriteRule ^(.*)$ http://%{SERVER_NAME}/$1 [P]
 
 When images generated by ImageCache appear to be broken
 -------------------------------------------------------
diff -up /Users/bend/Downloads/cdn/cdn.admin.inc cdn/cdn.admin.inc
--- /Users/bend/Downloads/cdn/cdn.admin.inc	2010-11-19 00:03:12.000000000 +0000
+++ cdn/cdn.admin.inc	2011-03-03 16:44:40.000000000 +0000
@@ -239,6 +239,91 @@ function cdn_admin_other_settings_form(&
     '#process'       => array('ctools_dependent_process'),
   );
 
+  $form['versioned_objects'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Versioned Objects'),
+    '#description' => t('Allows for objects to be versioned so they can be easily invalidated when updated'),
+  );
+  
+  $form['versioned_objects'][CDN_VERSIONED_OBJECTS_VARIABLE] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enable versioned objects'),
+    '#description' => t(
+      'Objects are verioned to allow for better cache object invalidation - requires .htaccess patch'
+    ),
+    '#default_value' => variable_get(CDN_VERSIONED_OBJECTS_VARIABLE, CDN_DISABLED),
+    '#process'       => array('ctools_dependent_process'),
+  );
+  
+  $identifiers = _cdn_get_cdn_object_version_identifiers();
+  
+  if(is_array($identifiers)){
+  
+    $form['versioned_objects']['identifiers'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Versioned Objects Identifiers'),
+      '#description' => t('Methods of generating unique identifiers which can be used in CDN invalidation. Enter the paths you\'d like each method to be used on'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE
+    );
+    
+    foreach ($identifiers as $identifier_id => $identifier){
+      $default_methods[$identifier_id] = $identifier['name'];
+    }
+    
+    $form['versioned_objects']['identifiers'][CDN_VERSIONED_OBJECTS_IDENTIFIER_DEFAULT] = array(
+      '#type' => 'radios',
+      '#title' => t('Default method'),
+      '#options' => $default_methods,
+      '#default_value' => variable_get(CDN_VERSIONED_OBJECTS_IDENTIFIER_DEFAULT, $default_methods_default),
+    );
+  
+    $identifier_count = 0;
+    foreach ($identifiers as $identifier_id => $identifier){
+        
+      $form['versioned_objects']['identifiers']['cdn_identifier_'.$identifier_id.'_fs'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('%name', 
+                     array('%name' => $identifier['name'])),
+        '#description' => t('Methods of generating unique identifiers which can be used in CDN invalidation. Enter the paths you\'d like each method to be used on'),
+        '#collapsible' => TRUE,
+        '#collapsed' => TRUE
+      );
+  
+      if (variable_get(CDN_VERSIONED_OBJECTS_IDENTIFIER_DEFAULT, $default_methods_default) == $identifier_id) $list_type = 'Blacklist';
+      else  $list_type = 'Whitelist';
+      
+      $form['versioned_objects']['identifiers']['cdn_identifier_'.$identifier_id.'_fs']['cdn_identifier_'.$identifier_id] = array(
+      
+        '#type' => 'textarea',
+        '#title' => t('%list_type: %name (%desc)', 
+                       array('%name' => $identifier['name'], 
+                             '%desc' => $identifier['desc'], 
+                             '%list_type' => $list_type)),
+        '#default_value' => variable_get('cdn_identifier_'.$identifier_id, ''),
+        '#description' => t("Enter one page per line as Drupal paths. The '*'
+                             character is a wildcard. Example paths are %blog for
+                             the blog page and %blog-wildcard for every personal
+                             blog. %front is the front page.",
+                             array(
+                               '%blog'          => 'blog',
+                               '%blog-wildcard' => 'blog/*',
+                               '%front'         => '<front>',
+                              ))
+      );
+      
+      $form['versioned_objects']['identifiers']['cdn_identifier_'.$identifier_id.'_fs']['cdn_identifier_'.$identifier_id.'_weight']= array(
+
+        '#type' => 'weight',
+        '#title' => t('%name Weight', array('%name' => $identifier['name'])), 
+        '#default_value' => variable_get('cdn_identifier_'.$identifier_id.'_weight', $identifier_count),
+        '#delta' => count($identifiers),
+      );
+      
+      $identifier_count--;
+    }
+  }
+
   $path_explanation = t(
     "Enter one file pattern per line. The '*' character is a wildcard.<br />
     Example file patterns are <code>*.js</code> for all JavaScript files and
@@ -318,6 +403,21 @@ function cdn_admin_other_settings_form(&
                           )
      ),
   );
+  $form['exceptions']['drupal_path'][CDN_EXCEPTION_DRUPAL_PATH_WHITELIST_VARIABLE] = array(
+    '#type' => 'textarea',
+    '#title' => t('Whitelist'),
+    '#default_value' => variable_get(CDN_EXCEPTION_DRUPAL_PATH_WHITELIST_VARIABLE, CDN_EXCEPTION_DRUPAL_PATH_WHITELIST_DEFAULT),
+    '#description' => t("Enter one page per line as Drupal paths. The '*'
+                         character is a wildcard. Example paths are %blog for
+                         the blog page and %blog-wildcard for every personal
+                         blog. %front is the front page.",
+                         array(
+                           '%blog'          => 'blog',
+                           '%blog-wildcard' => 'blog/*',
+                           '%front'         => '<front>',
+                          )
+     ),
+  );
 
   $form['exceptions']['auth_users'] = array(
     '#type' => 'fieldset',
diff -up /Users/bend/Downloads/cdn/cdn.module cdn/cdn.module
--- /Users/bend/Downloads/cdn/cdn.module	2010-11-23 14:44:59.000000000 +0000
+++ cdn/cdn.module	2011-03-03 16:41:45.000000000 +0000
@@ -27,13 +27,20 @@ define('CDN_HTTPS_SUPPORT_VARIABLE', 'cd
 define('CDN_THEME_LAYER_FALLBACK_VARIABLE', 'cdn_theme_layer_fallback');
 define('CDN_PICK_SERVER_PHP_CODE_VARIABLE', 'CDN_PICK_SERVER_PHP_CODE_VARIABLE');
 
+// Variables for versioned objects
+define('CDN_VERSIONED_OBJECTS_VARIABLE', 'cdn_versioned_objects');
+define('CDN_VERSIONED_OBJECTS_IDENTIFIER_DEFAULT', 'cdn_identifier_default');
+define('CDN_VERSIONED_OBJECTS_IDENTIFIER_DEFAULT_DEFAULT', 'md5_file');
+
 // Variables for exceptions.
 define('CDN_EXCEPTION_FILE_PATH_BLACKLIST_VARIABLE', 'cdn_exception_file_path_blacklist');
 define('CDN_EXCEPTION_FILE_PATH_BLACKLIST_DEFAULT',  "*.js\n*/image_captcha/*");
 define('CDN_EXCEPTION_FILE_PATH_WHITELIST_VARIABLE', 'cdn_exception_file_path_whitelist');
 define('CDN_EXCEPTION_FILE_PATH_WHITELIST_DEFAULT',  "misc/*.js\nmodules/block/block.js\nmodules/color/color.js\nmodules/comment/comment.js\nmodules/openid/openid.js\nmodules/profile/profile.js\nmodules/system/system.js\nmodules/taxonomy/taxonomy.js\nmodules/user/user.js\n*/admin_menu/admin_menu.js");
 define('CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_VARIABLE', 'cdn_exception_drupal_path_blacklist');
+define('CDN_EXCEPTION_DRUPAL_PATH_WHITELIST_VARIABLE', 'cdn_exception_drupal_path_whitelist');
 define('CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_DEFAULT',  'admin*');
+define('CDN_EXCEPTION_DRUPAL_PATH_WHITELIST_DEFAULT',  '');
 define('CDN_EXCEPTION_AUTH_USERS_BLACKLIST_VARIABLE', 'cdn_exception_auth_users_blacklist');
 define('CDN_EXCEPTION_AUTH_USERS_BLACKLIST_DEFAULT', '');
 
@@ -60,18 +67,23 @@ function cdn_file_url_alter(&$path) {
   $status                = variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED);
   $mode                  = variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC);
   $stats                 = variable_get(CDN_STATS_VARIABLE, FALSE) && user_access(CDN_PERM_ACCESS_STATS);
+  
+  $versioned_objects          = variable_get(CDN_VERSIONED_OBJECTS_VARIABLE, FALSE);
+  $versioned_objects_default  = variable_get(CDN_VERSIONED_OBJECTS_IDENTIFIER_DEFAULT, CDN_VERSIONED_OBJECTS_IDENTIFIER_DEFAULT_DEFAULT);
+
   $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);
   $drupal_path_blacklist = variable_get(CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_VARIABLE, CDN_EXCEPTION_DRUPAL_PATH_BLACKLIST_DEFAULT);
+  $drupal_path_whitelist = variable_get(CDN_EXCEPTION_DRUPAL_PATH_WHITELIST_VARIABLE, CDN_EXCEPTION_DRUPAL_PATH_WHITELIST_DEFAULT);
   $auth_users_blacklist  = variable_get(CDN_EXCEPTION_AUTH_USERS_BLACKLIST_VARIABLE, CDN_EXCEPTION_AUTH_USERS_BLACKLIST_DEFAULT);
   $https_support         = variable_get(CDN_HTTPS_SUPPORT_VARIABLE, FALSE);
   $is_https_page         = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'));
   global $user;
 
   if ($status == CDN_ENABLED || ($status == CDN_TESTING && user_access(CDN_PERM_ACCESS_TESTING))) {
-    // If the current path is an absolute path, return immediately.
-    $fragments = parse_url($path);
-    if (isset($fragments['host'])) {
+
+    // If the current path is a URL, return immediately.
+    if (filter_var($path, FILTER_VALIDATE_URL) !== FALSE) {
       return;
     }
 
@@ -89,11 +101,11 @@ function cdn_file_url_alter(&$path) {
       if (!drupal_match_path($path, $file_path_whitelist)) {
         return;
       }
-    }
-  
+    }    
+
     // If the current Drupal path matches one of the blacklisted Drupal paths,
     // return immediately.
-    if (drupal_match_path($_GET['q'], $drupal_path_blacklist)) {
+    if (drupal_match_path($_GET['q'], $drupal_path_blacklist) && ! drupal_match_path($_GET['q'], $drupal_path_whitelist)) {
       return;
     }
 
@@ -106,6 +118,44 @@ function cdn_file_url_alter(&$path) {
       cdn_load_include('stats');
       $start = microtime();
     }
+    
+    //If objects are being versioned, this allows long expiry objects to be invalidated easily
+    if($versioned_objects){
+    
+      $identifiers = _cdn_get_cdn_object_version_identifiers();
+      
+      if(is_array($identifiers)){
+      
+        foreach ($identifiers as $identifier_id => $identifier){
+        
+          //Don't try the default provider until all others have been tried
+          if ($identifier_id == $versioned_objects_default) continue;
+        
+          //Test to see if it's in the whitelist for this provider
+          if (drupal_match_path($path, variable_get('cdn_identifier_'.$identifier_id, ''))) {
+           
+            $cdn_file_identifier = _cdn_get_svn_object_version_get_identifier($identifier, $path);
+            break;
+          }
+        }
+        
+        //if none of the non-default providers have yielded an identifier, try the default
+        //Assuming it's not in the default provider's blacklist
+        if (!$cdn_file_identifier){
+        
+          //Test to see if it's in the blacklist for the default provider
+          if (!drupal_match_path($path, variable_get('cdn_identifier_'.$versioned_objects_default, ''))) {
+        
+            $cdn_file_identifier = _cdn_get_svn_object_version_get_identifier($identifiers[$versioned_objects_default], $path);
+          }
+        }
+      }
+      
+      //Apply the identifier to the path
+      if($cdn_file_identifier){
+        $path = 'cdn/'.$cdn_file_identifier.'/'.$path;
+      }    
+    }
 
     // Load the include file that contains the logic for the mode that's
     // currently enabled.
@@ -439,3 +489,96 @@ function _cdn_eval_pick_server($php_code
   ob_end_clean();
   return $result;
 }
+
+/**
+ * Helper function to get all identifiers, both the default one and those provided in other modules
+ * Identifiers are ways of determining changes to files based on a range of factors
+ */
+function _cdn_get_cdn_object_version_identifiers(){
+
+  //Invoke hook to get identifiers from other modules
+  $identifiers = module_invoke_all('cdn_object_version_identifier');
+
+  $identifiers['md5_file'] = array(
+    'name'   => 'MD5 File Hash',
+    'desc'   => 'MD5 hash of the asset (using md5_file)',
+    'type'   => 'function', //Identifiers can either produce a string or pass the file path to a function
+    'value'  => '_cdn_md5_file',//Name of function to pass it to
+    'prefix' => 'md5', //These are added to the path to easily show the identifer acting on a file
+    'default'=> TRUE //The default identifier is applied last
+  );
+  
+  $identifiers['filemtime'] = array(
+    'name'   => 'Modification time',
+    'desc'   => 'Modification time (using filemtime)',
+    'type'   => 'function',
+    'value'  => 'filemtime',
+    'prefix' => 'mtime',
+    'default'=> FALSE
+  );
+                                    
+  $identifiers['drupal_version'] = array( 
+    'name'   => 'Version of Drupal',
+    'desc'   => 'Uses the version of Drupal to allow rarely changed system files to be identified',
+    'type'   => 'string',
+    'value'  => preg_replace('/[^0-9a-z]/','', VERSION),
+    'prefix' => 'drupal',
+  );
+                  
+  //For all the identifiers apply the order as defined in the configuration page
+  if (is_array($identifiers) && count($identifiers) > 0){
+  
+    foreach ($identifiers as $identifier_id => $identifier){
+      $identifiers[$identifier_id]['#weight'] = variable_get('cdn_identifier_'.$identifier_id.'_weight', 0);
+    }
+    
+    //Sort based on wieght, Identifiers are then applied in this order
+    uasort(&$identifiers, "element_sort");
+    
+    return $identifiers;
+    
+  } else return FALSE;
+}
+
+/**
+ * Function to provide MD5 hashes of files
+ *
+ * @param $filepath
+ *   The filepath of the object
+ */
+function _cdn_md5_file($filepath=FALSE){
+
+  //In the case that the files does not yet exist (in the case of Imagecache etc)
+  //The 404 identifer is use for the first display and will be replaced on the next build
+  if ($filepath == FALSE || !file_exists($filepath)) return '404';
+  else return @md5_file($filepath);
+}
+
+/**
+ * Helper function to apply the identifers to filepaths and return the changed filepath
+ *
+ * @param $identifier
+ *   The identifier to use
+ * @param $filepath
+ *   The filepath of the object to act upon
+ */
+function _cdn_get_svn_object_version_get_identifier($identifier, $filepath){
+
+  switch($identifier['type']){
+            
+    case 'string':
+    
+      $cdn_file_identifier = $identifier['value'];
+      break;
+    
+    case 'function':
+    
+      $cdn_file_identifier = call_user_func_array($identifier['value'], array($filepath));
+      break;
+  }
+  
+  if (!empty($identifier['prefix'])) $cdn_file_identifier = $identifier['prefix'].$cdn_file_identifier;
+    
+  if (isset($cdn_file_identifier)) return $cdn_file_identifier;
+  else return FALSE;
+}
Common subdirectories: /Users/bend/Downloads/cdn/patches and cdn/patches
