WordPress.org

Making WordPress.org

Changeset 6022


Ignore:
Timestamp:
10/13/2017 08:56:56 AM (8 months ago)
Author:
dd32
Message:

Plugin Directory: Generate md5 hashes for plugins.

This is a POC and may change or be removed in the future, it's here for testing purposes.

A api.wordpress.org endpoint may be available in the future to access it.
This is only enabled for the 'exploit-scanner' plugin at present, purely for testing, as it publishes the md5 hashes of its own files already

Compare https://wordpress.org/plugins/exploit-scanner/ to https://downloads.wordpress.org/plugins/exploit-scanner.1.5.2.checksums.json

See #3192

Location:
sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/bin/rebuild-zip.php

    r5175 r6022  
    11<?php
    22namespace WordPressdotorg\Plugin_Directory;
     3use WordPressdotorg\Plugin_Directory\Plugin_Directory;
    34use WordPressdotorg\Plugin_Directory\Tools\SVN;
    45
     
    8687    $zip_builder = new ZIP\Builder();
    8788
     89    $plugin_post = Plugin_Directory::get_plugin_post( $plugin_slug );
     90    if ( ! $plugin_post ) {
     91        throw new Exception( "Could not locate plugin post" );
     92    }
     93    $stable_tag = get_post_meta( $plugin_post->ID, 'stable_tag', true ) ?? 'trunk';
     94
    8895    // (re)Build & Commit 5 Zips at a time to avoid limitations.
    8996    foreach ( array_chunk( $versions, 5 ) as $versions_to_build ) {
     
    9198            $plugin_slug,
    9299            $versions_to_build,
    93             "{$plugin_slug}: Rebuild triggered by " . php_uname('n' )
     100            "{$plugin_slug}: Rebuild triggered by " . php_uname('n' ),
     101            $stable_tag
    94102        );
    95103    }
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/cli/class-import.php

    r5841 r6022  
    196196                $svn_revision_triggered ?
    197197                    "{$plugin_slug}: ZIP build triggered by https://plugins.trac.wordpress.org/changeset/{$svn_revision_triggered}" :
    198                     "{$plugin_slug}: ZIP build triggered by " . php_uname('n')
     198                    "{$plugin_slug}: ZIP build triggered by " . php_uname('n'),
     199                $stable_tag
    199200            );
    200201        } catch( Exception $e ) {
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/zip/class-builder.php

    r5246 r6022  
    1515    const ZIP_SVN_URL = PLUGIN_ZIP_SVN_URL;
    1616
    17     protected $zip_file = '';
    18     protected $tmp_build_dir  = '';
    19     protected $tmp_dir = '';
    20 
    21     protected $slug    = '';
    22     protected $version = '';
    23     protected $context = '';
    24 
    25     /**
    26      * Generate a ZIP for a provided Plugin versions.
     17    protected $zip_file      = '';
     18    protected $checksum_file = '';
     19    protected $tmp_build_dir = '';
     20    protected $tmp_dir       = '';
     21
     22    protected $slug       = '';
     23    protected $version    = '';
     24    protected $context    = '';
     25    protected $stable_tag = '';
     26
     27    /**
     28     * Generate a ZIP for a provided Plugin tags.
    2729     *
    2830     * @param string $slug     The plugin slug.
     
    3032     * @param string $context  The context of this Builder instance (commit #, etc)
    3133     */
    32     public function build( $slug, $versions, $context = '' ) {
     34    public function build( $slug, $versions, $context = '', $stable_tag = '' ) {
    3335        // Bail when in an unconfigured environment.
    3436        if ( ! defined( 'PLUGIN_ZIP_SVN_URL' ) ) {
     
    3638        }
    3739
    38         $this->slug     = $slug;
    39         $this->versions = $versions;
    40         $this->context  = $context;
     40        $this->slug       = $slug;
     41        $this->versions   = $versions;
     42        $this->context    = $context;
     43        $this->stable_tag = $stable_tag;
    4144
    4245        // General TMP directory
     
    97100            // Pull the ZIP file down we're going to modify, which may not already exist.
    98101            SVN::up( $this->zip_file );
     102            // This is done within the checksum generation function due to us not knowing the checksum filename until export_plugin().
     103            // SVN::up( $this->checksum_file );
    99104
    100105            try {
     
    104109
    105110                $this->export_plugin();
    106                 $this->fix_directory_dates();           
     111                $this->fix_directory_dates();
     112
    107113                $this->generate_zip();
     114
     115                $this->generate_checksums();
     116
    108117                $this->cleanup_plugin_tmp();
    109118
     
    114123                // Perform an SVN up to revert any changes made.
    115124                SVN::up( $this->zip_file );
     125                if ( $this->checksum_file ) {
     126                    SVN::up( $this->checksum_file );
     127                }
    116128                continue;
    117129            }
    118130
    119131            // Add the ZIP file to SVN - This is only really needed for new files which don't exist in SVN.
    120             SVN::add( $this->zip_file );           
     132            SVN::add( $this->zip_file );
     133            if ( $this->checksum_file ) {
     134                SVN::add( $this->checksum_file );
     135            }
    121136        }
    122137
     
    146161
    147162    /**
     163     * Generates a JSON file containing the checksums of the files within the ZIP.
     164     *
     165     * In the event that a previous ZIP for this version exists, checksums for all versions of the file will be included.
     166     */
     167    function generate_checksums() {
     168        // Only enable this for the `exploit-scanner` plugin for the time being.
     169        if ( 'exploit-scanner' != $this->slug ) {
     170            return;
     171        }
     172
     173        // Don't create checksums for trunk.
     174        if ( ! $this->stable_tag || ( 'trunk' == $this->version && 'trunk' != $this->stable_tag && '' != $this->stable_tag ) ) {
     175            return;
     176        }
     177
     178        // Fetch the plugin headers
     179        $plugin_data = false;
     180        foreach ( glob( $this->tmp_build_dir . '/' . $this->slug . '/*.php' ) as $filename ) {
     181            $plugin_data = get_plugin_data( $filename, false, false );
     182
     183            if ( $plugin_data['Name'] && '' !== $plugin_data['Version'] ) {
     184                break;
     185            }
     186        }
     187
     188        if ( ! $plugin_data || '' === $plugin_data['Version'] ) {
     189            return;
     190        }
     191
     192        $plugin_version = $plugin_data['Version'];
     193        // Catch malformed version strings.
     194        if ( basename( $plugin_version ) != $plugin_version ) {
     195            return;
     196        }
     197
     198        $this->checksum_file = "{$this->tmp_dir}/{$this->slug}/{$this->slug}.{$plugin_version}.checksums.json";
     199
     200        // Checkout the Checksum file for this plugin version
     201        SVN::up( $this->checksum_file );
     202
     203        // Existing checksums?
     204        $existing_json_checksum_file = file_exists( $this->checksum_file );
     205
     206        $this->exec( sprintf(
     207            'cd %s && find . -type f -print0 | sort -z | xargs -0 md5sum 2>&1',
     208            escapeshellarg( $this->tmp_build_dir . '/' . $this->slug )
     209        ), $checksum_output, $return_value );
     210
     211        if ( $return_value ) {
     212        //  throw new Exception( __METHOD__ . ': Checksum generation failed, return code: ' . $return_value, 503 );
     213        // For now, just silently bail.
     214            return;
     215        }
     216
     217        $checksums = array();
     218        foreach ( $checksum_output as $line ) {
     219            list( $md5, $filename ) = preg_split( '!\s+!', $line );
     220            $filename = preg_replace( '!^./!', '', $filename );
     221            $checksums[ trim( $filename ) ] = trim( $md5 );
     222        }
     223
     224        $json_checksum_file = (object) array(
     225            'plugin'     => $this->slug,
     226            'version'    => $plugin_version,
     227            'source_tag' => $this->version,
     228            'zip'        => basename( $this->zip_file ),
     229            'checksums'  => $checksums
     230        );
     231
     232        // If the checksum file exists already, merge it into this one.
     233        if ( $existing_json_checksum_file ) {
     234            $existing_json_checksum_file = json_decode( file_get_contents( $this->checksum_file ) );
     235
     236            if ( $existing_json_checksum_file && ! empty( $existing_json_checksum_file->checksums ) ) {
     237                foreach ( $existing_json_checksum_file->checksums as $file => $checksum_details ) {
     238
     239                    if ( ! isset( $json_checksum_file->checksums[ $file ] ) ) {
     240                        // Deleted file, include it in checksums.
     241                        $json_checksum_file->checksums[ $file ] = $checksum_details;
     242
     243                    } elseif ( $json_checksum_file->checksums[ $file ] != $checksum_details ) {
     244                        // Checksum has changed, include both in the resulting json file.
     245                        if ( is_array( $checksum_details ) ) {
     246                            $checksum_details[] = $json_checksum_file->checksums[ $file ];
     247                            $json_checksum_file->checksums[ $file ] = $checksum_details;
     248                        } else {
     249                            $json_checksum_file->checksums[ $file ] = array(
     250                                $json_checksum_file->checksums[ $file ],
     251                                $checksum_details
     252                            );
     253                        }
     254                    }
     255                }
     256            }
     257        }
     258
     259        file_put_contents( $this->checksum_file, wp_json_encode( $json_checksum_file ) );
     260    }
     261
     262    /**
    148263     * Generates a temporary unique directory in a given directory
    149264     *
     
    279394     */
    280395    public function invalidate_zip_caches( $versions ) {
    281         // TODO: Implement PURGE 
     396        // TODO: Implement PURGE
    282397        return true;
    283398        if ( ! defined( 'PLUGIN_ZIP_X_ACCEL_REDIRECT_LOCATION' ) ) {
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/zip/class-serve.php

    r5305 r6022  
    3737        $zip = basename( parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH ) );
    3838
    39         $slug = false;
     39        if ( ! preg_match( "!^(?P<slug>[a-z0-9-_]+)(\.(?P<version>.+?))?\.(?P<request_type>zip|checksums\.json)$!i", $zip, $m ) ) {
     40            throw new Exception( __METHOD__ . ": Invalid URL." );
     41        }
     42
     43        $slug = strtolower( $m['slug'] );
     44
    4045        $version = 'trunk';
    41 
    42         if ( ! preg_match( "!^(?P<slug>[a-z0-9-_]+)(.(?P<version>.+))?.zip$!i", $zip, $m ) ) {
    43             throw new Exception( __METHOD__ . ": Invalid URL." );
    44         }
    45 
    46         $slug = strtolower( $m['slug'] );
    47         if ( isset( $m['version'] ) ) {
     46        if ( isset( $m['version'] ) && '' !== $m['version'] ) {
    4847            $version = $m['version'];
    4948        }
    50 
    5149        if ( 'latest-stable' == $version ) {
    5250            $version = $this->get_stable_tag( $slug );
     51        }
     52
     53        if ( 'zip' == strtolower( $m['request_type'] ) ) {
     54            $checksum_request = false;
     55        } else {
     56            $checksum_request = true;
     57
     58            // Checksum requests for 'trunk' are not possible.
     59            if ( 'trunk' == $version ) {
     60                throw new Exception( __METHOD__ . ": Checksum requests must include a version." );
     61            }
     62
    5363        }
    5464
     
    5666            'stats' => true,
    5767        );
    58         if ( isset( $_GET['stats'] ) ) {
     68
     69        if ( $checksum_request ) {
     70            $args['stats'] = false;
     71
     72        } elseif ( isset( $_GET['stats'] ) ) {
    5973            $args['stats'] = (bool) $_GET['stats'];
     74
    6075        } elseif ( isset( $_GET['nostats'] ) ) {
    6176            $args['stats'] = !empty( $_GET['nostats'] );
    6277        }
    6378
    64         return compact( 'zip', 'slug', 'version', 'args' );
     79        return compact( 'zip', 'slug', 'version', 'args', 'checksum_request' );
    6580    }
    6681
     
    118133
    119134    /**
    120      * Returns the files to use for the request.
     135     * Returns the file to be served for the request.
    121136     *
    122137     * @param array $request The request object for the request.
    123      * @return array An array containing the files to use for the request, 'zip' and 'md5'.
     138     * @return array The file to serve.
    124139     */
    125140    protected function get_file( $request ) {
    126         if ( empty( $request['version'] ) || 'trunk' == $request['version'] ) {
     141        // Checksum requests must include a version
     142        if ( $request['checksum_request'] ) {
     143            return "{$request['slug']}/{$request['slug']}.{$request['version']}.checksums.json";
     144
     145        } elseif ( empty( $request['version'] ) || 'trunk' == $request['version'] ) {
    127146            return "{$request['slug']}/{$request['slug']}.zip";
     147
    128148        } else {
    129149            return "{$request['slug']}/{$request['slug']}.{$request['version']}.zip";
    130150        }
     151
    131152    }
    132153
     
    137158     */
    138159    protected function serve_zip( $request ) {
    139         $zip = $this->get_file( $request );
     160        $file = $this->get_file( $request );
    140161
    141162        if ( defined( 'PLUGIN_ZIP_X_ACCEL_REDIRECT_LOCATION' ) ) {
    142             $zip_url = PLUGIN_ZIP_X_ACCEL_REDIRECT_LOCATION . $zip;
    143 
    144             header( 'Content-Type: application/zip' );
    145             header( 'Content-Disposition: attachment; filename=' . basename( $zip ) );
    146             header( "X-Accel-Redirect: $zip_url" );
     163            $file_url = PLUGIN_ZIP_X_ACCEL_REDIRECT_LOCATION . $file;
     164
     165            if ( $request['checksum_request'] ) {
     166                header( 'Content-Type: application/json' );
     167            } else {
     168                header( 'Content-Type: application/zip' );
     169                header( 'Content-Disposition: attachment; filename=' . basename( $file ) );
     170            }
     171            header( "X-Accel-Redirect: $file_url" );
    147172        } else {
    148173            header( 'Content-Type: text/plain' );
    149             echo "This is a request for $zip, this server isn't currently configured to serve zip files.\n";
     174            echo "This is a request for $file, this server isn't currently configured to serve files.\n";
    150175        }
    151176
Note: See TracChangeset for help on using the changeset viewer.