Completed
Push — master ( 95305c...fdbbfe )
by Andreas
08:00
created

io.php ➔ io_lock()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 4
nop 1
dl 0
loc 18
rs 9.3554
c 0
b 0
f 0
1
<?php
2
/**
3
 * File IO functions
4
 *
5
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6
 * @author     Andreas Gohr <[email protected]>
7
 */
8
9
if(!defined('DOKU_INC')) die('meh.');
10
11
/**
12
 * Removes empty directories
13
 *
14
 * Sends IO_NAMESPACE_DELETED events for 'pages' and 'media' namespaces.
15
 * Event data:
16
 * $data[0]    ns: The colon separated namespace path minus the trailing page name.
17
 * $data[1]    ns_type: 'pages' or 'media' namespace tree.
18
 *
19
 * @param string $id      - a pageid, the namespace of that id will be tried to deleted
20
 * @param string $basedir - the config name of the type to delete (datadir or mediadir usally)
21
 * @return bool - true if at least one namespace was deleted
22
 *
23
 * @author  Andreas Gohr <[email protected]>
24
 * @author Ben Coburn <[email protected]>
25
 */
26
function io_sweepNS($id,$basedir='datadir'){
27
    global $conf;
28
    $types = array ('datadir'=>'pages', 'mediadir'=>'media');
29
    $ns_type = (isset($types[$basedir])?$types[$basedir]:false);
30
31
    $delone = false;
32
33
    //scan all namespaces
34
    while(($id = getNS($id)) !== false){
35
        $dir = $conf[$basedir].'/'.utf8_encodeFN(str_replace(':','/',$id));
36
37
        //try to delete dir else return
38
        if(@rmdir($dir)) {
39
            if ($ns_type!==false) {
40
                $data = array($id, $ns_type);
41
                $delone = true; // we deleted at least one dir
42
                trigger_event('IO_NAMESPACE_DELETED', $data);
43
            }
44
        } else { return $delone; }
45
    }
46
    return $delone;
47
}
48
49
/**
50
 * Used to read in a DokuWiki page from file, and send IO_WIKIPAGE_READ events.
51
 *
52
 * Generates the action event which delegates to io_readFile().
53
 * Action plugins are allowed to modify the page content in transit.
54
 * The file path should not be changed.
55
 *
56
 * Event data:
57
 * $data[0]    The raw arguments for io_readFile as an array.
58
 * $data[1]    ns: The colon separated namespace path minus the trailing page name. (false if root ns)
59
 * $data[2]    page_name: The wiki page name.
60
 * $data[3]    rev: The page revision, false for current wiki pages.
61
 *
62
 * @author Ben Coburn <[email protected]>
63
 *
64
 * @param string   $file filename
65
 * @param string   $id page id
66
 * @param bool|int $rev revision timestamp
67
 * @return string
68
 */
69
function io_readWikiPage($file, $id, $rev=false) {
70
    if (empty($rev)) { $rev = false; }
71
    $data = array(array($file, true), getNS($id), noNS($id), $rev);
72
    return trigger_event('IO_WIKIPAGE_READ', $data, '_io_readWikiPage_action', false);
73
}
74
75
/**
76
 * Callback adapter for io_readFile().
77
 *
78
 * @author Ben Coburn <[email protected]>
79
 *
80
 * @param array $data event data
81
 * @return string
82
 */
83
function _io_readWikiPage_action($data) {
84
    if (is_array($data) && is_array($data[0]) && count($data[0])===2) {
85
        return call_user_func_array('io_readFile', $data[0]);
86
    } else {
87
        return ''; //callback error
88
    }
89
}
90
91
/**
92
 * Returns content of $file as cleaned string.
93
 *
94
 * Uses gzip if extension is .gz
95
 *
96
 * If you want to use the returned value in unserialize
97
 * be sure to set $clean to false!
98
 *
99
 * @author  Andreas Gohr <[email protected]>
100
 *
101
 * @param string $file  filename
102
 * @param bool   $clean
103
 * @return string|bool the file contents or false on error
104
 */
