Issues (847)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

inc/search.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * DokuWiki search functions
4
 *
5
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6
 * @author     Andreas Gohr <[email protected]>
7
 */
8
9
use dokuwiki\Utf8\Sort;
10
11
/**
12
 * Recurse directory
13
 *
14
 * This function recurses into a given base directory
15
 * and calls the supplied function for each file and directory
16
 *
17
 * @param   array    &$data The results of the search are stored here
18
 * @param   string    $base Where to start the search
19
 * @param   callback  $func Callback (function name or array with object,method)
20
 * @param   array     $opts option array will be given to the Callback
21
 * @param   string    $dir  Current directory beyond $base
22
 * @param   int       $lvl  Recursion Level
23
 * @param   mixed     $sort 'natural' to use natural order sorting (default);
24
 *                          'date' to sort by filemtime; leave empty to skip sorting.
25
 * @author  Andreas Gohr <[email protected]>
26
 */
27
function search(&$data,$base,$func,$opts,$dir='',$lvl=1,$sort='natural'){
28
    $dirs   = array();
29
    $files  = array();
30
    $filepaths = array();
31
32
    // safeguard against runaways #1452
33
    if($base == '' || $base == '/') {
34
        throw new RuntimeException('No valid $base passed to search() - possible misconfiguration or bug');
35
    }
36
37
    //read in directories and files
38
    $dh = @opendir($base.'/'.$dir);
39
    if(!$dh) return;
40
    while(($file = readdir($dh)) !== false){
41
        if(preg_match('/^[\._]/',$file)) continue; //skip hidden files and upper dirs
42
        if(is_dir($base.'/'.$dir.'/'.$file)){
43
            $dirs[] = $dir.'/'.$file;
44
            continue;
45
        }
46
        $files[] = $dir.'/'.$file;
47
        $filepaths[] = $base.'/'.$dir.'/'.$file;
48
    }
49
    closedir($dh);
50
    if (!empty($sort)) {
51
        if ($sort == 'date') {
52
            @array_multisort(array_map('filemtime', $filepaths), SORT_NUMERIC, SORT_DESC, $files);
53
        } else /* natural */ {
54
            Sort::asortFN($files);
55
        }
56
        Sort::asortFN($dirs);
57
    }
58
59
    //give directories to userfunction then recurse
60
    foreach($dirs as $dir){
61
        if (call_user_func_array($func, array(&$data,$base,$dir,'d',$lvl,$opts))){
62
            search($data,$base,$func,$opts,$dir,$lvl+1,$sort);
63
        }
64
    }
65
    //now handle the files
66
    foreach($files as $file){
67
        call_user_func_array($func, array(&$data,$base,$file,'f',$lvl,$opts));
68
    }
69
}
70
71
/**
72
 * The following functions are userfunctions to use with the search
73
 * function above. This function is called for every found file or
74
 * directory. When a directory is given to the function it has to
75
 * decide if this directory should be traversed (true) or not (false)
76
 * The function has to accept the following parameters:
77
 *
78
 * array &$data  - Reference to the result data structure
79
 * string $base  - Base usually $conf['datadir']
80
 * string $file  - current file or directory relative to $base
81
 * string $type  - Type either 'd' for directory or 'f' for file
82
 * int    $lvl   - Current recursion depht
83
 * array  $opts  - option array as given to search()
84
 *
85
 * return values for files are ignored
86
 *
87
 * All functions should check the ACL for document READ rights
88
 * namespaces (directories) are NOT checked (when sneaky_index is 0) as this
89
 * would break the recursion (You can have an nonreadable dir over a readable
90
 * one deeper nested) also make sure to check the file type (for example
91
 * in case of lockfiles).
92
 */
93
94
/**
95
 * Searches for pages beginning with the given query
96
 *
97
 * @author Andreas Gohr <[email protected]>
98
 *
99
 * @param array $data
100
 * @param string $base
101
 * @param string $file
102
 * @param string $type
103
 * @param integer $lvl
104
 * @param array $opts
105
 *
106
 * @return bool
107
 */
