79 $actions = [
'update',
'verify',
'make-sri' ];
80 if ( !in_array(
$action, $actions ) ) {
81 $this->
error(
"Invalid action.\n\nMust be one of " . implode(
', ', $actions ) .
'.' );
86 $this->registry = $this->
parseBasicYaml( file_get_contents( $this->registryFile ) );
87 if ( $module ===
'all' ) {
89 } elseif ( isset( $this->registry[ $module ] ) ) {
90 $modules = [ $module => $this->registry[ $module ] ];
92 $this->
error(
"Unknown module name.\n\nMust be one of:\n" .
93 wordwrap( implode(
', ', array_keys( $this->registry ) ), 80 ) .
99 foreach (
$modules as $moduleName => $info ) {
100 $this->
verbose(
"\n### {$moduleName}\n\n" );
101 $destDir =
"{$this->libDir}/$moduleName";
103 if ( $this->action ===
'update' ) {
104 $this->
output(
"... updating '{$moduleName}'\n" );
105 $this->
verbose(
"... emptying directory for $moduleName\n" );
107 } elseif ( $this->action ===
'verify' ) {
108 $this->
output(
"... verifying '{$moduleName}'\n" );
110 $this->
output(
"... checking '{$moduleName}'\n" );
113 $this->
verbose(
"... preparing {$this->tmpParentDir}\n" );
116 throw new Exception(
"Unable to create {$this->tmpParentDir}" );
119 if ( !isset( $info[
'type'] ) ) {
120 throw new Exception(
"Module '$moduleName' must have a 'type' key." );
122 switch ( $info[
'type'] ) {
133 throw new Exception(
"Unknown type '{$info['type']}' for '$moduleName'" );
137 $this->
output(
"\nDone!\n" );
139 if ( $this->hasErrors ) {
163 private function fetch( $src, $integrity ) {
164 $key = $this->
cacheKey( $src, $integrity );
170 $req = MWHttpRequest::factory( $src, [
'method' =>
'GET',
'followRedirects' =>
false ] );
171 if ( !$req->execute()->isOK() ) {
172 throw new Exception(
"Failed to download resource at {$src}" );
174 if ( $req->getStatus() !== 200 ) {
175 throw new Exception(
"Unexpected HTTP {$req->getStatus()} response from {$src}" );
177 $data = $req->getContent();
178 $algo = $integrity ===
null ? $this->defaultAlgo : explode(
'-', $integrity )[0];
179 $actualIntegrity = $algo .
'-' . base64_encode( hash( $algo, $data,
true ) );
180 if ( $integrity === $actualIntegrity ) {
181 $this->
verbose(
"... passed integrity check for {$src}\n" );
183 } elseif ( $this->action ===
'make-sri' ) {
184 $this->
output(
"Integrity for {$src}\n\tintegrity: ${actualIntegrity}\n" );
186 throw new Exception(
"Integrity check failed for {$src}\n" .
187 "\tExpected: {$integrity}\n" .
188 "\tActual: {$actualIntegrity}"
230 $info += [
'src' =>
null,
'integrity' =>
null,
'dest' => null ];
231 if ( $info[
'src'] ===
null ) {
232 throw new Exception(
"Module '$moduleName' must have a 'src' key." );
235 $data = $this->
fetch( $info[
'src'], $info[
'integrity' ] );
236 $tmpFile =
"{$this->tmpParentDir}/$moduleName.tar";
237 $this->
verbose(
"... writing '$moduleName' src to $tmpFile\n" );
238 file_put_contents( $tmpFile, $data );
239 $p =
new PharData( $tmpFile );
240 $tmpDir =
"{$this->tmpParentDir}/$moduleName";
241 $p->extractTo( $tmpDir );
244 if ( $info[
'dest'] ===
null ) {
246 $toCopy = [ $tmpDir => $destDir ];
250 foreach ( $info[
'dest'] as $fromSubPath => $toSubPath ) {
252 $fromPaths = glob(
"{$tmpDir}/{$fromSubPath}", GLOB_BRACE );
254 throw new Exception(
"Path '$fromSubPath' of '$moduleName' not found." );
256 foreach ( $fromPaths as $fromPath ) {
257 $toCopy[$fromPath] = $toSubPath ===
null
258 ?
"$destDir/" . basename( $fromPath )
259 :
"$destDir/$toSubPath/" . basename( $fromPath );
263 foreach ( $toCopy as $from => $to ) {
264 if ( $this->action ===
'verify' ) {
265 $this->
verbose(
"... verifying $to\n" );
266 if ( is_dir( $from ) ) {
267 $rii =
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
269 RecursiveDirectoryIterator::SKIP_DOTS
272 foreach ( $rii as
$file ) {
273 $remote =
$file->getPathname();
274 $local = strtr( $remote, [ $from => $to ] );
275 if ( sha1_file( $remote ) !== sha1_file( $local ) ) {
276 $this->
error(
"File '$local' is different." );
277 $this->hasErrors =
true;
280 } elseif ( sha1_file( $from ) !== sha1_file( $to ) ) {
281 $this->
error(
"File '$to' is different." );
282 $this->hasErrors =
true;
284 } elseif ( $this->action ===
'update' ) {
285 $this->
verbose(
"... moving $from to $to\n" );
287 if ( !rename( $from, $to ) ) {
288 throw new Exception(
"Could not move $from to $to." );
337 $lines = explode(
"\n", $input );
341 foreach (
$lines as $i => $text ) {
343 $trimmed = ltrim( $text,
' ' );
344 if ( $trimmed ===
'' || $trimmed[0] ===
'#' ) {
347 $indent = strlen( $text ) - strlen( $trimmed );
348 if ( $indent % 2 !== 0 ) {
349 throw new Exception( __METHOD__ .
": Odd indentation on line $line." );
351 $depth = $indent === 0 ? 0 : ( $indent / 2 );
352 if ( $depth < $prev ) {
354 array_splice( $stack, $depth + 1 );
356 if ( !array_key_exists( $depth, $stack ) ) {
357 throw new Exception( __METHOD__ .
": Too much indentation on line $line." );
359 if ( strpos( $trimmed,
':' ) ===
false ) {
360 throw new Exception( __METHOD__ .
": Missing colon on line $line." );
362 $dest =& $stack[ $depth ];
363 if ( $dest ===
null ) {
367 list( $key, $val ) = explode(
':', $trimmed, 2 );
368 $val = ltrim( $val,
' ' );
371 $dest[ $key ] = $val;
376 $dest[ $key ] = &$val;
379 unset( $dest, $val );