105
function io_readFile($file,$clean=true){
106
    $ret = '';
107
    if(file_exists($file)){
108
        if(substr($file,-3) == '.gz'){
109
            if(!DOKU_HAS_GZIP) return false;
110
            $ret = gzfile($file);
111
            if(is_array($ret)) $ret = join('', $ret);
112
        }else if(substr($file,-4) == '.bz2'){
113
            if(!DOKU_HAS_BZIP) return false;
114
            $ret = bzfile($file);
115
        }else{
116
            $ret = file_get_contents($file);
117
        }
118
    }
119
    if($ret === null) return false;
120
    if($ret !== false && $clean){
121
        return cleanText($ret);
122
    }else{
123
        return $ret;
124
    }
125
}
126
/**
127
 * Returns the content of a .bz2 compressed file as string
128
 *
129
 * @author marcel senf <[email protected]>
130
 * @author  Andreas Gohr <[email protected]>
131
 *
132
 * @param string $file filename
133
 * @param bool   $array return array of lines
134
 * @return string|array|bool content or false on error
135
 */
136
function bzfile($file, $array=false) {
137
    $bz = bzopen($file,"r");
138
    if($bz === false) return false;
139
140
    if($array) $lines = array();
141
    $str = '';
142
    while (!feof($bz)) {
143
        //8192 seems to be the maximum buffersize?
144
        $buffer = bzread($bz,8192);
145
        if(($buffer === false) || (bzerrno($bz) !== 0)) {
146
            return false;
147
        }
148
        $str = $str . $buffer;
149
        if($array) {
150
            $pos = strpos($str, "\n");
151
            while($pos !== false) {
152
                $lines[] = substr($str, 0, $pos+1);
0 ignored issues
show
Bug introduced by
The variable $lines does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
153
                $str = substr($str, $pos+1);
154
                $pos = strpos($str, "\n");
155
            }
156
        }
157
    }
158
    bzclose($bz);
159
    if($array) {
160
        if($str !== '') $lines[] = $str;
161
        return $lines;
162
    }
163
    return $str;
164
}
165
166
/**
167
 * Used to write out a DokuWiki page to file, and send IO_WIKIPAGE_WRITE events.
168
 *
169
 * This generates an action event and delegates to io_saveFile().
170
 * Action plugins are allowed to modify the page content in transit.
171
 * The file path should not be changed.
172
 * (The append parameter is set to false.)
173
 *
174
 * Event data:
175
 * $data[0]    The raw arguments for io_saveFile as an array.
176
 * $data[1]    ns: The colon separated namespace path minus the trailing page name. (false if root ns)
177
 * $data[2]    page_name: The wiki page name.
178
 * $data[3]    rev: The page revision, false for current wiki pages.
179
 *
180
 * @author Ben Coburn <[email protected]>
181
 *
182
 * @param string $file      filename
183
 * @param string $content
184
 * @param string $id        page id
185
 * @param int|bool $rev timestamp of revision
186
 * @return bool
187
 */
188
function io_writeWikiPage($file, $content, $id, $rev=false) {
189
    if (empty($rev)) { $rev = false; }
190
    if ($rev===false) { io_createNamespace($id); } // create namespaces as needed
191
    $data = array(array($file, $content, false), getNS($id), noNS($id), $rev);
192
    return trigger_event('IO_WIKIPAGE_WRITE', $data, '_io_writeWikiPage_action', false);
193
}
194
195
/**
196
 * Callback adapter for io_saveFile().
197
 * @author Ben Coburn <[email protected]>
198
 *
199
 * @param array $data event data
200
 * @return bool
201
 */
