Making WordPress.org

Changeset 6022


Ignore:
Timestamp:
10/13/2017 08:56:56 AM (9 years 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.