108
function search_qsearch(&$data,$base,$file,$type,$lvl,$opts){
109
    $opts = array(
110
            'idmatch'   => '(^|:)'.preg_quote($opts['query'],'/').'/',
111
            'listfiles' => true,
112
            'pagesonly' => true,
113
            );
114
    return search_universal($data,$base,$file,$type,$lvl,$opts);
115
}
116
117
/**
118
 * Build the browsable index of pages
119
 *
120
 * $opts['ns'] is the currently viewed namespace
121
 *
122
 * @author  Andreas Gohr <[email protected]>
123
 *
124
 * @param array $data
125
 * @param string $base
126
 * @param string $file
127
 * @param string $type
128
 * @param integer $lvl
129
 * @param array $opts
130
 *
131
 * @return bool
132
 */
133
function search_index(&$data,$base,$file,$type,$lvl,$opts){
134
    global $conf;
135
    $ns = isset($opts['ns']) ? $opts['ns'] : '';
136
    $opts = array(
137
        'pagesonly' => true,
138
        'listdirs' => true,
139
        'listfiles' => empty($opts['nofiles']),
140
        'sneakyacl' => $conf['sneaky_index'],
141
        // Hacky, should rather use recmatch
142
        'depth' => preg_match('#^'.preg_quote($file, '#').'(/|$)#','/'.$ns) ? 0 : -1
143
    );
144
145
    return search_universal($data, $base, $file, $type, $lvl, $opts);
146
}
147
148
/**
149
 * List all namespaces
150
 *
151
 * @author  Andreas Gohr <[email protected]>
152
 *
153
 * @param array $data
154
 * @param string $base
155
 * @param string $file
156
 * @param string $type
157
 * @param integer $lvl
158
 * @param array $opts
159
 *
160
 * @return bool
161
 */
162
function search_namespaces(&$data,$base,$file,$type,$lvl,$opts){
163
    $opts = array(
164
            'listdirs' => true,
165
            );
166
    return search_universal($data,$base,$file,$type,$lvl,$opts);
167
}
168
169
/**
170
 * List all mediafiles in a namespace
171
 *   $opts['depth']     recursion level, 0 for all
172
 *   $opts['showmsg']   shows message if invalid media id is used
173
 *   $opts['skipacl']   skip acl checking
174
 *   $opts['pattern']   check given pattern
175
 *   $opts['hash']      add hashes to result list
176
 *
177
 * @author  Andreas Gohr <[email protected]>
178
 *
179
 * @param array $data
180
 * @param string $base
181
 * @param string $file
182
 * @param string $type
183
 * @param integer $lvl
184
 * @param array $opts
185
 *
186
 * @return bool
187
 */
188
function search_media(&$data,$base,$file,$type,$lvl,$opts){
189
190
    //we do nothing with directories
191
    if($type == 'd') {
192
        if(empty($opts['depth'])) return true; // recurse forever
193
        $depth = substr_count($file,'/');
194
        if($depth >= $opts['depth']) return false; // depth reached
195
        return true;
196
    }
197
198
    $info         = array();
199
    $info['id']   = pathID($file,true);
200
    if($info['id'] != cleanID($info['id'])){
201
        if($opts['showmsg'])
202
            msg(hsc($info['id']).' is not a valid file name for DokuWiki - skipped',-1);
203
        return false; // skip non-valid files
204
    }
205
206
    //check ACL for namespace (we have no ACL for mediafiles)
207
    $info['perm'] = auth_quickaclcheck(getNS($info['id']).':*');
208
    if(empty($opts['skipacl']) && $info['perm'] < AUTH_READ){
209
        return false;
210
    }
211
212
    //check pattern filter
213
    if(!empty($opts['pattern']) && !@preg_match($opts['pattern'], $info['id'])){
214
        return false;
215
    }
216
217
    $info['file']     = \dokuwiki\Utf8\PhpString::basename($file);
218
    $info['size']     = filesize($base.'/'.$file);
219
    $info['mtime']    = filemtime($base.'/'.$file);
220
    $info['writable'] = is_writable($base.'/'.$file);
221
    if(preg_match("/\.(jpe?g|gif|png)$/",$file)){
222
        $info['isimg'] = true;
223
        $info['meta']  = new JpegMeta($base.'/'.$file);
224
    }else{
225
        $info['isimg'] = false;
226
    }
227
    if(!empty($opts['hash'])){
228
        $info['hash'] = md5(io_readFile(mediaFN($info['id']),false));
229
    }
230
231
    $data[] = $info;
232
233
    return false;
234
}
235
236
/**
237
 * List all mediafiles in a namespace
238
 *   $opts['depth']     recursion level, 0 for all
239
 *   $opts['showmsg']   shows message if invalid media id is used
240
 *   $opts['skipacl']   skip acl checking
241
 *   $opts['pattern']   check given pattern
242
 *   $opts['hash']      add hashes to result list
243
 *
244
 * @todo This is a temporary copy of search_media returning a list of MediaFile intances
245
 *
246
 * @param array $data
247
 * @param string $base
248
 * @param string $file
249
 * @param string $type
250
 * @param integer $lvl
251
 * @param array $opts
252
 *
253
 * @return bool
254
 */
