Skip to content

Fix version constrains, support multiple stable branches. #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 60 additions & 49 deletions build/build-composer-json.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
<?php

use Doctrine\Common\Cache\FilesystemCache;
use DrupalComposer\DrupalSecurityAdvisories\Projects;
use DrupalComposer\DrupalSecurityAdvisories\UrlHelper;
use DrupalComposer\DrupalSecurityAdvisories\VersionParser;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Kevinrob\GuzzleCache\CacheMiddleware;
use Doctrine\Common\Cache\FilesystemCache;
use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
use Kevinrob\GuzzleCache\Storage\DoctrineCacheStorage;
use Kevinrob\GuzzleCache\Strategy\GreedyCacheStrategy;

require __DIR__ . '/vendor/autoload.php';

Expand All @@ -16,62 +19,74 @@
$stack = HandlerStack::create();
$stack->push(
new CacheMiddleware(
new PrivateCacheStrategy(
new GreedyCacheStrategy(
new DoctrineCacheStorage(
new FilesystemCache(__DIR__ . '/cache')
)
),
3600
)
),
'cache'
);
$client = new Client(['handler' => $stack]);

$data = json_decode($client->get('https://www.drupal.org/api-d7/node.json?type=project_release&taxonomy_vocabulary_7=100&field_release_build_type=static')->getBody());

$projects = [];
$projects = new Projects($client);
$conflict = [];