202
function _io_writeWikiPage_action($data) {
203
    if (is_array($data) && is_array($data[0]) && count($data[0])===3) {
204
        $ok = call_user_func_array('io_saveFile', $data[0]);
205
        // for attic files make sure the file has the mtime of the revision
206
        if($ok && is_int($data[3]) && $data[3] > 0) {
207
            @touch($data[0][0], $data[3]);
208
        }
209
        return $ok;
210
    } else {
211
        return false; //callback error
212
    }
213
}
214
215
/**
216
 * Internal function to save contents to a file.
217
 *
218
 * @author  Andreas Gohr <[email protected]>
219
 *
220
 * @param string $file filename path to file
221
 * @param string $content
222
 * @param bool   $append
223
 * @return bool true on success, otherwise false
224
 */
225
function _io_saveFile($file, $content, $append) {
226
    global $conf;
227
    $mode = ($append) ? 'ab' : 'wb';
228
    $fileexists = file_exists($file);
229
230
    if(substr($file,-3) == '.gz'){
231
        if(!DOKU_HAS_GZIP) return false;
232
        $fh = @gzopen($file,$mode.'9');
233
        if(!$fh) return false;
234
        gzwrite($fh, $content);
235
        gzclose($fh);
236
    }else if(substr($file,-4) == '.bz2'){
237
        if(!DOKU_HAS_BZIP) return false;
238
        if($append) {
239
            $bzcontent = bzfile($file);
240
            if($bzcontent === false) return false;
241
            $content = $bzcontent.$content;
242
        }
243
        $fh = @bzopen($file,'w');
244
        if(!$fh) return false;
245
        bzwrite($fh, $content);
246
        bzclose($fh);
247
    }else{
248
        $fh = @fopen($file,$mode);
249
        if(!$fh) return false;
250
        fwrite($fh, $content);
251
        fclose($fh);
252
    }
253
254
    if(!$fileexists and !empty($conf['fperm'])) chmod($file, $conf['fperm']);
255
    return true;
256
}
257
258
/**
259
 * Saves $content to $file.
260
 *
261
 * If the third parameter is set to true the given content
262
 * will be appended.
263
 *
264
 * Uses gzip if extension is .gz
265
 * and bz2 if extension is .bz2
266
 *
267
 * @author  Andreas Gohr <[email protected]>
268
 *
269
 * @param string $file filename path to file
270
 * @param string $content
271
 * @param bool   $append
272
 * @return bool true on success, otherwise false
273
 */
274
function io_saveFile($file, $content, $append=false) {
275
    io_makeFileDir($file);
276
    io_lock($file);
277
    if(!_io_saveFile($file, $content, $append)) {
278
        msg("Writing $file failed",-1);
279
        io_unlock($file);
280
        return false;
281
    }
282
    io_unlock($file);
283
    return true;
284
}
285
286
/**
287
 * Replace one or more occurrences of a line in a file.
288
 *
289
 * The default, when $maxlines is 0 is to delete all matching lines then append a single line.
290
 * A regex that matches any part of the line will remove the entire line in this mode.
291
 * Captures in $newline are not available.
292
 *
293
 * Otherwise each line is matched and replaced individually, up to the first $maxlines lines
294
 * or all lines if $maxlines is -1. If $regex is true then captures can be used in $newline.
295
 *
296
 * Be sure to include the trailing newline in $oldline when replacing entire lines.
297
 *
298
 * Uses gzip if extension is .gz
299
 * and bz2 if extension is .bz2
300
 *
301
 * @author Steven Danz <[email protected]>
302
 * @author Christopher Smith <[email protected]>
303
 * @author Patrick Brown <[email protected]>
304
 *
305
 * @param string $file     filename
306
 * @param string $oldline  exact linematch to remove
307
 * @param string $newline  new line to insert
308
 * @param bool   $regex    use regexp?
309
 * @param int    $maxlines number of occurrences of the line to replace
310
 * @return bool true on success
311
 */
