--- redirect/redirect.admin.inc.ORIGINAL	2011-08-23 23:01:09.000000000 +0200
+++ redirect/redirect.admin.inc			2011-09-15 10:45:33.000000000 +0200
@@ -509,11 +509,16 @@ function redirect_edit_form_validate($fo
   $redirect = (object) $form_state['values'];
 
   if (empty($form_state['values']['override'])) {
-    if ($existing = redirect_load_by_source($redirect->source, $redirect->language)) {
-      if ($redirect->rid != $existing->rid && $redirect->language == $existing->language) {
-        // The "from" path should not conflict with another redirect
-        $form_state['storage']['override_messages']['redirect-conflict'] = t('The base source path %source is already being redirected. Do you want to <a href="@edit-page">edit the existing redirect</a>?', array('%source' => $redirect->source, '@edit-page' => url('admin/config/search/redirect/edit/'. $existing->rid)));
-        $form_state['rebuild'] = TRUE;
+    if ($redirects = (variable_get('redirect_use_wildcards', 0) == 1) ?
+        redirect_load_by_source($redirect->source, $redirect->language, TRUE) :
+        redirect_load_by_source($redirect->source, $redirect->language)) {
+
+      foreach ($redirects as $existing) {
+        if ($redirect->rid != $existing->rid && $redirect->language == $existing->language) {
+          // The "from" path should not conflict with another redirect
+          $form_state['storage']['override_messages']['redirect-conflict'] = t('The base source path %source is already being redirected. Do you want to <a href="@edit-page">edit the existing redirect</a>?', array('%source' => $redirect->source, '@edit-page' => url('admin/config/search/redirect/edit/'. $existing->rid)));
+          $form_state['rebuild'] = TRUE;
+        }
       }
     }
 
@@ -613,6 +618,12 @@ function redirect_settings_form($form, &
     '#description' => t('This feature requires <a href="@performance">Cache pages for anonymous users</a> to be enabled and the %variable variable to be TRUE.', array('@performance' => url('admin/config/development/performance'), '%variable' => "\$conf['page_cache_invoke_hooks']")),
     '#disabled' => !variable_get('cache', 0) || !variable_get('page_cache_invoke_hooks', TRUE),
   );
+  $form['redirect_use_wildcards'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enable use of wildcards (*) in redirects ("From:" and "To:" fields).'),
+    '#description' => t('<b>Check redirects carefully if you disable this option</b> once it has been used already, as <b>all redirects with wildcards will stop working</b>.'),
+    '#default_value' => variable_get('redirect_use_wildcards', 0),
+  );
   $form['redirect_purge_inactive'] = array(
     '#type' => 'select',
     '#title' => t('Delete redirects that have not been accessed for'),
--- redirect/redirect.module.ORIGINAL		2011-08-23 23:01:09.000000000 +0200
+++ redirect/redirect.module			2011-09-15 10:49:28.000000000 +0200
@@ -251,7 +251,148 @@ function redirect_init() {
   $current_path = current_path();
   $current_langcode = $GLOBALS['language']->language;
   $current_query = drupal_get_query_parameters();
-  if ($redirect = redirect_load_by_source($current_path, $current_langcode, $current_query)) {
+
+  $redirect = '';
+
+  if (variable_get('redirect_use_wildcards', 0) == 1) {
+    // Add all redirects with wildcards to a check
+    // Note that redirects array may contain a 'clear' (w/o wildcards) redirect as well
+    // All redirects (incl. a 'clear' one) are handled in the order they were entered
+    // and the first one that match is used
+
+    $redirects = redirect_load_by_source($current_path, $current_langcode,
+                                         TRUE, $current_query);
+
+    // current_path() returns a path without a trailing slash (even in the case it was
+    // submitted); this can cause improper wildcard matches.
+    // Example: Redirect From = '<something>/*'
+    //     URL: '<something>/' should match, but as '<something>' it wouldn't
+
+    $request_uri = request_uri();
+    if (substr($request_uri, -1) == '/' || strpos($request_uri, '/?')) $current_path .= '/';
+
+    if (!is_array($redirects)) $redirects = array($redirects);
+
+    $matched_redirects = array();
+
+    foreach ($redirects as $current_redirect) {
+      // We don't have a match at the beginning:
+      $error = 10;
+      $weight = 0;
+
+      if ($current_redirect) {
+        // Extract 'source' and 'redirect' parts from a $current_redirect:
+        $red_source = $current_redirect->source;
+        $red_source_options = $current_redirect->source_options;
+        $red_redirect = $current_redirect->redirect;
+        $red_redirect_options = $current_redirect->redirect_options;
+
+        $red_to = '';
+
+        if ($red_source_options || $red_redirect_options) {
+          // A "complete match" case - source or redirect options are specified
+          // We can not simply pass options received, but have to compare them 
+          // with redirects to match a complete URLs
+
+          $current_query_tmp = array('query' => $current_query);
+
+          // Merge (and clear) any options received to a path received:
+          if ($current_query_tmp) {
+            $current_path = redirect_url($current_path, $current_query_tmp);
+            unset($current_query_tmp);
+          }
+
+          // Merge (and clear) any source options to a source path:
+          if ($red_source_options) {
+            $red_source = redirect_url($red_source, $red_source_options);
+            unset($red_source_options);
+          }
+
+          // Merge (and clear) any redirecte options to a redirect path:
+          if ($red_redirect_options != '') {
+            $red_redirect = redirect_url($red_redirect, $red_redirect_options);
+            unset($red_redirect_options);
+          }
+
+          if (substr_count($red_source, '*') > 0) {
+            // We have wildcards in 'Redirect From'; check for a match...
+              $weight_tmp = 0;
+              $error = redirect_match_wildcards(
+                         $current_path, $red_source, $red_redirect, $red_to, $weight_tmp);
+              if ($error == 0) $weight += $weight_tmp;
+          }
+          elseif ($current_path == $red_source) {
+            // No wildcards in 'Redirect From' - Path shall be equal to 'Redirect From'...
+            $redirect = $current_redirect;
+            $error = 0;
+            $weight = 0;
+          }
+        }
+        else {
+          // A "partial match" case - no source or redirect options
+          // We simply compare paths only, and pass any options received to a matched redirect
+
+          $weight += strlen(drupal_http_build_query($current_query)) + 1;
+
+          if (substr_count($red_source, '*') > 0) {
+            // We have wildcards in 'Redirect From'; check for a match...
+              $weight_tmp = 0;
+              $error = redirect_match_wildcards(
+                         $current_path, $red_source, $red_redirect, $red_to, $weight_tmp);
+              if ($error == 0) $weight += 1000 * $weight_tmp;
+          }
+          elseif ($current_path == $red_source) {
+            // No wildcards in 'Redirect From' - Path shall be equal to 'Redirect From'...
+            $redirect = $current_redirect;
+            $error = 0;
+            $weight = 0;
+          }
+        }
+
+        if ($error == 0 && $red_to != '') {
+          // We have a new destination through a wildcard match(es):
+          $redirect = $current_redirect;
+
+//          $redirect->source = current_path();
+//          $redirect->source_options = array('query' => $current_query);
+
+          $redirect_tmp = redirect_parse_url($red_to);
+
+          $redirect->redirect = (array_key_exists('path', $redirect_tmp)) ?
+                                $redirect_tmp['path'] : '';
+
+          $red_redirect_options = array();
+          if (array_key_exists('query', $redirect_tmp))
+            $red_redirect_options['query'] = $redirect_tmp['query'];
+          if (array_key_exists('fragment', $redirect_tmp))
+            $red_redirect_options['fragment'] = $redirect_tmp['fragment'];
+          $redirect->redirect_options = $red_redirect_options;
+        }
+
+        if ($error == 0)
+          $matched_redirects[] = array('weight' => $weight, 'redirect' => $redirect);
+      }
+    }
+
+    if (!empty($matched_redirects)) {
+      foreach ($matched_redirects as $mr_key => $mr_val)
+        $mr_tmp[$mr_key] = $mr_val['weight'];
+      asort($mr_tmp, SORT_NUMERIC);
+      foreach ($mr_tmp as $mr_key => $mr_val)
+        $matched_redirects_sorted[] = $matched_redirects[$mr_key];
+      $matched_redirects = $matched_redirects_sorted;
+    }
+
+    if (!empty($matched_redirects))
+      $redirect = $matched_redirects[0]['redirect'];
+  }
+  else {
+    $redirects = redirect_load_by_source($current_path, $current_langcode,
+                                         FALSE, $current_query);
+    $redirect = $redirects[0];
+  }
+
+  if ($redirect != '') {
     redirect_redirect($redirect);
   }
 
@@ -403,7 +544,7 @@ function redirect_page_build(&$page) {
  * Adds support for creating redirects if a node URL alias is changed.
  */
 function redirect_form_node_form_alter(&$form, $form_state) {
-  if (!empty($form['path']['pid']['#value']) && !isset($form['path']['original'])) {
+  if (!empty($form['path']['pid']['#valueTemp.']) && !isset($form['path']['original'])) {
     $form['path']['original'] = array('#type' => 'value', '#value' => path_load($form['path']['pid']['#value']));
   }
 }
@@ -481,47 +622,76 @@ function redirect_load_by_hash($hash, $r
  *
  * @ingroup redirect_api
  */
-function redirect_load_by_source($source, $language = LANGUAGE_NONE, array $query = array()) {
+function redirect_load_by_source($source, $language = LANGUAGE_NONE, $with_wildcards = FALSE, array $query = array()) {
   // Run a case-insensitive query for matching RIDs first.
   $rid_query = db_select('redirect');
   $rid_query->addField('redirect', 'rid');
-  if ($source != variable_get('site_frontpage', 'node')) {
-    $rid_query->condition('source', db_like($source), 'LIKE');
+  if ($with_wildcards) {
+    if ($source != variable_get('site_frontpage', 'node')) {
+      $source_condition = db_or();
+      $source_condition->condition('source', db_like($source), 'LIKE');
+      $source_condition->where('instr(source, \'*\')');
+      $source_condition->where('instr(source_options, \'*\')');
+      $rid_query->condition($source_condition);
+    }
+    else {
+      $source_condition = db_or();
+      $source_condition->condition('source', db_like($source), 'LIKE');
+      $source_condition->condition('source', '');
+      $source_condition->where('instr(source, \'*\')');
+      $source_condition->where('instr(source_options, \'*\')');
+      $rid_query->condition($source_condition);
+    }
   }
   else {
-    $source_condition = db_or();
-    $source_condition->condition('source', db_like($source), 'LIKE');
-    $source_condition->condition('source', '');
-    $rid_query->condition($source_condition);
+    if ($source != variable_get('site_frontpage', 'node')) {
+      $rid_query->condition('source', db_like($source), 'LIKE');
+    }
+    else {
+      $source_condition = db_or();
+      $source_condition->condition('source', db_like($source), 'LIKE');
+      $source_condition->condition('source', '');
+      $rid_query->condition($source_condition);
+    }
   }
   $rid_query->condition('language', array($language, LANGUAGE_NONE));
   $rids = $rid_query->execute()->fetchCol();
 
   if ($rids && $redirects = redirect_load_multiple($rids)) {
-    // Narrow down the list of candidates.
-    foreach ($redirects as $rid => $redirect) {
-      if (!empty($redirect->source_options['query'])) {
-        if (empty($query) || !redirect_compare_array_recursive($redirect->source_options['query'], $query)) {
-          unset($redirects[$rid]);
-          continue;
+    if (!$with_wildcards) {
+      // Narrow down the list of candidates.
+      foreach ($redirects as $rid => $redirect) {
+        if (!empty($redirect->source_options['query'])) {
+          if (empty($query) || !redirect_compare_array_recursive($redirect->source_options['query'], $query)) {
+            unset($redirects[$rid]);
+            continue;
+          }
+        }
+
+        // Add a case sensitive matches condition to be used in sorting.
+        if ($source !== $redirect->source) {
+          $redirects[$rid]->weight = 1;
         }
       }
 
-      // Add a case sensitive matches condition to be used in sorting.
-      if ($source !== $redirect->source) {
-        $redirects[$rid]->weight = 1;
+      if (!empty($redirects)) {
+        // Sort the redirects in the proper order.
+        uasort($redirects, '_redirect_uasort');
+
+        // Allow other modules to alter the redirect candidates before selecting the top one.
+        $context = array('language' => $language, 'query' => $query);
+        drupal_alter('redirect_load_by_source', $redirects, $source, $context);
+
+        return !empty($redirects) ? reset($redirects) : FALSE;
       }
     }
+    else {
+      $returns = array();
+      foreach ($redirects as $rid => $redirect) {
+        array_push($returns, $redirect);
+      }
 
-    if (!empty($redirects)) {
-      // Sort the redirects in the proper order.
-      uasort($redirects, '_redirect_uasort');
-
-      // Allow other modules to alter the redirect candidates before selecting the top one.
-      $context = array('language' => $language, 'query' => $query);
-      drupal_alter('redirect_load_by_source', $redirects, $source, $context);
-
-      return !empty($redirects) ? reset($redirects) : FALSE;
+      return !empty($returns) ? $returns : FALSE;
     }
   }
 
@@ -671,6 +841,14 @@ function redirect_object_prepare($redire
 function redirect_save($redirect) {
   $transaction = db_transaction();
 
+  $check = $redirect;
+  redirect_hash($check);
+  $existing = redirect_load_by_hash($check->hash);
+
+  if ($existing && !(variable_get('redirect_use_wildcards', 0) == 1)) {
+    form_set_error('source', t('The source path %source is already being redirected. Do you want to <a href="@edit-page">edit the existing redirect</a>?', array('%source' => redirect_url($redirect->source, $redirect->source_options), '@edit-page' => url('admin/config/search/redirect/edit/'. $existing->rid))));
+  } else
+
   try {
     if (!empty($redirect->rid) && !isset($redirect->original)) {
       $redirect->original = entity_load_unchanged('redirect', $redirect->rid);
@@ -923,7 +1101,8 @@ function redirect_redirect($redirect = N
     $redirect->status_code = variable_get('redirect_default_status_code', 301);
   }
 
-  if (variable_get('redirect_passthrough_querystring', 1)) {
+  if (variable_get('redirect_passthrough_querystring', 1)
+      && !(variable_get('redirect_use_wildcards', 0) && !empty($redirect->source_options))) {
     // Preserve the current query parameters in the redirect.
     $redirect->redirect_options += array('query' => array());
     $redirect->redirect_options['query'] += drupal_get_query_parameters();
@@ -1211,7 +1390,7 @@ function redirect_url($path, array $opti
 
   if (isset($options['query'])) {
     $url .= $clean_url ? '?' : '&';
-    $url .= drupal_http_build_query($options['query']);
+    $url .= str_replace('%2A', '*', drupal_http_build_query($options['query']));
   }
   if (isset($options['fragment'])) {
     $url .= '#' . $options['fragment'];
@@ -1232,6 +1411,7 @@ function redirect_variables() {
     'redirect_global_clean' => 1,
     'redirect_global_canonical' => 1,
     'redirect_global_admin_paths' => 0,
+    'redirect_use_wildcards' => 0,
   );
 }
 
@@ -1254,7 +1434,8 @@ function redirect_variables() {
 
 function redirect_parse_url($url) {
   $original_url = $url;
-  $url = trim($url, " \t\n\r\0\x0B\/");
+  $url = (variable_get('redirect_use_wildcards', 0) == 1) ?
+         trim($url, " \t\n\r\0\x0B") : trim($url, " \t\n\r\0\x0B\/");
   $parsed = parse_url($url);
 
   if (isset($parsed['fragment'])) {
@@ -1280,7 +1461,8 @@ function redirect_parse_url($url) {
     }
   }
 
-  $url = trim($url, '/');
+  if (variable_get('redirect_use_wildcards', 0) != 1)
+    $url = trim($url, '/');
 
   // Convert to frontpage paths.
   if ($url == '<front>') {
@@ -1484,3 +1666,192 @@ function redirect_redirect_operations() 
   );
   return $operations;
 }
+
+
+function redirect_match_wildcards($in, $in_fmt, $out_fmt, &$out, &$weight) {
+// -------------------------------------------------------------------------------------
+// Function parses input string ($in) according to input format string ($in_fmt), and
+// replaces corresponding wildcard characters (*) in output format string ($out_fmt)
+// with the first possible matches from input string ($in); if there are more wildcard
+// characters in input format string ($in_fmt) as in output format string ($out_fmt),
+// only replacements for the first matching ones are used to calculate the output string
+// -------------------------------------------------------------------------------------
+// Function returns calculated string setting the $out argument
+// -------------------------------------------------------------------------------------
+// Function returns the error code:
+// 0 => OK,
+// 1 => wildcard error,
+// > 1 => input string ($in) doesn't match input format string ($in_fmt)
+// -------------------------------------------------------------------------------------
+
+  $out = '';
+  $error = 0;
+  $weight = 0;
+
+  // Wildcards should not be repeated in $in_fmt (ambiguity)
+  while (substr_count($in_fmt, '**') > 0) {
+    $in_fmt = str_replace('**', '*', $in_fmt);
+  }
+
+  $wcif = substr_count($in_fmt, '*');
+  $wcof = substr_count($out_fmt, '*');
+
+  if ($wcif < $wcof) {
+    // ERROR #1: The number of wildcards in $in_fmt shall not
+    // be smaller than the number of wildcards in $out_fmt (as
+    // some of them could not be resolved in such a case):
+    $out = '';
+    $error = 1;
+  }
+
+  if ($error == 0) {
+
+    $weight_multiplier = 1000;
+
+    if ($wcif > 0) {
+    // There is at least one wildcard in $in_fmt:
+      $wpif = strpos($in_fmt, '*');
+
+      $query_begin = strpos($in_fmt, '?'); 
+      if ($query_begin > $wpif) $weight_multiplier = 1;
+
+      if ($wpif > 0) {
+        // If there is anything left to the first wildcard character in $in_fmt, the
+        // same substring ($match) shall be at the very beginning of $in as well;
+        // If so, we strip the matching part ($match) from the left of both strings,
+        // and proceed with the rest; otherwise, we set "INPUT DOESN'T MATCH" error
+        $match = substr($in_fmt, 0, $wpif);
+
+        if (substr($in, 0, $wpif) == $match) {
+          $in = substr($in, $wpif);
+          $in_fmt = substr($in_fmt, $wpif);
+        }
+        else {
+          // ERROR #2: INPUT doesn't match INPUT FORMAT!!!
+          $out = '';
+          $error = 2;
+        }
+      }
+
+      if ($error == 0) {
+        // We have a wildcard character at the beginning of $in_fmt here for sure!
+        $in_fmt = substr($in_fmt, 1);
+        $wcif--;
+
+        if ($wcif > 0) {
+          // There is another wildcard somewhere in $in_fmt...
+          // We strip the substring found between both wildcards ($match) from $in_fmt
+
+          $wpif = strpos($in_fmt, '*');
+          $match = substr($in_fmt, 0, $wpif);
+          $in_fmt = substr($in_fmt, $wpif);
+
+          if (substr_count($in, $match) > 0) {
+            // There is at least one $match in $in; everything left to the first $match
+            // within $in is considered to be a replacement ($repl) for the first wildcard;
+            // we strip ($repl . $match) from $in, and proceed with the rest...
+            $wpi = strpos($in, $match);
+            $repl = substr($in, 0, $wpi);
+            $weight += $weight_multiplier * strlen($repl);
+
+            $in = substr($in, $wpi + strlen($match));
+
+            if ($wcof > 0) {
+              // If there are any wildcards in $out_fmt left, we replace the first one
+              $wpof = strpos($out_fmt, '*');
+              $out_fmt = substr_replace($out_fmt, $repl, $wpof, 1);
+              $wcof--;
+            }
+          }
+          else {
+            // ERROR #3: INPUT doesn't match INPUT FORMAT!!!
+            $out = '';
+            $error = 3;
+          }
+
+          if ($error == 0) {
+            if (strlen($in_fmt) > 0) {
+              // If there are more wildcards in $in_fmt,
+              // just recursively process the remaining...
+              $weight_tmp = 0;
+              $error = redirect_match_wildcards($in, $in_fmt, $out_fmt, $out, $weight_tmp);
+              if ($error == 0) $weight += $weight_tmp;
+            }
+            else {
+              // We've done it...
+              $out = $out_fmt;
+            }
+          }
+        }
+        else {
+          // There is no wildcard in $in_fmt anymore...
+          // If there is anything in $in_fmt, it must match the right substring of $in
+          // (nothing should be right to the last $match within $in), and all the
+          // rest (before that $match) is considered as this last replacement ($repl)
+          $match = $in_fmt;
+          if ($match == '') {
+            // We've had only the wildcard character in $in_fmt here
+            // (the whole $in is a replacement $repl)
+            $repl = $in;
+            $weight += $weight_multiplier * strlen($repl);
+
+            if ($wcof > 0) {
+            // If there are any wildcards in $out_fmt left, we replace the first one
+              $wpof = strpos($out_fmt, '*');
+              $out_fmt = substr_replace($out_fmt, $repl, $wpof, 1);
+              $wcof--;
+            }
+
+            // We've done it...
+            $out = $out_fmt;
+          }
+          elseif (substr_count($in, $match) > 0) {
+          // As stated above, there should be a match, and nothing right to it within $in
+          // (everything left to $match is considered as a replacement $repl)
+            $wpi = strrpos($in, $match);
+            $repl = substr($in, 0, $wpi);
+            $weight += $weight_multiplier * strlen($repl);
+
+            if (strlen($in) > $wpi + strlen($match)) {
+            // ERROR #4: INPUT doesn't match INPUT FORMAT!!!
+              $out = '';
+              $error = 4;
+            }
+            else {
+              if ($wcof > 0) {
+                // If there are any wildcards in $out_fmt left, we replace the first one
+                $wpof = strpos($out_fmt, '*');
+                $out_fmt = substr_replace($out_fmt, $repl, $wpof, 1);
+                $wcof--;
+                $weight += $weight_multiplier * strlen($repl);
+              }
+
+              // We've done it...
+              $out = $out_fmt;
+            }
+          }
+          else {
+            // ERROR #5: INPUT doesn't match INPUT FORMAT!!!
+            $out = '';
+            $error = 5;
+          }
+        }
+      }
+    }
+    else {
+    // There is no wildcard character in $in_fmt;
+    // if $in and $in_fmt do match, we've done it...
+      if (strcmp($in, $in_fmt) == 0) {
+        $out = $out_fmt;
+        $error = 0;
+      }
+      else {
+        // ERROR #6: INPUT doesn't match INPUT FORMAT!!!
+        $out = '';
+        $error = 6;
+      }
+    }
+  }
+
+  return $error;
+}