class UrlHelper {

public static function prepareUrl($url) {
return str_replace('https://www.drupal.org/api-d7/node', 'https://www.drupal.org/api-d7/node.json', $url);
/**
* @param $url
* @param \GuzzleHttp\Client $client
*
* @return array
*/
function fetchAllData($url, Client $client) {
$results = [];
$data = json_decode($client->get($url)->getBody());
while (isset($data) && isset($data->list)) {
$results = array_merge($results, $data->list);

if (isset($data->next)) {
$data = json_decode($client->get(UrlHelper::prepareUrl($data->next))->getBody());
}
else {
$data = NULL;
}
}

return $results;
}

class VersionParser {

public static function getSemVer($version, $isCore) {
$version = $isCore ? static::handleCore($version) : static::handleContrib($version);
return static::isValid($version) ? $version : FALSE;
}
// Security releases
$results = fetchAllData('https://www.drupal.org/api-d7/node.json?type=project_release&taxonomy_vocabulary_7=100&field_release_build_type=static', $client);
foreach ($results as $result) {
$nid = $result->field_release_project->id;
$core = (int) substr($result->field_release_version, 0, 1);

public static function handleCore($version) {
return $version;
// Skip D6 and older.
if ($core < 7) {
continue;
}

public static function handleContrib($version) {
list($core, $version) = explode('-', $version, 2);
return $version;
}
$project = $projects->getFromNid($nid);

public static function isValid($version) {
return (strpos($version, 'unstable') === FALSE);
if (!$project) {
// @todo: log error
continue;
}

}

while (isset($data) && isset($data->list)) {
$results = array_merge($results, $data->list);

if (isset($data->next)) {
$data = json_decode($client->get(UrlHelper::prepareUrl($data->next))->getBody());
}
else {
$data = NULL;
try {
$is_core = ($project->field_project_machine_name == 'drupal') ? TRUE : FALSE;
$constraint = VersionParser::generateRangeConstraint($result->field_release_version, $is_core);
if (!$constraint) {
throw new InvalidArgumentException('Invalid version number.');
}
$conflict[$core]['drupal/' . $project->field_project_machine_name][] = $constraint;
} catch (\Exception $e) {
// @todo: log exception
continue;
}
}

// Insecure releases
$results = fetchAllData('https://www.drupal.org/api-d7/node.json?type=project_release&taxonomy_vocabulary_7=188131&field_release_build_type=static', $client);
foreach ($results as $result) {
$nid = $result->field_release_project->id;
$core = (int) substr($result->field_release_version, 0, 1);
Expand All @@ -81,24 +96,20 @@ public static function isValid($version) {
continue;
}

try {
if (!isset($projects[$nid])) {
$project = json_decode($client->get('https://www.drupal.org/api-d7/node.json?nid=' . $nid)->getBody());
$projects[$nid] = $project->list[0];
}
} catch (\GuzzleHttp\Exception\ServerException $e) {
// @todo: log exception
$project = $projects->getFromNid($nid);

if (!$project) {
// @todo: log error
continue;
}

try {
$project = $projects[$nid];
$is_core = ($project->field_project_machine_name == 'drupal') ? TRUE : FALSE;
$version = VersionParser::getSemVer($result->field_release_version, $is_core);
if (!$version) {
$constraint = VersionParser::generateExplicitConstraint($result->field_release_version, $is_core);
if (!$constraint) {
throw new InvalidArgumentException('Invalid version number.');
}
$conflict[$core]['drupal/' . $project->field_project_machine_name][] = '<' . $version;
$conflict[$core]['drupal/' . $project->field_project_machine_name][] = $constraint;
} catch (\Exception $e) {
// @todo: log exception
continue;
Expand All @@ -121,7 +132,7 @@ public static function isValid($version) {

foreach ($packages as $package => $constraints) {
natsort($constraints);
$composer['conflict'][$package] = implode(',', $constraints);
$composer['conflict'][$package] = implode('|', $constraints);
}

// drupal/core is a subtree split for drupal/drupal and has no own SAs.
Expand Down
8 changes: 7 additions & 1 deletion build/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
"composer/composer": "^1.0",
"kevinrob/guzzle-cache-middleware": "^3.2",
"guzzlehttp/guzzle": "^6.3",
"doctrine/cache": "^1.7"
"doctrine/cache": "^1.7",
"ext-json": "*"
},
"autoload": {
"psr-4": {
"DrupalComposer\\DrupalSecurityAdvisories\\": "src"
}
}
}
27 changes: 27 additions & 0 deletions build/src/Projects.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace DrupalComposer\DrupalSecurityAdvisories;

class Projects {

protected $storage = [];

protected $client;

public function __construct(\GuzzleHttp\Client $client) {
$this->client = $client;
}

public function getFromNid($nid) {
try {
if (!isset($this->storage[$nid])) {
$project = json_decode($this->client->get('https://www.drupal.org/api-d7/node.json?nid=' . $nid)->getBody());
$this->storage[$nid] = $project->list[0];
}
} catch (\GuzzleHttp\Exception\ServerException $e) {
$this->storage[$nid] = [];
}
return $this->storage[$nid];
}

}
11 changes: 11 additions & 0 deletions build/src/UrlHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace DrupalComposer\DrupalSecurityAdvisories;

class UrlHelper {

public static function prepareUrl($url) {
return str_replace('https://www.drupal.org/api-d7/node', 'https://www.drupal.org/api-d7/node.json', $url);
}

}
42 changes: 42 additions & 0 deletions build/src/VersionParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace DrupalComposer\DrupalSecurityAdvisories;

class VersionParser {

public static function generateRangeConstraint($version, $isCore) {
if (!static::isValid($version)) {
return FALSE;
}
return $isCore ? static::handleCore($version) : static::handleContrib($version);
}

public static function generateExplicitConstraint($version, $isCore) {
if (!static::isValid($version)) {
return FALSE;
}
if ($isCore) {
return $version;
}
else {
list($core, $version) = explode('-', $version, 2);
}
return $version;
}

public static function handleCore($version) {
list($major, $minor) = explode('.', $version);
return ">=$major.$minor,<$version";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For D7, this returns constraints like >7.67,<=7.67 instead of >7,<=7.67, so a different return is needed

  public static function handleCore($version) {
    list($major, $minor) = explode('.', $version);
    if ($major == '7') {
      return ">=$major,<$version";
    }
    return ">=$major.$minor,<$version";
  }

}

public static function handleContrib($version) {
list($core, $version) = explode('-', $version, 2);
list($major) = explode('.', $version);
return ">=$major,<$version";
}

public static function isValid($version) {
return (strpos($version, 'unstable') === FALSE);
}

}