312
function io_replaceInFile($file, $oldline, $newline, $regex=false, $maxlines=0) {
313
    if ((string)$oldline === '') {
314
        trigger_error('$oldline parameter cannot be empty in io_replaceInFile()', E_USER_WARNING);
315
        return false;
316
    }
317
318
    if (!file_exists($file)) return true;
319
320
    io_lock($file);
321
322
    // load into array
323
    if(substr($file,-3) == '.gz'){
324
        if(!DOKU_HAS_GZIP) return false;
325
        $lines = gzfile($file);
326
    }else if(substr($file,-4) == '.bz2'){
327
        if(!DOKU_HAS_BZIP) return false;
328
        $lines = bzfile($file, true);
329
    }else{
330
        $lines = file($file);
331
    }
332
333
    // make non-regexes into regexes
334
    $pattern = $regex ? $oldline : '/^'.preg_quote($oldline,'/').'$/';
335
    $replace = $regex ? $newline : addcslashes($newline, '\$');
336
337
    // remove matching lines
338
    if ($maxlines > 0) {
339
        $count = 0;
340
        $matched = 0;
341
        foreach($lines as $i => $line) {
0 ignored issues
show
Bug introduced by
The expression $lines of type false|array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
342
            if($count >= $maxlines) break;
343
            // $matched will be set to 0|1 depending on whether pattern is matched and line replaced
344
            $lines[$i] = preg_replace($pattern, $replace, $line, -1, $matched);
345
            if ($matched) $count++;
0 ignored issues
show
Bug Best Practice introduced by
The expression $matched of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
346
        }
347
    } else if ($maxlines == 0) {
348
        $lines = preg_grep($pattern, $lines, PREG_GREP_INVERT);
349
350
        if ((string)$newline !== ''){
351
            $lines[] = $newline;
352
        }
353
    } else {
354
        $lines = preg_replace($pattern, $replace, $lines);
355
    }
356
357
    if(count($lines)){
358
        if(!_io_saveFile($file, join('',$lines), false)) {
359
            msg("Removing content from $file failed",-1);
360
            io_unlock($file);
361
            return false;
362
        }
363
    }else{
364
        @unlink($file);
365
    }
366
367
    io_unlock($file);
368
    return true;
369
}
370
371
/**
372
 * Delete lines that match $badline from $file.
373
 *
374
 * Be sure to include the trailing newline in $badline
375
 *
376
 * @author Patrick Brown <[email protected]>
377
 *
378
 * @param string $file    filename
379
 * @param string $badline exact linematch to remove
380
 * @param bool   $regex   use regexp?
381
 * @return bool true on success
382
 */
383
function io_deleteFromFile($file,$badline,$regex=false){
384
    return io_replaceInFile($file,$badline,null,$regex,0);
385
}
386
387
/**
388
 * Tries to lock a file
389
 *
390
 * Locking is only done for io_savefile and uses directories
391
 * inside $conf['lockdir']
392
 *
393
 * It waits maximal 3 seconds for the lock, after this time
394
 * the lock is assumed to be stale and the function goes on
395
 *
396
 * @author Andreas Gohr <[email protected]>
397
 *
398
 * @param string $file filename
399
 */
400
function io_lock($file){
401
    global $conf;
402
403
    $lockDir = $conf['lockdir'].'/'.md5($file);
404
    @ignore_user_abort(1);
405
406
    $timeStart = time();
407
    do {
408
        //waited longer than 3 seconds? -> stale lock
409
        if ((time() - $timeStart) > 3) break;
410
        $locked = @mkdir($lockDir, $conf['dmode']);
411
        if($locked){
412
            if(!empty($conf['dperm'])) chmod($lockDir, $conf['dperm']);
413
            break;
414
        }
415
        usleep(50);
416
    } while ($locked === false);
417
}
418
419
/**
420
 * Unlocks a file
421
 *
422
 * @author Andreas Gohr <[email protected]>
423
 *
424
 * @param string $file filename
425
 */