255
function search_mediafiles(&$data,$base,$file,$type,$lvl,$opts){
256
257
    //we do nothing with directories
258
    if($type == 'd') {
259
        if(empty($opts['depth'])) return true; // recurse forever
260
        $depth = substr_count($file,'/');
261
        if($depth >= $opts['depth']) return false; // depth reached
262
        return true;
263
    }
264
265
    $id   = pathID($file,true);
266
    if($id != cleanID($id)){
267
        if($opts['showmsg'])
268
            msg(hsc($id).' is not a valid file name for DokuWiki - skipped',-1);
269
        return false; // skip non-valid files
270
    }
271
272
    //check ACL for namespace (we have no ACL for mediafiles)
273
    $info['perm'] = auth_quickaclcheck(getNS($id).':*');
0 ignored issues
show
Coding Style Comprehensibility introduced by
$info was never initialized. Although not strictly required by PHP, it is generally a good practice to add $info = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
274
    if(empty($opts['skipacl']) && $info['perm'] < AUTH_READ){
275
        return false;
276
    }
277
278
    //check pattern filter
279
    if(!empty($opts['pattern']) && !@preg_match($opts['pattern'], $id)){
280
        return false;
281
    }
282
283
    $data[] = new \dokuwiki\File\MediaFile($id);
284
    return false;
285
}
286
287
288
/**
289
 * This function just lists documents (for RSS namespace export)
290
 *
291
 * @author  Andreas Gohr <[email protected]>
292
 *
293
 * @param array $data
294
 * @param string $base
295
 * @param string $file
296
 * @param string $type
297
 * @param integer $lvl
298
 * @param array $opts
299
 *
300
 * @return bool
301
 */
302
function search_list(&$data,$base,$file,$type,$lvl,$opts){
303
    //we do nothing with directories
304
    if($type == 'd') return false;
305
    //only search txt files
306
    if(substr($file,-4) == '.txt'){
307
        //check ACL
308
        $id = pathID($file);
309
        if(auth_quickaclcheck($id) < AUTH_READ){
310
            return false;
311
        }
312
        $data[]['id'] = $id;
313
    }
314
    return false;
315
}
316
317
/**
318
 * Quicksearch for searching matching pagenames
319
 *
320
 * $opts['query'] is the search query
321
 *
322
 * @author  Andreas Gohr <[email protected]>
323
 *
324
 * @param array $data
325
 * @param string $base
326
 * @param string $file
327
 * @param string $type
328
 * @param integer $lvl
329
 * @param array $opts
330
 *
331
 * @return bool
332
 */
333
function search_pagename(&$data,$base,$file,$type,$lvl,$opts){
334
    //we do nothing with directories
335
    if($type == 'd') return true;
336
    //only search txt files
337
    if(substr($file,-4) != '.txt') return true;
338
339
    //simple stringmatching
340
    if (!empty($opts['query'])){
341
        if(strpos($file,$opts['query']) !== false){
342
            //check ACL
343
            $id = pathID($file);
344
            if(auth_quickaclcheck($id) < AUTH_READ){
345
                return false;
346
            }
347
            $data[]['id'] = $id;
348
        }
349
    }
350
    return true;
351
}
352
353
/**
354
 * Just lists all documents
355
 *
356
 * $opts['depth']   recursion level, 0 for all
357
 * $opts['hash']    do md5 sum of content?
358
 * $opts['skipacl'] list everything regardless of ACL
359
 *
360
 * @author  Andreas Gohr <[email protected]>
361
 *
362
 * @param array $data
363
 * @param string $base
364
 * @param string $file
365
 * @param string $type
366
 * @param integer $lvl
367
 * @param array $opts
368
 *
369
 * @return bool
370
 */
