WordPress.org

Make WordPress Core

Ticket #19552: file.php

File file.php, 21.5 KB (added by ryan, 4 years ago)

file.php

Line 
1<?php
2/**
3 * File handling API
4 *
5 * @package WordPress
6 */
7
8/**
9 * Recursive directory creation based on full path.
10 *
11 * Will attempt to set permissions on folders.
12 *
13 * @since 2.0.1
14 *
15 * @param string $target Full path to attempt to create.
16 * @return bool Whether the path was created. True if path already exists.
17 */
18function wp_mkdir_p( $target ) {
19        // from php.net/mkdir user contributed notes
20        $target = str_replace( '//', '/', $target );
21
22        // safe mode fails with a trailing slash under certain PHP versions.
23        $target = rtrim($target, '/'); // Use rtrim() instead of untrailingslashit to avoid formatting.php dependency.
24        if ( empty($target) )
25                $target = '/';
26
27        if ( file_exists( $target ) )
28                return @is_dir( $target );
29
30        // Attempting to create the directory may clutter up our display.
31        if ( @mkdir( $target ) ) {
32                $stat = @stat( dirname( $target ) );
33                $dir_perms = $stat['mode'] & 0007777;  // Get the permission bits.
34                @chmod( $target, $dir_perms );
35                return true;
36        } elseif ( is_dir( dirname( $target ) ) ) {
37                        return false;
38        }
39
40        // If the above failed, attempt to create the parent node, then try again.
41        if ( ( $target != '/' ) && ( wp_mkdir_p( dirname( $target ) ) ) )
42                return wp_mkdir_p( $target );
43
44        return false;
45}
46
47/**
48 * Test if a give filesystem path is absolute ('/foo/bar', 'c:\windows').
49 *
50 * @since 2.5.0
51 *
52 * @param string $path File path
53 * @return bool True if path is absolute, false is not absolute.
54 */
55function path_is_absolute( $path ) {
56        // this is definitive if true but fails if $path does not exist or contains a symbolic link
57        if ( realpath($path) == $path )
58                return true;
59
60        if ( strlen($path) == 0 || $path[0] == '.' )
61                return false;
62
63        // windows allows absolute paths like this
64        if ( preg_match('#^[a-zA-Z]:\\\\#', $path) )
65                return true;
66
67        // a path starting with / or \ is absolute; anything else is relative
68        return ( $path[0] == '/' || $path[0] == '\\' );
69}
70
71/**
72 * Join two filesystem paths together (e.g. 'give me $path relative to $base').
73 *
74 * If the $path is absolute, then it the full path is returned.
75 *
76 * @since 2.5.0
77 *
78 * @param string $base
79 * @param string $path
80 * @return string The path with the base or absolute path.
81 */
82function path_join( $base, $path ) {
83        if ( path_is_absolute($path) )
84                return $path;
85
86        return rtrim($base, '/') . '/' . ltrim($path, '/');
87}
88
89/**
90 * Determines a writable directory for temporary files.
91 * Function's preference is to WP_CONTENT_DIR followed by the return value of <code>sys_get_temp_dir()</code>, before finally defaulting to /tmp/
92 *
93 * In the event that this function does not find a writable location, It may be overridden by the <code>WP_TEMP_DIR</code> constant in your <code>wp-config.php</code> file.
94 *
95 * @since 2.5.0
96 *
97 * @return string Writable temporary directory
98 */
99function get_temp_dir() {
100        static $temp;
101        if ( defined('WP_TEMP_DIR') )
102                return trailingslashit(WP_TEMP_DIR);
103
104        if ( $temp )
105                return trailingslashit($temp);
106
107        $temp = WP_CONTENT_DIR . '/';
108        if ( is_dir($temp) && @is_writable($temp) )
109                return $temp;
110
111        if  ( function_exists('sys_get_temp_dir') ) {
112                $temp = sys_get_temp_dir();
113                if ( @is_writable($temp) )
114                        return trailingslashit($temp);
115        }
116
117        $temp = ini_get('upload_tmp_dir');
118        if ( is_dir($temp) && @is_writable($temp) )
119                return trailingslashit($temp);
120
121        $temp = '/tmp/';
122        return $temp;
123}
124
125/**
126 * Get an array containing the current upload directory's path and url.
127 *
128 * Checks the 'upload_path' option, which should be from the web root folder,
129 * and if it isn't empty it will be used. If it is empty, then the path will be
130 * 'WP_CONTENT_DIR/uploads'. If the 'UPLOADS' constant is defined, then it will
131 * override the 'upload_path' option and 'WP_CONTENT_DIR/uploads' path.
132 *
133 * The upload URL path is set either by the 'upload_url_path' option or by using
134 * the 'WP_CONTENT_URL' constant and appending '/uploads' to the path.
135 *
136 * If the 'uploads_use_yearmonth_folders' is set to true (checkbox if checked in
137 * the administration settings panel), then the time will be used. The format
138 * will be year first and then month.
139 *
140 * If the path couldn't be created, then an error will be returned with the key
141 * 'error' containing the error message. The error suggests that the parent
142 * directory is not writable by the server.
143 *
144 * On success, the returned array will have many indices:
145 * 'path' - base directory and sub directory or full path to upload directory.
146 * 'url' - base url and sub directory or absolute URL to upload directory.
147 * 'subdir' - sub directory if uploads use year/month folders option is on.
148 * 'basedir' - path without subdir.
149 * 'baseurl' - URL path without subdir.
150 * 'error' - set to false.
151 *
152 * @since 2.0.0
153 * @uses apply_filters() Calls 'upload_dir' on returned array.
154 *
155 * @param string $time Optional. Time formatted in 'yyyy/mm'.
156 * @return array See above for description.
157 */
158function wp_upload_dir( $time = null ) {
159        global $switched;
160        $siteurl = get_option( 'siteurl' );
161        $upload_path = get_option( 'upload_path' );
162        $upload_path = trim($upload_path);
163        $main_override = is_multisite() && defined( 'MULTISITE' ) && is_main_site();
164        if ( empty($upload_path) ) {
165                $dir = WP_CONTENT_DIR . '/uploads';
166        } else {
167                $dir = $upload_path;
168                if ( 'wp-content/uploads' == $upload_path ) {
169                        $dir = WP_CONTENT_DIR . '/uploads';
170                } elseif ( 0 !== strpos($dir, ABSPATH) ) {
171                        // $dir is absolute, $upload_path is (maybe) relative to ABSPATH
172                        $dir = path_join( ABSPATH, $dir );
173                }
174        }
175
176        if ( !$url = get_option( 'upload_url_path' ) ) {
177                if ( empty($upload_path) || ( 'wp-content/uploads' == $upload_path ) || ( $upload_path == $dir ) )
178                        $url = WP_CONTENT_URL . '/uploads';
179                else
180                        $url = trailingslashit( $siteurl ) . $upload_path;
181        }
182
183        if ( defined('UPLOADS') && !$main_override && ( !isset( $switched ) || $switched === false ) ) {
184                $dir = ABSPATH . UPLOADS;
185                $url = trailingslashit( $siteurl ) . UPLOADS;
186        }
187
188        if ( is_multisite() && !$main_override && ( !isset( $switched ) || $switched === false ) ) {
189                if ( defined( 'BLOGUPLOADDIR' ) )
190                        $dir = untrailingslashit(BLOGUPLOADDIR);
191                $url = str_replace( UPLOADS, 'files', $url );
192        }
193
194        $bdir = $dir;
195        $burl = $url;
196
197        $subdir = '';
198        if ( get_option( 'uploads_use_yearmonth_folders' ) ) {
199                // Generate the yearly and monthly dirs
200                if ( !$time )
201                        $time = current_time( 'mysql' );
202                $y = substr( $time, 0, 4 );
203                $m = substr( $time, 5, 2 );
204                $subdir = "/$y/$m";
205        }
206
207        $dir .= $subdir;
208        $url .= $subdir;
209
210        $uploads = apply_filters( 'upload_dir', array( 'path' => $dir, 'url' => $url, 'subdir' => $subdir, 'basedir' => $bdir, 'baseurl' => $burl, 'error' => false ) );
211
212        // Make sure we have an uploads dir
213        if ( ! wp_mkdir_p( $uploads['path'] ) ) {
214                $message = sprintf( __( 'Unable to create directory %s. Is its parent directory writable by the server?' ), $uploads['path'] );
215                return array( 'error' => $message );
216        }
217
218        return $uploads;
219}
220
221/**
222 * Get a filename that is sanitized and unique for the given directory.
223 *
224 * If the filename is not unique, then a number will be added to the filename
225 * before the extension, and will continue adding numbers until the filename is
226 * unique.
227 *
228 * The callback is passed three parameters, the first one is the directory, the
229 * second is the filename, and the third is the extension.
230 *
231 * @since 2.5.0
232 *
233 * @param string $dir
234 * @param string $filename
235 * @param mixed $unique_filename_callback Callback.
236 * @return string New filename, if given wasn't unique.
237 */
238function wp_unique_filename( $dir, $filename, $unique_filename_callback = null ) {
239        // sanitize the file name before we begin processing
240        $filename = sanitize_file_name($filename);
241
242        // separate the filename into a name and extension
243        $info = pathinfo($filename);
244        $ext = !empty($info['extension']) ? '.' . $info['extension'] : '';
245        $name = basename($filename, $ext);
246
247        // edge case: if file is named '.ext', treat as an empty name
248        if ( $name === $ext )
249                $name = '';
250
251        // Increment the file number until we have a unique file to save in $dir. Use callback if supplied.
252        if ( $unique_filename_callback && is_callable( $unique_filename_callback ) ) {
253                $filename = call_user_func( $unique_filename_callback, $dir, $name, $ext );
254        } else {
255                $number = '';
256
257                // change '.ext' to lower case
258                if ( $ext && strtolower($ext) != $ext ) {
259                        $ext2 = strtolower($ext);
260                        $filename2 = preg_replace( '|' . preg_quote($ext) . '$|', $ext2, $filename );
261
262                        // check for both lower and upper case extension or image sub-sizes may be overwritten
263                        while ( file_exists($dir . "/$filename") || file_exists($dir . "/$filename2") ) {
264                                $new_number = $number + 1;
265                                $filename = str_replace( "$number$ext", "$new_number$ext", $filename );
266                                $filename2 = str_replace( "$number$ext2", "$new_number$ext2", $filename2 );
267                                $number = $new_number;
268                        }
269                        return $filename2;
270                }
271
272                while ( file_exists( $dir . "/$filename" ) ) {
273                        if ( '' == "$number$ext" )
274                                $filename = $filename . ++$number . $ext;
275                        else
276                                $filename = str_replace( "$number$ext", ++$number . $ext, $filename );
277                }
278        }
279
280        return $filename;
281}
282
283/**
284 * Create a file in the upload folder with given content.
285 *
286 * If there is an error, then the key 'error' will exist with the error message.
287 * If success, then the key 'file' will have the unique file path, the 'url' key
288 * will have the link to the new file. and the 'error' key will be set to false.
289 *
290 * This function will not move an uploaded file to the upload folder. It will
291 * create a new file with the content in $bits parameter. If you move the upload
292 * file, read the content of the uploaded file, and then you can give the
293 * filename and content to this function, which will add it to the upload
294 * folder.
295 *
296 * The permissions will be set on the new file automatically by this function.
297 *
298 * @since 2.0.0
299 *
300 * @param string $name
301 * @param null $deprecated Never used. Set to null.
302 * @param mixed $bits File content
303 * @param string $time Optional. Time formatted in 'yyyy/mm'.
304 * @return array
305 */
306function wp_upload_bits( $name, $deprecated, $bits, $time = null ) {
307        if ( !empty( $deprecated ) )
308                _deprecated_argument( __FUNCTION__, '2.0' );
309
310        if ( empty( $name ) )
311                return array( 'error' => __( 'Empty filename' ) );
312
313        $wp_filetype = wp_check_filetype( $name );
314        if ( !$wp_filetype['ext'] )
315                return array( 'error' => __( 'Invalid file type' ) );
316
317        $upload = wp_upload_dir( $time );
318
319        if ( $upload['error'] !== false )
320                return $upload;
321
322        $upload_bits_error = apply_filters( 'wp_upload_bits', array( 'name' => $name, 'bits' => $bits, 'time' => $time ) );
323        if ( !is_array( $upload_bits_error ) ) {
324                $upload[ 'error' ] = $upload_bits_error;
325                return $upload;
326        }
327
328        $filename = wp_unique_filename( $upload['path'], $name );
329
330        $new_file = $upload['path'] . "/$filename";
331        if ( ! wp_mkdir_p( dirname( $new_file ) ) ) {
332                $message = sprintf( __( 'Unable to create directory %s. Is its parent directory writable by the server?' ), dirname( $new_file ) );
333                return array( 'error' => $message );
334        }
335
336        $ifp = @ fopen( $new_file, 'wb' );
337        if ( ! $ifp )
338                return array( 'error' => sprintf( __( 'Could not write file %s' ), $new_file ) );
339
340        @fwrite( $ifp, $bits );
341        fclose( $ifp );
342        clearstatcache();
343
344        // Set correct file permissions
345        $stat = @ stat( dirname( $new_file ) );
346        $perms = $stat['mode'] & 0007777;
347        $perms = $perms & 0000666;
348        @ chmod( $new_file, $perms );
349        clearstatcache();
350
351        // Compute the URL
352        $url = $upload['url'] . "/$filename";
353
354        return array( 'file' => $new_file, 'url' => $url, 'error' => false );
355}
356
357/**
358 * Retrieve the file type based on the extension name.
359 *
360 * @package WordPress
361 * @since 2.5.0
362 * @uses apply_filters() Calls 'ext2type' hook on default supported types.
363 *
364 * @param string $ext The extension to search.
365 * @return string|null The file type, example: audio, video, document, spreadsheet, etc. Null if not found.
366 */
367function wp_ext2type( $ext ) {
368        $ext2type = apply_filters( 'ext2type', array(
369                'audio'       => array( 'aac', 'ac3',  'aif',  'aiff', 'm3a',  'm4a',   'm4b', 'mka', 'mp1', 'mp2',  'mp3', 'ogg', 'oga', 'ram', 'wav', 'wma' ),
370                'video'       => array( 'asf', 'avi',  'divx', 'dv',   'flv',  'm4v',   'mkv', 'mov', 'mp4', 'mpeg', 'mpg', 'mpv', 'ogm', 'ogv', 'qt',  'rm', 'vob', 'wmv' ),
371                'document'    => array( 'doc', 'docx', 'docm', 'dotm', 'odt',  'pages', 'pdf', 'rtf', 'wp',  'wpd' ),
372                'spreadsheet' => array( 'numbers',     'ods',  'xls',  'xlsx', 'xlsb',  'xlsm' ),
373                'interactive' => array( 'key', 'ppt',  'pptx', 'pptm', 'odp',  'swf' ),
374                'text'        => array( 'asc', 'csv',  'tsv',  'txt' ),
375                'archive'     => array( 'bz2', 'cab',  'dmg',  'gz',   'rar',  'sea',   'sit', 'sqx', 'tar', 'tgz',  'zip', '7z' ),
376                'code'        => array( 'css', 'htm',  'html', 'php',  'js' ),
377        ));
378        foreach ( $ext2type as $type => $exts )
379                if ( in_array( $ext, $exts ) )
380                        return $type;
381}
382
383/**
384 * Retrieve the file type from the file name.
385 *
386 * You can optionally define the mime array, if needed.
387 *
388 * @since 2.0.4
389 *
390 * @param string $filename File name or path.
391 * @param array $mimes Optional. Key is the file extension with value as the mime type.
392 * @return array Values with extension first and mime type.
393 */
394function wp_check_filetype( $filename, $mimes = null ) {
395        if ( empty($mimes) )
396                $mimes = get_allowed_mime_types();
397        $type = false;
398        $ext = false;
399
400        foreach ( $mimes as $ext_preg => $mime_match ) {
401                $ext_preg = '!\.(' . $ext_preg . ')$!i';
402                if ( preg_match( $ext_preg, $filename, $ext_matches ) ) {
403                        $type = $mime_match;
404                        $ext = $ext_matches[1];
405                        break;
406                }
407        }
408
409        return compact( 'ext', 'type' );
410}
411
412/**
413 * Attempt to determine the real file type of a file.
414 * If unable to, the file name extension will be used to determine type.
415 *
416 * If it's determined that the extension does not match the file's real type,
417 * then the "proper_filename" value will be set with a proper filename and extension.
418 *
419 * Currently this function only supports validating images known to getimagesize().
420 *
421 * @since 3.0.0
422 *
423 * @param string $file Full path to the image.
424 * @param string $filename The filename of the image (may differ from $file due to $file being in a tmp directory)
425 * @param array $mimes Optional. Key is the file extension with value as the mime type.
426 * @return array Values for the extension, MIME, and either a corrected filename or false if original $filename is valid
427 */
428function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) {
429
430        $proper_filename = false;
431
432        // Do basic extension validation and MIME mapping
433        $wp_filetype = wp_check_filetype( $filename, $mimes );
434        extract( $wp_filetype );
435
436        // We can't do any further validation without a file to work with
437        if ( ! file_exists( $file ) )
438                return compact( 'ext', 'type', 'proper_filename' );
439
440        // We're able to validate images using GD
441        if ( $type && 0 === strpos( $type, 'image/' ) && function_exists('getimagesize') ) {
442
443                // Attempt to figure out what type of image it actually is
444                $imgstats = @getimagesize( $file );
445
446                // If getimagesize() knows what kind of image it really is and if the real MIME doesn't match the claimed MIME
447                if ( !empty($imgstats['mime']) && $imgstats['mime'] != $type ) {
448                        // This is a simplified array of MIMEs that getimagesize() can detect and their extensions
449                        // You shouldn't need to use this filter, but it's here just in case
450                        $mime_to_ext = apply_filters( 'getimagesize_mimes_to_exts', array(
451                                'image/jpeg' => 'jpg',
452                                'image/png'  => 'png',
453                                'image/gif'  => 'gif',
454                                'image/bmp'  => 'bmp',
455                                'image/tiff' => 'tif',
456                        ) );
457
458                        // Replace whatever is after the last period in the filename with the correct extension
459                        if ( ! empty( $mime_to_ext[ $imgstats['mime'] ] ) ) {
460                                $filename_parts = explode( '.', $filename );
461                                array_pop( $filename_parts );
462                                $filename_parts[] = $mime_to_ext[ $imgstats['mime'] ];
463                                $new_filename = implode( '.', $filename_parts );
464
465                                if ( $new_filename != $filename )
466                                        $proper_filename = $new_filename; // Mark that it changed
467
468                                // Redefine the extension / MIME
469                                $wp_filetype = wp_check_filetype( $new_filename, $mimes );
470                                extract( $wp_filetype );
471                        }
472                }
473        }
474
475        // Let plugins try and validate other types of files
476        // Should return an array in the style of array( 'ext' => $ext, 'type' => $type, 'proper_filename' => $proper_filename )
477        return apply_filters( 'wp_check_filetype_and_ext', compact( 'ext', 'type', 'proper_filename' ), $file, $filename, $mimes );
478}
479
480/**
481 * Retrieve list of allowed mime types and file extensions.
482 *
483 * @since 2.8.6
484 *
485 * @return array Array of mime types keyed by the file extension regex corresponding to those types.
486 */
487function get_allowed_mime_types() {
488        static $mimes = false;
489
490        if ( !$mimes ) {
491                // Accepted MIME types are set here as PCRE unless provided.
492                $mimes = apply_filters( 'upload_mimes', array(
493                'jpg|jpeg|jpe' => 'image/jpeg',
494                'gif' => 'image/gif',
495                'png' => 'image/png',
496                'bmp' => 'image/bmp',
497                'tif|tiff' => 'image/tiff',
498                'ico' => 'image/x-icon',
499                'asf|asx|wax|wmv|wmx' => 'video/asf',
500                'avi' => 'video/avi',
501                'divx' => 'video/divx',
502                'flv' => 'video/x-flv',
503                'mov|qt' => 'video/quicktime',
504                'mpeg|mpg|mpe' => 'video/mpeg',
505                'txt|asc|c|cc|h' => 'text/plain',
506                'csv' => 'text/csv',
507                'tsv' => 'text/tab-separated-values',
508                'ics' => 'text/calendar',
509                'rtx' => 'text/richtext',
510                'css' => 'text/css',
511                'htm|html' => 'text/html',
512                'mp3|m4a|m4b' => 'audio/mpeg',
513                'mp4|m4v' => 'video/mp4',
514                'ra|ram' => 'audio/x-realaudio',
515                'wav' => 'audio/wav',
516                'ogg|oga' => 'audio/ogg',
517                'ogv' => 'video/ogg',
518                'mid|midi' => 'audio/midi',
519                'wma' => 'audio/wma',
520                'mka' => 'audio/x-matroska',
521                'mkv' => 'video/x-matroska',
522                'rtf' => 'application/rtf',
523                'js' => 'application/javascript',
524                'pdf' => 'application/pdf',
525                'doc|docx' => 'application/msword',
526                'pot|pps|ppt|pptx|ppam|pptm|sldm|ppsm|potm' => 'application/vnd.ms-powerpoint',
527                'wri' => 'application/vnd.ms-write',
528                'xla|xls|xlsx|xlt|xlw|xlam|xlsb|xlsm|xltm' => 'application/vnd.ms-excel',
529                'mdb' => 'application/vnd.ms-access',
530                'mpp' => 'application/vnd.ms-project',
531                'docm|dotm' => 'application/vnd.ms-word',
532                'pptx|sldx|ppsx|potx' => 'application/vnd.openxmlformats-officedocument.presentationml',
533                'xlsx|xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml',
534                'docx|dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml',
535                'onetoc|onetoc2|onetmp|onepkg' => 'application/onenote',
536                'swf' => 'application/x-shockwave-flash',
537                'class' => 'application/java',
538                'tar' => 'application/x-tar',
539                'zip' => 'application/zip',
540                'gz|gzip' => 'application/x-gzip',
541                'rar' => 'application/rar',
542                '7z' => 'application/x-7z-compressed',
543                'exe' => 'application/x-msdownload',
544                // openoffice formats
545                'odt' => 'application/vnd.oasis.opendocument.text',
546                'odp' => 'application/vnd.oasis.opendocument.presentation',
547                'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
548                'odg' => 'application/vnd.oasis.opendocument.graphics',
549                'odc' => 'application/vnd.oasis.opendocument.chart',
550                'odb' => 'application/vnd.oasis.opendocument.database',
551                'odf' => 'application/vnd.oasis.opendocument.formula',
552                // wordperfect formats
553                'wp|wpd' => 'application/wordperfect',
554                ) );
555        }
556
557        return $mimes;
558}
559
560/**
561 * File validates against allowed set of defined rules.
562 *
563 * A return value of '1' means that the $file contains either '..' or './'. A
564 * return value of '2' means that the $file contains ':' after the first
565 * character. A return value of '3' means that the file is not in the allowed
566 * files list.
567 *
568 * @since 1.2.0
569 *
570 * @param string $file File path.
571 * @param array $allowed_files List of allowed files.
572 * @return int 0 means nothing is wrong, greater than 0 means something was wrong.
573 */
574function validate_file( $file, $allowed_files = '' ) {
575        if ( false !== strpos( $file, '..' ) )
576                return 1;
577
578        if ( false !== strpos( $file, './' ) )
579                return 1;
580
581        if ( ! empty( $allowed_files ) && ! in_array( $file, $allowed_files ) )
582                return 3;
583
584        if (':' == substr( $file, 1, 1 ) )
585                return 2;
586
587        return 0;
588}
589
590/**
591 * Strip close comment and close php tags from file headers used by WP.
592 * See http://core.trac.wordpress.org/ticket/8497
593 *
594 * @since 2.8.0
595 *
596 * @param string $str
597 * @return string
598 */
599function _cleanup_header_comment($str) {
600        return trim(preg_replace("/\s*(?:\*\/|\?>).*/", '', $str));
601}
602
603/**
604 * Retrieve metadata from a file.
605 *
606 * Searches for metadata in the first 8kiB of a file, such as a plugin or theme.
607 * Each piece of metadata must be on its own line. Fields can not span multiple
608 * lines, the value will get cut at the end of the first line.
609 *
610 * If the file data is not within that first 8kiB, then the author should correct
611 * their plugin file and move the data headers to the top.
612 *
613 * @see http://codex.wordpress.org/File_Header
614 *
615 * @since 2.9.0
616 * @param string $file Path to the file
617 * @param array $default_headers List of headers, in the format array('HeaderKey' => 'Header Name')
618 * @param string $context If specified adds filter hook "extra_{$context}_headers"
619 */
620function get_file_data( $file, $default_headers, $context = '' ) {
621        // We don't need to write to the file, so just open for reading.
622        $fp = fopen( $file, 'r' );
623
624        // Pull only the first 8kiB of the file in.
625        $file_data = fread( $fp, 8192 );
626
627        // PHP will close file handle, but we are good citizens.
628        fclose( $fp );
629
630        if ( $context != '' ) {
631                $extra_headers = apply_filters( "extra_{$context}_headers", array() );
632
633                $extra_headers = array_flip( $extra_headers );
634                foreach( $extra_headers as $key=>$value ) {
635                        $extra_headers[$key] = $key;
636                }
637                $all_headers = array_merge( $extra_headers, (array) $default_headers );
638        } else {
639                $all_headers = $default_headers;
640        }
641
642        foreach ( $all_headers as $field => $regex ) {
643                preg_match( '/^[ \t\/*#@]*' . preg_quote( $regex, '/' ) . ':(.*)$/mi', $file_data, ${$field});
644                if ( !empty( ${$field} ) )
645                        ${$field} = _cleanup_header_comment( ${$field}[1] );
646                else
647                        ${$field} = '';
648        }
649
650        $file_data = compact( array_keys( $all_headers ) );
651
652        return $file_data;
653}