426
function io_unlock($file){
427
    global $conf;
428
429
    $lockDir = $conf['lockdir'].'/'.md5($file);
430
    @rmdir($lockDir);
431
    @ignore_user_abort(0);
432
}
433
434
/**
435
 * Create missing namespace directories and send the IO_NAMESPACE_CREATED events
436
 * in the order of directory creation. (Parent directories first.)
437
 *
438
 * Event data:
439
 * $data[0]    ns: The colon separated namespace path minus the trailing page name.
440
 * $data[1]    ns_type: 'pages' or 'media' namespace tree.
441
 *
442
 * @author Ben Coburn <[email protected]>
443
 *
444
 * @param string $id page id
445
 * @param string $ns_type 'pages' or 'media'
446
 */
447
function io_createNamespace($id, $ns_type='pages') {
448
    // verify ns_type
449
    $types = array('pages'=>'wikiFN', 'media'=>'mediaFN');
450
    if (!isset($types[$ns_type])) {
451
        trigger_error('Bad $ns_type parameter for io_createNamespace().');
452
        return;
453
    }
454
    // make event list
455
    $missing = array();
456
    $ns_stack = explode(':', $id);
457
    $ns = $id;
458
    $tmp = dirname( $file = call_user_func($types[$ns_type], $ns) );
459
    while (!@is_dir($tmp) && !(file_exists($tmp) && !is_dir($tmp))) {
460
        array_pop($ns_stack);
461
        $ns = implode(':', $ns_stack);
462
        if (strlen($ns)==0) { break; }
463
        $missing[] = $ns;
464
        $tmp = dirname(call_user_func($types[$ns_type], $ns));
465
    }
466
    // make directories
467
    io_makeFileDir($file);
468
    // send the events
469
    $missing = array_reverse($missing); // inside out
470
    foreach ($missing as $ns) {
471
        $data = array($ns, $ns_type);
472
        trigger_event('IO_NAMESPACE_CREATED', $data);
473
    }
474
}
475
476
/**
477
 * Create the directory needed for the given file
478
 *
479
 * @author  Andreas Gohr <[email protected]>
480
 *
481
 * @param string $file file name
482
 */
483
function io_makeFileDir($file){
484
    $dir = dirname($file);
485
    if(!@is_dir($dir)){
486
        io_mkdir_p($dir) || msg("Creating directory $dir failed",-1);
487
    }
488
}
489
490
/**
491
 * Creates a directory hierachy.
492
 *
493
 * @link    http://php.net/manual/en/function.mkdir.php
494
 * @author  <[email protected]>
495
 * @author  Andreas Gohr <[email protected]>
496
 *
497
 * @param string $target filename
498
 * @return bool|int|string
499
 */
500
function io_mkdir_p($target){
501
    global $conf;
502
    if (@is_dir($target)||empty($target)) return 1; // best case check first
503
    if (file_exists($target) && !is_dir($target)) return 0;
504
    //recursion
505
    if (io_mkdir_p(substr($target,0,strrpos($target,'/')))){
506
        $ret = @mkdir($target,$conf['dmode']); // crawl back up & create dir tree
507
        if($ret && !empty($conf['dperm'])) chmod($target, $conf['dperm']);
508
        return $ret;
509
    }
510
    return 0;
511
}
512
513
/**
514
 * Recursively delete a directory
515
 *
516
 * @author Andreas Gohr <[email protected]>
517
 * @param string $path
518
 * @param bool   $removefiles defaults to false which will delete empty directories only
519
 * @return bool
520
 */
