Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
77.27% covered (warning)
77.27%
34 / 44
20.00% covered (danger)
20.00%
2 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
FileUtils
77.27% covered (warning)
77.27%
34 / 44
20.00% covered (danger)
20.00%
2 / 10
26.18
0.00% covered (danger)
0.00%
0 / 1
 copy
33.33% covered (danger)
33.33%
1 / 3
0.00% covered (danger)
0.00%
0 / 1
3.19
 getContents
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 putContents
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
2.50
 openInputFile
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 openOutputFile
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 openInputFileStream
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 openOutputFileStream
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 copyStreamToFile
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
4.37
 mkdir
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
2.50
 getMwHashes
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
3.00
1<?php
2
3namespace Shellbox;
4
5use GuzzleHttp\Psr7\Utils;
6use Psr\Http\Message\StreamInterface;
7
8/**
9 * Throwing wrappers for file functions
10 *
11 * @internal
12 */
13class FileUtils {
14    private const COPY_BUFFER_SIZE = 65536;
15
16    /**
17     * Copy file
18     *
19     * @param string $source
20     * @param string $dest
21     * @throws ShellboxError
22     */
23    public static function copy( $source, $dest ) {
24        if ( !copy( $source, $dest ) ) {
25            throw new ShellboxError( "Error while copying " .
26                basename( $source ) . ' to ' . basename( $dest ) );
27        }
28    }
29
30    /**
31     * Get contents
32     *
33     * @param string $path
34     * @return string
35     * @throws ShellboxError
36     */
37    public static function getContents( $path ) {
38        $contents = file_get_contents( $path );
39        if ( $contents === false ) {
40            throw new ShellboxError( "Unable to read " . basename( $path ) );
41        }
42        return $contents;
43    }
44
45    /**
46     * Put contents
47     *
48     * @param string $path
49     * @param string $contents
50     * @throws ShellboxError
51     */
52    public static function putContents( $path, $contents ) {
53        if ( !file_put_contents( $path, $contents ) ) {
54            throw new ShellboxError( "Unable to write " . basename( $path ) );
55        }
56    }
57
58    /**
59     * Open a file in read mode
60     *
61     * @param string $path
62     * @return resource
63     * @throws ShellboxError
64     */
65    public static function openInputFile( $path ) {
66        $file = fopen( $path, 'r' );
67        if ( !$file ) {
68            throw new ShellboxError( "Error opening input file " . basename( $path ) );
69        }
70        return $file;
71    }
72
73    /**
74     * Open a file in write mode
75     *
76     * @param string $path
77     * @return resource
78     * @throws ShellboxError
79     */
80    public static function openOutputFile( $path ) {
81        $file = fopen( $path, 'w' );
82        if ( !$file ) {
83            throw new ShellboxError( "Error opening output file " . basename( $path ) );
84        }
85        return $file;
86    }
87
88    /**
89     * Open a file in read mode and convert it to a StreamInterface
90     *
91     * @param string $path
92     * @return StreamInterface
93     * @throws ShellboxError
94     */
95    public static function openInputFileStream( $path ) {
96        return Utils::streamFor( self::openInputFile( $path ) );
97    }
98
99    /**
100     * Open a file in write mode and convert it to a StreamInterface
101     *
102     * @param string $path
103     * @return StreamInterface
104     * @throws ShellboxError
105     */
106    public static function openOutputFileStream( $path ) {
107        return Utils::streamFor( self::openOutputFile( $path ) );
108    }
109
110    /**
111     * Copy a stream to a file
112     *
113     * @param StreamInterface $stream
114     * @param string $path
115     * @throws ShellboxError
116     */
117    public static function copyStreamToFile( StreamInterface $stream, string $path ) {
118        $file = self::openOutputFile( $path );
119        while ( !$stream->eof() ) {
120            $buf = $stream->read( self::COPY_BUFFER_SIZE );
121            if ( fwrite( $file, $buf ) !== strlen( $buf ) ) {
122                throw new ShellboxError( "Error copying stream to file " . basename( $path ) );
123            }
124        }
125        if ( !fclose( $file ) ) {
126            throw new ShellboxError( "Error copying stream to file " . basename( $path ) );
127        }
128    }
129
130    /**
131     * Make a directory with group/other permission bits masked out
132     *
133     * @param string $path
134     * @throws ShellboxError
135     */
136    public static function mkdir( $path ) {
137        if ( !mkdir( $path, 0700 ) ) {
138            throw new ShellboxError( "Error creating directory " . basename( $path ) );
139        }
140    }
141
142    /**
143     * Get content hash headers for MediaWiki from a stream
144     *
145     * @param StreamInterface $stream
146     * @return array
147     * @throws ShellboxError
148     */
149    public static function getMwHashes( StreamInterface $stream ) {
150        $srcSize = $stream->getSize();
151        $md5Context = hash_init( 'md5' );
152        $sha1Context = hash_init( 'sha1' );
153        $hashDigestSize = 0;
154        while ( !$stream->eof() ) {
155            $buffer = $stream->read( 131_072 ); // 128 KiB
156            hash_update( $md5Context, $buffer );
157            hash_update( $sha1Context, $buffer );
158            $hashDigestSize += strlen( $buffer );
159        }
160        if ( $hashDigestSize !== $srcSize ) {
161            throw new ShellboxError( "Stream truncated while hashing" );
162        }
163        return [
164            'etag' => hash_final( $md5Context ),
165            'x-object-meta-sha1base36' =>
166                \Wikimedia\base_convert( hash_final( $sha1Context ), 16, 36, 31 )
167        ];
168    }
169}