371
function search_allpages(&$data,$base,$file,$type,$lvl,$opts){
372
    if(isset($opts['depth']) && $opts['depth']){
373
        $parts = explode('/',ltrim($file,'/'));
374
        if(($type == 'd' && count($parts) >= $opts['depth'])
375
          || ($type != 'd' && count($parts) > $opts['depth'])){
376
            return false; // depth reached
377
        }
378
    }
379
380
    //we do nothing with directories
381
    if($type == 'd'){
382
        return true;
383
    }
384
385
    //only search txt files
386
    if(substr($file,-4) != '.txt') return true;
387
388
    $item = array();
389
    $item['id']   = pathID($file);
390
    if(empty($opts['skipacl']) && auth_quickaclcheck($item['id']) < AUTH_READ){
391
        return false;
392
    }
393
394
    $item['rev']   = filemtime($base.'/'.$file);
395
    $item['mtime'] = $item['rev'];
396
    $item['size']  = filesize($base.'/'.$file);
397
    if(!empty($opts['hash'])){
398
        $item['hash'] = md5(trim(rawWiki($item['id'])));
399
    }
400
401
    $data[] = $item;
402
    return true;
403
}
404
405
/* ------------- helper functions below -------------- */
406
407
/**
408
 * fulltext sort
409
 *
410
 * Callback sort function for use with usort to sort the data
411
 * structure created by search_fulltext. Sorts descending by count
412
 *
413
 * @author  Andreas Gohr <[email protected]>
414
 *
415
 * @param array $a
416
 * @param array $b
417
 *
418
 * @return int
419
 */
420
function sort_search_fulltext($a,$b){
421
    if($a['count'] > $b['count']){
422
        return -1;
423
    }elseif($a['count'] < $b['count']){
424
        return 1;
425
    }else{
426
        return Sort::strcmp($a['id'],$b['id']);
427
    }
428
}
429
430
/**
431
 * translates a document path to an ID
432
 *
433
 * @author  Andreas Gohr <[email protected]>
434
 * @todo    move to pageutils
435
 *
436
 * @param string $path
437
 * @param bool $keeptxt
438
 *
439
 * @return mixed|string
440
 */
441
function pathID($path,$keeptxt=false){
442
    $id = utf8_decodeFN($path);
443
    $id = str_replace('/',':',$id);
444
    if(!$keeptxt) $id = preg_replace('#\.txt$#','',$id);
445
    $id = trim($id, ':');
446
    return $id;
447
}
448
449
450
/**
451
 * This is a very universal callback for the search() function, replacing
452
 * many of the former individual functions at the cost of a more complex
453
 * setup.
454
 *
455
 * How the function behaves, depends on the options passed in the $opts
456
 * array, where the following settings can be used.
457
 *
458
 * depth      int     recursion depth. 0 for unlimited                       (default: 0)
459
 * keeptxt    bool    keep .txt extension for IDs                            (default: false)
460
 * listfiles  bool    include files in listing                               (default: false)
461
 * listdirs   bool    include namespaces in listing                          (default: false)
462
 * pagesonly  bool    restrict files to pages                                (default: false)
463
 * skipacl    bool    do not check for READ permission                       (default: false)
464
 * sneakyacl  bool    don't recurse into nonreadable dirs                    (default: false)
465
 * hash       bool    create MD5 hash for files                              (default: false)
466
 * meta       bool    return file metadata                                   (default: false)
467
 * filematch  string  match files against this regexp                        (default: '', so accept everything)
468
 * idmatch    string  match full ID against this regexp                      (default: '', so accept everything)
469
 * dirmatch   string  match directory against this regexp when adding        (default: '', so accept everything)
470
 * nsmatch    string  match namespace against this regexp when adding        (default: '', so accept everything)
471
 * recmatch   string  match directory against this regexp when recursing     (default: '', so accept everything)
472
 * showmsg    bool    warn about non-ID files                                (default: false)
473
 * showhidden bool    show hidden files(e.g. by hidepages config) too        (default: false)
474
 * firsthead  bool    return first heading for pages                         (default: false)
475
 *
476
 * @param array &$data  - Reference to the result data structure
477
 * @param string $base  - Base usually $conf['datadir']
478
 * @param string $file  - current file or directory relative to $base
479
 * @param string $type  - Type either 'd' for directory or 'f' for file
480
 * @param int    $lvl   - Current recursion depht
481
 * @param array  $opts  - option array as given to search()
482
 * @return bool if this directory should be traversed (true) or not (false)
483
 *              return value is ignored for files
484
 *
485
 * @author Andreas Gohr <[email protected]>
486
 */