521
function io_rmdir($path, $removefiles = false) {
522
    if(!is_string($path) || $path == "") return false;
523
    if(!file_exists($path)) return true; // it's already gone or was never there, count as success
524
525
    if(is_dir($path) && !is_link($path)) {
526
        $dirs  = array();
527
        $files = array();
528
529
        if(!$dh = @opendir($path)) return false;
530
        while(false !== ($f = readdir($dh))) {
531
            if($f == '..' || $f == '.') continue;
532
533
            // collect dirs and files first
534
            if(is_dir("$path/$f") && !is_link("$path/$f")) {
535
                $dirs[] = "$path/$f";
536
            } else if($removefiles) {
537
                $files[] = "$path/$f";
538
            } else {
539
                return false; // abort when non empty
540
            }
541
542
        }
543
        closedir($dh);
544
545
        // now traverse into  directories first
546
        foreach($dirs as $dir) {
547
            if(!io_rmdir($dir, $removefiles)) return false; // abort on any error
548
        }
549
550
        // now delete files
551
        foreach($files as $file) {
552
            if(!@unlink($file)) return false; //abort on any error
553
        }
554
555
        // remove self
556
        return @rmdir($path);
557
    } else if($removefiles) {
558
        return @unlink($path);
559
    }
560
    return false;
561
}
562
563
/**
564
 * Creates a unique temporary directory and returns
565
 * its path.
566
 *
567
 * @author Michael Klier <[email protected]>
568
 *
569
 * @return false|string path to new directory or false
570
 */
571
function io_mktmpdir() {
572
    global $conf;
573
574
    $base = $conf['tmpdir'];
575
    $dir  = md5(uniqid(mt_rand(), true));
576
    $tmpdir = $base.'/'.$dir;
577
578
    if(io_mkdir_p($tmpdir)) {
579
        return($tmpdir);
580
    } else {
581
        return false;
582
    }
583
}
584
585
/**
586
 * downloads a file from the net and saves it
587
 *
588
 * if $useAttachment is false,
589
 * - $file is the full filename to save the file, incl. path
590
 * - if successful will return true, false otherwise
591
 *
592
 * if $useAttachment is true,
593
 * - $file is the directory where the file should be saved
594
 * - if successful will return the name used for the saved file, false otherwise
595
 *
596
 * @author Andreas Gohr <[email protected]>
597
 * @author Chris Smith <[email protected]>
598
 *
599
 * @param string $url           url to download
600
 * @param string $file          path to file or directory where to save
601
 * @param bool   $useAttachment if true: try to use name of download, uses otherwise $defaultName, false: uses $file as path to file
602
 * @param string $defaultName   fallback for if using $useAttachment
603
 * @param int    $maxSize       maximum file size
604
 * @return bool|string          if failed false, otherwise true or the name of the file in the given dir
605
 */
606
function io_download($url,$file,$useAttachment=false,$defaultName='',$maxSize=2097152){
607
    global $conf;
608
    $http = new DokuHTTPClient();
609
    $http->max_bodysize = $maxSize;
610
    $http->timeout = 25; //max. 25 sec
611
    $http->keep_alive = false; // we do single ops here, no need for keep-alive
612
613
    $data = $http->get($url);
614
    if(!$data) return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression $data of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
615
616
    $name = '';
617
    if ($useAttachment) {
618
        if (isset($http->resp_headers['content-disposition'])) {
619
            $content_disposition = $http->resp_headers['content-disposition'];
620
            $match=array();
621
            if (is_string($content_disposition) &&
622
                    preg_match('/attachment;\s*filename\s*=\s*"([^"]*)"/i', $content_disposition, $match)) {
623
624
                $name = utf8_basename($match[1]);
625
            }
626
627
        }
628
629
        if (!$name) {
630
            if (!$defaultName) return false;
631
            $name = $defaultName;
632
        }
633
634
        $file = $file.$name;
635
    }
636
637
    $fileexists = file_exists($file);
638
    $fp = @fopen($file,"w");
639
    if(!$fp) return false;
640
    fwrite($fp,$data);
641
    fclose($fp);
642
    if(!$fileexists and $conf['fperm']) chmod($file, $conf['fperm']);
643
    if ($useAttachment) return $name;
644
    return true;
645
}
646
647
/**
648
 * Windows compatible rename
649
 *
650
 * rename() can not overwrite existing files on Windows
651
 * this function will use copy/unlink instead
652
 *
653
 * @param string $from
654
 * @param string $to
655
 * @return bool succes or fail
656
 */
