[Pkg-drupal-commits] r2283 - in /branches/drupal7/debian: changelog patches/70_SA-CORE-2013-002 patches/series

luigi at users.alioth.debian.org luigi at users.alioth.debian.org
Sat Feb 23 14:16:33 UTC 2013


Author: luigi
Date: Sat Feb 23 14:16:33 2013
New Revision: 2283

URL: http://svn.debian.org/wsvn/pkg-drupal/?sc=1&rev=2283
Log:
Incorporated fix for DoS on image derivative generation (Ref: SA-CORE-2013-002, CVE-2013-0316) (Closes: #701165)

Added:
    branches/drupal7/debian/patches/70_SA-CORE-2013-002
Modified:
    branches/drupal7/debian/changelog
    branches/drupal7/debian/patches/series

Modified: branches/drupal7/debian/changelog
URL: http://svn.debian.org/wsvn/pkg-drupal/branches/drupal7/debian/changelog?rev=2283&op=diff
==============================================================================
--- branches/drupal7/debian/changelog (original)
+++ branches/drupal7/debian/changelog Sat Feb 23 14:16:33 2013
@@ -1,6 +1,8 @@
 drupal7 (7.14-2) UNRELEASED; urgency=low
 
   * Acknowledge NMUs from Gunnar Wolf
+  * Incorporated fix for DoS on image derivative generation
+    (Ref: SA-CORE-2013-002, CVE-2013-0316) (Closes: #701165)
 
  -- Luigi Gangitano <luigi at debian.org>  Sat, 23 Feb 2013 15:12:35 +0100
 

Added: branches/drupal7/debian/patches/70_SA-CORE-2013-002
URL: http://svn.debian.org/wsvn/pkg-drupal/branches/drupal7/debian/patches/70_SA-CORE-2013-002?rev=2283&op=file
==============================================================================
--- branches/drupal7/debian/patches/70_SA-CORE-2013-002 (added)
+++ branches/drupal7/debian/patches/70_SA-CORE-2013-002 Sat Feb 23 14:16:33 2013
@@ -1,0 +1,428 @@
+--- a/modules/image/image.module
++++ b/modules/image/image.module
+@@ -30,11 +30,16 @@
+  */
+ define('IMAGE_STORAGE_MODULE', IMAGE_STORAGE_OVERRIDE | IMAGE_STORAGE_DEFAULT);
+ 
++/**
++ * The name of the query parameter for image derivative tokens.
++ */
++define('IMAGE_DERIVATIVE_TOKEN', 'itok');
++
+ // Load all Field module hooks for Image.
+ require_once DRUPAL_ROOT . '/modules/image/image.field.inc';
+ 
+ /**
+- * Implement of hook_help().
++ * Implements hook_help().
+  */
+ function image_help($path, $arg) {
+   switch ($path) {
+@@ -766,16 +771,24 @@
+  *   The image style
+  */
+ function image_style_deliver($style, $scheme) {
+-  // Check that the style is defined and the scheme is valid.
+-  if (!$style || !file_stream_wrapper_valid_scheme($scheme)) {
+-    drupal_exit();
+-  }
+-
+   $args = func_get_args();
+   array_shift($args);
+   array_shift($args);
+   $target = implode('/', $args);
+ 
++  // Check that the style is defined, the scheme is valid, and the image
++  // derivative token is valid. (Sites which require image derivatives to be
++  // generated without a token can set the 'image_allow_insecure_derivatives'
++  // variable to TRUE to bypass the latter check, but this will increase the
++  // site's vulnerability to denial-of-service attacks.)
++  $valid = !empty($style) && file_stream_wrapper_valid_scheme($scheme);
++  if (!variable_get('image_allow_insecure_derivatives', FALSE)) {
++    $valid = $valid && isset($_GET[IMAGE_DERIVATIVE_TOKEN]) && $_GET[IMAGE_DERIVATIVE_TOKEN] === image_style_path_token($style['name'], $scheme . '://' . $target);
++  }
++  if (!$valid) {
++    return MENU_ACCESS_DENIED;
++  }
++
+   $image_uri = $scheme . '://' . $target;
+   $derivative_uri = image_style_path($style['name'], $image_uri);
+ 
+@@ -960,6 +973,10 @@
+  */
+ function image_style_url($style_name, $path) {
+   $uri = image_style_path($style_name, $path);
++  // The token query is added even if the 'image_allow_insecure_derivatives'
++  // variable is TRUE, so that the emitted links remain valid if it is changed
++  // back to the default FALSE.
++  $token_query = array(IMAGE_DERIVATIVE_TOKEN => image_style_path_token($style_name, $path));
+ 
+   // If not using clean URLs, the image derivative callback is only available
+   // with the query string. If the file does not exist, use url() to ensure
+@@ -967,10 +984,33 @@
+   // actual file path, this avoids bootstrapping PHP once the files are built.
+   if (!variable_get('clean_url') && file_uri_scheme($uri) == 'public' && !file_exists($uri)) {
+     $directory_path = file_stream_wrapper_get_instance_by_uri($uri)->getDirectoryPath();
+-    return url($directory_path . '/' . file_uri_target($uri), array('absolute' => TRUE));
++    return url($directory_path . '/' . file_uri_target($uri), array('absolute' => TRUE, 'query' => $token_query));
+   }
+ 
+-  return file_create_url($uri);
++  $file_url = file_create_url($uri);
++  // Append the query string with the token.
++  return $file_url . (strpos($file_url, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($token_query);
++}
++
++/**
++ * Generates a token to protect an image style derivative.
++ *
++ * This prevents unauthorized generation of an image style derivative,
++ * which can be costly both in CPU time and disk space.
++ *
++ * @param $style_name
++ *   The name of the image style.
++ * @param $uri
++ *   The URI of the image for this style, for example as returned by
++ *   image_style_path().
++ *
++ * @return
++ *   An eight-character token which can be used to protect image style
++ *   derivatives against denial-of-service attacks.
++ */
++function image_style_path_token($style_name, $uri) {
++  // Return the first eight characters.
++  return substr(drupal_hmac_base64($style_name . ':' . $uri, drupal_get_private_key() . drupal_get_hash_salt()), 0, 8);
+ }
+ 
+ /**
+@@ -1055,7 +1095,7 @@
+   $effects = &drupal_static(__FUNCTION__);
+ 
+   if (!isset($effects)) {
+-    if ($cache = cache_get("image_effects:$langcode") && !empty($cache->data)) {
++    if ($cache = cache_get("image_effects:$langcode")) {
+       $effects = $cache->data;
+     }
+     else {
+@@ -1263,7 +1303,7 @@
+   $variables['width'] = $dimensions['width'];
+   $variables['height'] = $dimensions['height'];
+ 
+-  // Determine the url for the styled image.
++  // Determine the URL for the styled image.
+   $variables['path'] = image_style_url($variables['style_name'], $variables['path']);
+   return theme('image', $variables);
+ }
+--- a/modules/image/image.test
++++ b/modules/image/image.test
+@@ -183,7 +183,7 @@
+ 
+     // Create a working copy of the file.
+     $files = $this->drupalGetTestFiles('image');
+-    $file = reset($files);
++    $file = array_shift($files);
+     $image_info = image_get_info($file->uri);
+     $original_uri = file_unmanaged_copy($file->uri, $scheme . '://', FILE_EXISTS_RENAME);
+     // Let the image_module_test module know about this file, so it can claim
+@@ -192,13 +192,19 @@
+     $this->assertNotIdentical(FALSE, $original_uri, t('Created the generated image file.'));
+ 
+     // Get the URL of a file that has not been generated and try to create it.
+-    $generated_uri = $scheme . '://styles/' . $this->style_name . '/' . $scheme . '/'. drupal_basename($original_uri);
++    $generated_uri = image_style_path($this->style_name, $original_uri);
+     $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.'));
+     $generate_url = image_style_url($this->style_name, $original_uri);
+ 
+     if (!$clean_url) {
+       $this->assertTrue(strpos($generate_url, '?q=') !== FALSE, 'When using non-clean URLS, the system path contains the query string.');
+     }
++    // Add some extra chars to the token.
++    $this->drupalGet(str_replace(IMAGE_DERIVATIVE_TOKEN . '=', IMAGE_DERIVATIVE_TOKEN . '=Zo', $generate_url));
++    $this->assertResponse(403, 'Image was inaccessible at the URL wih an invalid token.');
++    // Change the parameter name so the token is missing.
++    $this->drupalGet(str_replace(IMAGE_DERIVATIVE_TOKEN . '=', 'wrongparam=', $generate_url));
++    $this->assertResponse(403, 'Image was inaccessible at the URL wih a missing token.');
+ 
+     // Fetch the URL that generates the file.
+     $this->drupalGet($generate_url);
+@@ -212,20 +218,37 @@
+       $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', t('Expires header was sent.'));
+       $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate, post-check=0, pre-check=0', t('Cache-Control header was set to prevent caching.'));
+       $this->assertEqual($this->drupalGetHeader('X-Image-Owned-By'), 'image_module_test', t('Expected custom header has been added.'));
+-      // Verify access is denied to private image styles.
+-      $this->drupalLogout();
++
++      // Make sure that a second request to the already existing derivate works
++      // too.
+       $this->drupalGet($generate_url);
++      $this->assertResponse(200, t('Image was generated at the URL.'));
++
++      // Repeat this with a different file that we do not have access to and
++      // make sure that access is denied.
++      $file_noaccess = array_shift($files);
++      $original_uri_noaccess = file_unmanaged_copy($file_noaccess->uri, $scheme . '://', FILE_EXISTS_RENAME);
++      $generated_uri_noaccess = $scheme . '://styles/' . $this->style_name . '/' . $scheme . '/'. drupal_basename($original_uri_noaccess);
++      $this->assertFalse(file_exists($generated_uri_noaccess), t('Generated file does not exist.'));
++      $generate_url_noaccess = image_style_url($this->style_name, $original_uri_noaccess);
++
++      $this->drupalGet($generate_url_noaccess);
+       $this->assertResponse(403, t('Confirmed that access is denied for the private image style.') );
+       // Verify that images are not appended to the response. Currently this test only uses PNG images.
+       if (strpos($generate_url, '.png') === FALSE ) {
+-        $this->fail( t('Confirming that private image styles are not appended require PNG file.') );
++        $this->fail('Confirming that private image styles are not appended require PNG file.');
+       }
+       else {
+         // Check for PNG-Signature (cf. http://www.libpng.org/pub/png/book/chapter08.html#png.ch08.div.2) in the
+         // response body.
+-        $this->assertNoRaw( chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10), t('No PNG signature found in the response body.') );
++        $this->assertNoRaw( chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10), 'No PNG signature found in the response body.');
+       }
+     }
++    elseif ($clean_url) {
++      // Add some extra chars to the token.
++      $this->drupalGet(str_replace(IMAGE_DERIVATIVE_TOKEN . '=', IMAGE_DERIVATIVE_TOKEN . '=Zo', $generate_url));
++      $this->assertResponse(200, 'Existing image was accessible at the URL wih an invalid token.');
++    }
+   }
+ }
+ 
+@@ -243,7 +266,7 @@
+   }
+ 
+   function setUp() {
+-    parent::setUp('image_test');
++    parent::setUp('image_module_test');
+     module_load_include('inc', 'image', 'image.effects');
+   }
+ 
+@@ -330,6 +353,25 @@
+     $this->assertEqual($calls['rotate'][0][1], 90, t('Degrees were passed correctly'));
+     $this->assertEqual($calls['rotate'][0][2], 0xffffff, t('Background color was passed correctly'));
+   }
++
++  /**
++   * Test image effect caching.
++   */
++  function testImageEffectsCaching() {
++    $image_effect_definitions_called = &drupal_static('image_module_test_image_effect_info_alter');
++
++    // First call should grab a fresh copy of the data.
++    $effects = image_effect_definitions();
++    $this->assertTrue($image_effect_definitions_called === 1, 'image_effect_definitions() generated data.');
++
++    // Second call should come from cache.
++    drupal_static_reset('image_effect_definitions');
++    drupal_static_reset('image_module_test_image_effect_info_alter');
++    $cached_effects = image_effect_definitions();
++    $this->assertTrue(is_null($image_effect_definitions_called), 'image_effect_definitions() returned data from cache.');
++
++    $this->assertTrue($effects == $cached_effects, 'Cached effects are the same as generated effects.');
++  }
+ }
+ 
+ /**
+@@ -630,7 +672,7 @@
+ 
+     // Test that image is displayed using newly created style.
+     $this->drupalGet('node/' . $nid);
+-    $this->assertRaw(image_style_url($style_name, $node->{$field_name}[LANGUAGE_NONE][0]['uri']), t('Image displayed using style @style.', array('@style' => $style_name)));
++    $this->assertRaw(check_plain(image_style_url($style_name, $node->{$field_name}[LANGUAGE_NONE][0]['uri'])), t('Image displayed using style @style.', array('@style' => $style_name)));
+ 
+     // Rename the style and make sure the image field is updated.
+     $new_style_name = strtolower($this->randomName(10));
+@@ -640,7 +682,7 @@
+     $this->drupalPost('admin/config/media/image-styles/edit/' . $style_name, $edit, t('Update style'));
+     $this->assertText(t('Changes to the style have been saved.'), t('Style %name was renamed to %new_name.', array('%name' => $style_name, '%new_name' => $new_style_name)));
+     $this->drupalGet('node/' . $nid);
+-    $this->assertRaw(image_style_url($new_style_name, $node->{$field_name}[LANGUAGE_NONE][0]['uri']), t('Image displayed using style replacement style.'));
++    $this->assertRaw(check_plain(image_style_url($new_style_name, $node->{$field_name}[LANGUAGE_NONE][0]['uri'])), t('Image displayed using style replacement style.'));
+ 
+     // Delete the style and choose a replacement style.
+     $edit = array(
+@@ -651,7 +693,7 @@
+     $this->assertRaw($message, $message);
+ 
+     $this->drupalGet('node/' . $nid);
+-    $this->assertRaw(image_style_url('thumbnail', $node->{$field_name}[LANGUAGE_NONE][0]['uri']), t('Image displayed using style replacement style.'));
++    $this->assertRaw(check_plain(image_style_url('thumbnail', $node->{$field_name}[LANGUAGE_NONE][0]['uri'])), t('Image displayed using style replacement style.'));
+   }
+ }
+ 
+@@ -717,7 +759,7 @@
+     if ($scheme == 'private') {
+       // Only verify HTTP headers when using private scheme and the headers are
+       // sent by Drupal.
+-      $this->assertEqual($this->drupalGetHeader('Content-Type'), 'image/png; name="' . $test_image->filename . '"', t('Content-Type header was sent.'));
++      $this->assertEqual($this->drupalGetHeader('Content-Type'), 'image/png', t('Content-Type header was sent.'));
+       $this->assertEqual($this->drupalGetHeader('Content-Disposition'), 'inline; filename="' . $test_image->filename . '"', t('Content-Disposition header was sent.'));
+       $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'private', t('Cache-Control header was sent.'));
+ 
+@@ -744,7 +786,9 @@
+     // Ensure the derivative image is generated so we do not have to deal with
+     // image style callback paths.
+     $this->drupalGet(image_style_url('thumbnail', $image_uri));
+-    $image_info['path'] = image_style_path('thumbnail', $image_uri);
++    // Need to create the URL again since it will change if clean URLs
++    // are disabled.
++    $image_info['path'] = image_style_url('thumbnail', $image_uri);
+     $image_info['width'] = 100;
+     $image_info['height'] = 50;
+     $default_output = theme('image', $image_info);
+@@ -1030,7 +1074,7 @@
+ 
+     image_effect_save($effect);
+     $img_tag = theme_image_style($variables);
+-    $this->assertEqual($img_tag, '<img typeof="foaf:Image" src="' . $url . '" width="120" height="60" alt="" />', t('Expected img tag was found.'));
++    $this->assertEqual($img_tag, '<img typeof="foaf:Image" src="' . check_plain($url) . '" width="120" height="60" alt="" />', t('Expected img tag was found.'));
+     $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.'));
+     $this->drupalGet($url);
+     $this->assertResponse(200, t('Image was generated at the URL.'));
+@@ -1051,7 +1095,7 @@
+ 
+     image_effect_save($effect);
+     $img_tag = theme_image_style($variables);
+-    $this->assertEqual($img_tag, '<img typeof="foaf:Image" src="' . $url . '" width="60" height="120" alt="" />', t('Expected img tag was found.'));
++    $this->assertEqual($img_tag, '<img typeof="foaf:Image" src="' . check_plain($url) . '" width="60" height="120" alt="" />', t('Expected img tag was found.'));
+     $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.'));
+     $this->drupalGet($url);
+     $this->assertResponse(200, t('Image was generated at the URL.'));
+@@ -1073,7 +1117,7 @@
+ 
+     image_effect_save($effect);
+     $img_tag = theme_image_style($variables);
+-    $this->assertEqual($img_tag, '<img typeof="foaf:Image" src="' . $url . '" width="45" height="90" alt="" />', t('Expected img tag was found.'));
++    $this->assertEqual($img_tag, '<img typeof="foaf:Image" src="' . check_plain($url) . '" width="45" height="90" alt="" />', t('Expected img tag was found.'));
+     $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.'));
+     $this->drupalGet($url);
+     $this->assertResponse(200, t('Image was generated at the URL.'));
+@@ -1095,7 +1139,7 @@
+ 
+     image_effect_save($effect);
+     $img_tag = theme_image_style($variables);
+-    $this->assertEqual($img_tag, '<img typeof="foaf:Image" src="' . $url . '" width="45" height="90" alt="" />', t('Expected img tag was found.'));
++    $this->assertEqual($img_tag, '<img typeof="foaf:Image" src="' . check_plain($url) . '" width="45" height="90" alt="" />', t('Expected img tag was found.'));
+     $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.'));
+     $this->drupalGet($url);
+     $this->assertResponse(200, t('Image was generated at the URL.'));
+@@ -1113,7 +1157,7 @@
+ 
+     image_effect_save($effect);
+     $img_tag = theme_image_style($variables);
+-    $this->assertEqual($img_tag, '<img typeof="foaf:Image" src="' . $url . '" width="45" height="90" alt="" />', t('Expected img tag was found.'));
++    $this->assertEqual($img_tag, '<img typeof="foaf:Image" src="' . check_plain($url) . '" width="45" height="90" alt="" />', t('Expected img tag was found.'));
+     $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.'));
+     $this->drupalGet($url);
+     $this->assertResponse(200, t('Image was generated at the URL.'));
+@@ -1134,7 +1178,7 @@
+ 
+     image_effect_save($effect);
+     $img_tag = theme_image_style($variables);
+-    $this->assertEqual($img_tag, '<img typeof="foaf:Image" src="' . $url . '" alt="" />', t('Expected img tag was found.'));
++    $this->assertEqual($img_tag, '<img typeof="foaf:Image" src="' . check_plain($url) . '" alt="" />', t('Expected img tag was found.'));
+     $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.'));
+     $this->drupalGet($url);
+     $this->assertResponse(200, t('Image was generated at the URL.'));
+@@ -1154,7 +1198,7 @@
+ 
+     image_effect_save($effect);
+     $img_tag = theme_image_style($variables);
+-    $this->assertEqual($img_tag, '<img typeof="foaf:Image" src="' . $url . '" width="30" height="30" alt="" />', t('Expected img tag was found.'));
++    $this->assertEqual($img_tag, '<img typeof="foaf:Image" src="' . check_plain($url) . '" width="30" height="30" alt="" />', t('Expected img tag was found.'));
+     $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.'));
+     $this->drupalGet($url);
+     $this->assertResponse(200, t('Image was generated at the URL.'));
+@@ -1175,7 +1219,7 @@
+ 
+     $effect = image_effect_save($effect);
+     $img_tag = theme_image_style($variables);
+-    $this->assertEqual($img_tag, '<img typeof="foaf:Image" src="' . $url . '" alt="" />', t('Expected img tag was found.'));
++    $this->assertEqual($img_tag, '<img typeof="foaf:Image" src="' . check_plain($url) . '" alt="" />', t('Expected img tag was found.'));
+     $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.'));
+     $this->drupalGet($url);
+     $this->assertResponse(200, t('Image was generated at the URL.'));
+@@ -1193,7 +1237,7 @@
+ 
+     image_effect_save($effect);
+     $img_tag = theme_image_style($variables);
+-    $this->assertEqual($img_tag, '<img typeof="foaf:Image" src="' . $url . '" alt="" />', t('Expected img tag was found.'));
++    $this->assertEqual($img_tag, '<img typeof="foaf:Image" src="' . check_plain($url) . '" alt="" />', t('Expected img tag was found.'));
+   }
+ }
+ 
+@@ -1566,3 +1610,64 @@
+   }
+ 
+ }
++
++/**
++ * Tests image theme functions.
++ */
++class ImageThemeFunctionWebTestCase extends DrupalWebTestCase {
++
++  public static function getInfo() {
++    return array(
++      'name' => 'Image theme functions',
++      'description' => 'Test that the image theme functions work correctly.',
++      'group' => 'Image',
++    );
++  }
++
++  function setUp() {
++    parent::setUp(array('image'));
++  }
++
++  /**
++   * Tests usage of the image field formatters.
++   */
++  function testImageFormatterTheme() {
++    // Create an image.
++    $files = $this->drupalGetTestFiles('image');
++    $file = reset($files);
++    $original_uri = file_unmanaged_copy($file->uri, 'public://', FILE_EXISTS_RENAME);
++
++    // Create a style.
++    image_style_save(array('name' => 'test'));
++    $url = image_style_url('test', $original_uri);
++
++    // Test using theme_image_formatter() without an image title, alt text, or
++    // link options.
++    $path = $this->randomName();
++    $element = array(
++      '#theme' => 'image_formatter',
++      '#image_style' => 'test',
++      '#item' => array(
++        'uri' => $original_uri,
++      ),
++      '#path' => array(
++        'path' => $path,
++      ),
++    );
++    $rendered_element = render($element);
++    $expected_result = '<a href="' . url($path) . '"><img typeof="foaf:Image" src="' . check_plain($url) . '" alt="" /></a>';
++    $this->assertEqual($expected_result, $rendered_element, 'theme_image_formatter() correctly renders without title, alt, or path options.');
++
++    // Link the image to a fragment on the page, and not a full URL.
++    $fragment = $this->randomName();
++    $element['#path']['path'] = '';
++    $element['#path']['options'] = array(
++      'external' => TRUE,
++      'fragment' => $fragment,
++    );
++    $rendered_element = render($element);
++    $expected_result = '<a href="#' . $fragment . '"><img typeof="foaf:Image" src="' . check_plain($url) . '" alt="" /></a>';
++    $this->assertEqual($expected_result, $rendered_element, 'theme_image_formatter() correctly renders a link fragment.');
++  }
++
++}
+--- a/modules/user/user.test
++++ b/modules/user/user.test
+@@ -916,7 +916,7 @@
+       $this->assertRaw($text, t('Image was resized.'));
+       $alt = t("@user's picture", array('@user' => format_username($this->user)));
+       $style = variable_get('user_picture_style', '');
+-      $this->assertRaw(image_style_url($style, $pic_path), t("Image is displayed in user's edit page"));
++      $this->assertRaw(check_plain(image_style_url($style, $pic_path)), t("Image is displayed in user's edit page"));
+ 
+       // Check if file is located in proper directory.
+       $this->assertTrue(is_file($pic_path), t("File is located in proper directory"));

Modified: branches/drupal7/debian/patches/series
URL: http://svn.debian.org/wsvn/pkg-drupal/branches/drupal7/debian/patches/series?rev=2283&op=diff
==============================================================================
--- branches/drupal7/debian/patches/series (original)
+++ branches/drupal7/debian/patches/series Sat Feb 23 14:16:33 2013
@@ -3,3 +3,4 @@
 40_SA-CORE-2012-003
 50_SA-CORE-2012-004
 60_SA-CORE-2013-001
+70_SA-CORE-2013-002




More information about the Pkg-drupal-commits mailing list