487
function search_universal(&$data,$base,$file,$type,$lvl,$opts){
488
    $item   = array();
489
    $return = true;
490
491
    // get ID and check if it is a valid one
492
    $item['id'] = pathID($file,($type == 'd' || !empty($opts['keeptxt'])));
493
    if($item['id'] != cleanID($item['id'])){
494
        if(!empty($opts['showmsg'])){
495
            msg(hsc($item['id']).' is not a valid file name for DokuWiki - skipped',-1);
496
        }
497
        return false; // skip non-valid files
498
    }
499
    $item['ns']  = getNS($item['id']);
500
501
    if($type == 'd') {
502
        // decide if to recursion into this directory is wanted
503
        if(empty($opts['depth'])){
504
            $return = true; // recurse forever
505
        }else{
506
            $depth = substr_count($file,'/');
507
            if($depth >= $opts['depth']){
508
                $return = false; // depth reached
509
            }else{
510
                $return = true;
511
            }
512
        }
513
514
        if ($return) {
515
            $match = empty($opts['recmatch']) || preg_match('/'.$opts['recmatch'].'/',$file);
516
            if (!$match) {
517
                return false; // doesn't match
518
            }
519
        }
520
    }
521
522
    // check ACL
523
    if(empty($opts['skipacl'])){
524
        if($type == 'd'){
525
            $item['perm'] = auth_quickaclcheck($item['id'].':*');
526
        }else{
527
            $item['perm'] = auth_quickaclcheck($item['id']); //FIXME check namespace for media files
528
        }
529
    }else{
530
        $item['perm'] = AUTH_DELETE;
531
    }
532
533
    // are we done here maybe?
534
    if($type == 'd'){
535
        if(empty($opts['listdirs'])) return $return;
536
        //neither list nor recurse forbidden items:
537
        if(empty($opts['skipacl']) && !empty($opts['sneakyacl']) && $item['perm'] < AUTH_READ) return false;
538
        if(!empty($opts['dirmatch']) && !preg_match('/'.$opts['dirmatch'].'/',$file)) return $return;
539
        if(!empty($opts['nsmatch']) && !preg_match('/'.$opts['nsmatch'].'/',$item['ns'])) return $return;
540
    }else{
541
        if(empty($opts['listfiles'])) return $return;
542
        if(empty($opts['skipacl']) && $item['perm'] < AUTH_READ) return $return;
543
        if(!empty($opts['pagesonly']) && (substr($file,-4) != '.txt')) return $return;
544
        if(empty($opts['showhidden']) && isHiddenPage($item['id'])) return $return;
545
        if(!empty($opts['filematch']) && !preg_match('/'.$opts['filematch'].'/',$file)) return $return;
546
        if(!empty($opts['idmatch']) && !preg_match('/'.$opts['idmatch'].'/',$item['id'])) return $return;
547
    }
548
549
    // still here? prepare the item
550
    $item['type']  = $type;
551
    $item['level'] = $lvl;
552
    $item['open']  = $return;
553
554
    if(!empty($opts['meta'])){
555
        $item['file']       = \dokuwiki\Utf8\PhpString::basename($file);
556
        $item['size']       = filesize($base.'/'.$file);
557
        $item['mtime']      = filemtime($base.'/'.$file);
558
        $item['rev']        = $item['mtime'];
559
        $item['writable']   = is_writable($base.'/'.$file);
560
        $item['executable'] = is_executable($base.'/'.$file);
561
    }
562
563
    if($type == 'f'){
564
        if(!empty($opts['hash'])) $item['hash'] = md5(io_readFile($base.'/'.$file,false));
565
        if(!empty($opts['firsthead'])) $item['title'] = p_get_first_heading($item['id'],METADATA_DONT_RENDER);
566
    }
567
568
    // finally add the item
569
    $data[] = $item;
570
    return $return;
571
}
572
573
//Setup VIM: ex: et ts=4 :
574