657
function io_rename($from,$to){
658
    global $conf;
659
    if(!@rename($from,$to)){
660
        if(@copy($from,$to)){
661
            if($conf['fperm']) chmod($to, $conf['fperm']);
662
            @unlink($from);
663
            return true;
664
        }
665
        return false;
666
    }
667
    return true;
668
}
669
670
/**
671
 * Runs an external command with input and output pipes.
672
 * Returns the exit code from the process.
673
 *
674
 * @author Tom N Harris <[email protected]>
675
 *
676
 * @param string $cmd
677
 * @param string $input  input pipe
678
 * @param string $output output pipe
679
 * @return int exit code from process
680
 */
681
function io_exec($cmd, $input, &$output){
682
    $descspec = array(
683
            0=>array("pipe","r"),
684
            1=>array("pipe","w"),
685
            2=>array("pipe","w"));
686
    $ph = proc_open($cmd, $descspec, $pipes);
687
    if(!$ph) return -1;
688
    fclose($pipes[2]); // ignore stderr
689
    fwrite($pipes[0], $input);
690
    fclose($pipes[0]);
691
    $output = stream_get_contents($pipes[1]);
692
    fclose($pipes[1]);
693
    return proc_close($ph);
694
}
695
696
/**
697
 * Search a file for matching lines
698
 *
699
 * This is probably not faster than file()+preg_grep() but less
700
 * memory intensive because not the whole file needs to be loaded
701
 * at once.
702
 *
703
 * @author Andreas Gohr <[email protected]>
704
 * @param  string $file    The file to search
705
 * @param  string $pattern PCRE pattern
706
 * @param  int    $max     How many lines to return (0 for all)
707
 * @param  bool   $backref When true returns array with backreferences instead of lines
708
 * @return array matching lines or backref, false on error
709
 */
710
function io_grep($file,$pattern,$max=0,$backref=false){
711
    $fh = @fopen($file,'r');
712
    if(!$fh) return false;
713
    $matches = array();
714
715
    $cnt  = 0;
716
    $line = '';
717
    while (!feof($fh)) {
718
        $line .= fgets($fh, 4096);  // read full line
719
        if(substr($line,-1) != "\n") continue;
720
721
        // check if line matches
722
        if(preg_match($pattern,$line,$match)){
723
            if($backref){
724
                $matches[] = $match;
725
            }else{
726
                $matches[] = $line;
727
            }
728
            $cnt++;
729
        }
730
        if($max && $max == $cnt) break;
731
        $line = '';
732
    }
733
    fclose($fh);
734
    return $matches;
735
}
736
737
738
/**
739
 * Get size of contents of a file, for a compressed file the uncompressed size
740
 * Warning: reading uncompressed size of content of bz-files requires uncompressing
741
 *
742
 * @author  Gerrit Uitslag <[email protected]>
743
 *
744
 * @param string $file filename path to file
745
 * @return int size of file
746
 */
747
function io_getSizeFile($file) {
748
    if (!file_exists($file)) return 0;
749
750
    if(substr($file,-3) == '.gz'){
751
        $fp = @fopen($file, "rb");
752
        if($fp === false) return 0;
753
754
        fseek($fp, -4, SEEK_END);
755
        $buffer = fread($fp, 4);
756
        fclose($fp);
757
        $array = unpack("V", $buffer);
758
        $uncompressedsize = end($array);
759
    }else if(substr($file,-4) == '.bz2'){
760
        if(!DOKU_HAS_BZIP) return 0;
761
762
        $bz = bzopen($file,"r");
763
        if($bz === false) return 0;
764
765
        $uncompressedsize = 0;
766
        while (!feof($bz)) {
767
            //8192 seems to be the maximum buffersize?
768
            $buffer = bzread($bz,8192);
769
            if(($buffer === false) || (bzerrno($bz) !== 0)) {
770
                return 0;
771
            }
772
            $uncompressedsize += strlen($buffer);
773
        }
774
    }else{
775
        $uncompressedsize = filesize($file);
776
    }
777
778
    return $uncompressedsize;
779
 }
780