Coverage Report

Created: 2026-06-02 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/main/fopen_wrappers.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright © The PHP Group and Contributors.                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to the Modified BSD License that is      |
6
   | bundled with this package in the file LICENSE, and is available      |
7
   | through the World Wide Web at <https://www.php.net/license/>.        |
8
   |                                                                      |
9
   | SPDX-License-Identifier: BSD-3-Clause                                |
10
   +----------------------------------------------------------------------+
11
   | Authors: Rasmus Lerdorf <rasmus@lerdorf.on.ca>                       |
12
   |          Jim Winstead <jimw@php.net>                                 |
13
   +----------------------------------------------------------------------+
14
 */
15
16
/* {{{ includes */
17
#include "php.h"
18
#include "php_globals.h"
19
#include "SAPI.h"
20
21
#include <stdio.h>
22
#include <stdlib.h>
23
#include <errno.h>
24
#include <sys/types.h>
25
#include <sys/stat.h>
26
#include <fcntl.h>
27
28
#ifdef PHP_WIN32
29
#define O_RDONLY _O_RDONLY
30
#include "win32/param.h"
31
#else
32
#include <sys/param.h>
33
#endif
34
35
#include "ext/standard/head.h"
36
#include "ext/standard/php_standard.h"
37
#include "zend_compile.h"
38
#include "php_network.h"
39
#include "zend_smart_str.h"
40
41
#ifdef HAVE_PWD_H
42
#include <pwd.h>
43
#endif
44
45
#ifdef HAVE_SYS_SOCKET_H
46
#include <sys/socket.h>
47
#endif
48
49
#ifdef PHP_WIN32
50
#include <winsock2.h>
51
#else
52
#include <netinet/in.h>
53
#include <netdb.h>
54
#ifdef HAVE_ARPA_INET_H
55
#include <arpa/inet.h>
56
#endif
57
#endif
58
59
#if defined(PHP_WIN32) || defined(__riscos__)
60
#undef AF_UNIX
61
#endif
62
63
#if defined(AF_UNIX)
64
#include <sys/un.h>
65
#endif
66
/* }}} */
67
68
/* {{{ OnUpdateBaseDir
69
Allows any change to open_basedir setting in during Startup and Shutdown events,
70
or a tightening during activation/runtime/deactivation */
71
PHPAPI ZEND_INI_MH(OnUpdateBaseDir)
72
6
{
73
6
  char **p = ZEND_INI_GET_ADDR();
74
6
  char *pathbuf, *ptr, *end;
75
76
6
  if (stage == PHP_INI_STAGE_STARTUP || stage == PHP_INI_STAGE_SHUTDOWN || stage == PHP_INI_STAGE_ACTIVATE || stage == PHP_INI_STAGE_DEACTIVATE) {
77
4
    if (PG(open_basedir_modified)) {
78
1
      efree(*p);
79
1
    }
80
    /* We're in a PHP_INI_SYSTEM context, no restrictions */
81
4
    *p = new_value ? ZSTR_VAL(new_value) : NULL;
82
4
    PG(open_basedir_modified) = false;
83
4
    return SUCCESS;
84
4
  }
85
86
  /* Shortcut: When we have a open_basedir and someone tries to unset, we know it'll fail */
87
2
  if (!new_value || !*ZSTR_VAL(new_value)) {
88
1
    return FAILURE;
89
1
  }
90
91
  /* Is the proposed open_basedir at least as restrictive as the current setting? */
92
1
  smart_str buf = {0};
93
1
  ptr = pathbuf = estrdup(ZSTR_VAL(new_value));
94
2
  while (ptr && *ptr) {
95
1
    end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
96
1
    if (end != NULL) {
97
0
      *end = '\0';
98
0
      end++;
99
0
    }
100
1
    char resolved_name[MAXPATHLEN + 1];
101
1
    if (expand_filepath(ptr, resolved_name) == NULL) {
102
0
      efree(pathbuf);
103
0
      smart_str_free(&buf);
104
0
      return FAILURE;
105
0
    }
106
1
    if (php_check_open_basedir_ex(resolved_name, 0) != 0) {
107
      /* At least one portion of this open_basedir is less restrictive than the prior one, FAIL */
108
0
      efree(pathbuf);
109
0
      smart_str_free(&buf);
110
0
      return FAILURE;
111
0
    }
112
1
    if (smart_str_get_len(&buf) != 0) {
113
0
      smart_str_appendc(&buf, DEFAULT_DIR_SEPARATOR);
114
0
    }
115
1
    smart_str_appends(&buf, resolved_name);
116
1
    ptr = end;
117
1
  }
118
1
  efree(pathbuf);
119
120
  /* Everything checks out, set it */
121
1
  zend_string *tmp = smart_str_extract(&buf);
122
1
  char *result = estrdup(ZSTR_VAL(tmp));
123
1
  if (PG(open_basedir_modified)) {
124
0
    efree(*p);
125
0
  }
126
1
  *p = result;
127
1
  PG(open_basedir_modified) = true;
128
1
  zend_string_release(tmp);
129
130
1
  return SUCCESS;
131
1
}
132
/* }}} */
133
134
/* {{{ php_check_specific_open_basedir
135
  When open_basedir is not NULL, check if the given filename is located in
136
  open_basedir. Returns -1 if error or not in the open_basedir, else 0.
137
  When open_basedir is NULL, always return 0.
138
*/
139
PHPAPI int php_check_specific_open_basedir(const char *basedir, const char *path)
140
63
{
141
63
  char resolved_name[MAXPATHLEN + 1];
142
63
  char resolved_basedir[MAXPATHLEN + 1];
143
63
  char local_open_basedir[MAXPATHLEN];
144
63
  char path_tmp[MAXPATHLEN + 1];
145
63
  char *path_file;
146
63
  size_t resolved_basedir_len;
147
63
  size_t resolved_name_len;
148
63
  size_t path_len;
149
63
  int nesting_level = 0;
150
151
  /* Special case basedir==".": Use script-directory */
152
63
  if (strcmp(basedir, ".") || !VCWD_GETCWD(local_open_basedir, MAXPATHLEN)) {
153
    /* Else use the unmodified path */
154
63
    strlcpy(local_open_basedir, basedir, sizeof(local_open_basedir));
155
63
  }
156
157
63
  path_len = strlen(path);
158
63
  if (path_len > (MAXPATHLEN - 1)) {
159
    /* empty and too long paths are invalid */
160
0
    return -1;
161
0
  }
162
163
  /* normalize and expand path */
164
63
  if (expand_filepath(path, resolved_name) == NULL) {
165
0
    return -1;
166
0
  }
167
168
63
  path_len = strlen(resolved_name);
169
63
  memcpy(path_tmp, resolved_name, path_len + 1); /* safe */
170
171
136
  while (VCWD_REALPATH(path_tmp, resolved_name) == NULL) {
172
74
#if defined(PHP_WIN32) || defined(HAVE_SYMLINK)
173
74
    if (nesting_level == 0) {
174
55
      ssize_t ret;
175
55
      char buf[MAXPATHLEN];
176
177
55
      ret = php_sys_readlink(path_tmp, buf, MAXPATHLEN - 1);
178
55
      if (ret == -1) {
179
        /* not a broken symlink, move along.. */
180
55
      } else {
181
        /* put the real path into the path buffer */
182
0
        memcpy(path_tmp, buf, ret);
183
0
        path_tmp[ret] = '\0';
184
0
      }
185
55
    }
186
74
#endif
187
188
#ifdef PHP_WIN32
189
    path_file = strrchr(path_tmp, DEFAULT_SLASH);
190
    if (!path_file) {
191
      path_file = strrchr(path_tmp, '/');
192
    }
193
#else
194
74
    path_file = strrchr(path_tmp, DEFAULT_SLASH);
195
74
#endif
196
74
    if (!path_file) {
197
      /* none of the path components exist. definitely not in open_basedir.. */
198
0
      return -1;
199
74
    } else {
200
74
      path_len = path_file - path_tmp + 1;
201
#ifdef PHP_WIN32
202
      if (path_len > 1 && path_tmp[path_len - 2] == ':') {
203
        if (path_len != 3) {
204
          return -1;
205
        }
206
        /* this is c:\ */
207
        path_tmp[path_len] = '\0';
208
      } else {
209
        path_tmp[path_len - 1] = '\0';
210
      }
211
#else
212
74
      path_tmp[path_len - 1] = '\0';
213
74
#endif
214
74
    }
215
74
    if (*path_tmp == '\0') {
216
      /* Do not pass an empty string to realpath(), as this will resolve to CWD. */
217
1
      break;
218
1
    }
219
73
    nesting_level++;
220
73
  }
221
222
  /* Resolve open_basedir to resolved_basedir */
223
63
  if (expand_filepath(local_open_basedir, resolved_basedir) != NULL) {
224
63
    size_t basedir_len = strlen(basedir);
225
    /* Handler for basedirs that end with a / */
226
63
    resolved_basedir_len = strlen(resolved_basedir);
227
#ifdef PHP_WIN32
228
    if (basedir[basedir_len - 1] == PHP_DIR_SEPARATOR || basedir[basedir_len - 1] == '/') {
229
#else
230
63
    if (basedir[basedir_len - 1] == PHP_DIR_SEPARATOR) {
231
0
#endif
232
0
      if (resolved_basedir[resolved_basedir_len - 1] != PHP_DIR_SEPARATOR) {
233
0
        resolved_basedir[resolved_basedir_len] = PHP_DIR_SEPARATOR;
234
0
        resolved_basedir[++resolved_basedir_len] = '\0';
235
0
      }
236
63
    } else {
237
63
        resolved_basedir[resolved_basedir_len++] = PHP_DIR_SEPARATOR;
238
63
        resolved_basedir[resolved_basedir_len] = '\0';
239
63
    }
240
241
63
    resolved_name_len = strlen(resolved_name);
242
63
    if (path_tmp[path_len - 1] == PHP_DIR_SEPARATOR) {
243
0
      if (resolved_name[resolved_name_len - 1] != PHP_DIR_SEPARATOR) {
244
0
        resolved_name[resolved_name_len] = PHP_DIR_SEPARATOR;
245
0
        resolved_name[++resolved_name_len] = '\0';
246
0
      }
247
0
    }
248
249
    /* Check the path */
250
#ifdef PHP_WIN32
251
    if (strncasecmp(resolved_basedir, resolved_name, resolved_basedir_len) == 0) {
252
#else
253
63
    if (strncmp(resolved_basedir, resolved_name, resolved_basedir_len) == 0) {
254
6
#endif
255
6
      if (resolved_name_len > resolved_basedir_len &&
256
6
        resolved_name[resolved_basedir_len - 1] != PHP_DIR_SEPARATOR) {
257
0
        return -1;
258
6
      } else {
259
        /* File is in the right directory */
260
6
        return 0;
261
6
      }
262
57
    } else {
263
      /* /openbasedir/ and /openbasedir are the same directory */
264
57
      if (resolved_basedir_len == (resolved_name_len + 1) && resolved_basedir[resolved_basedir_len - 1] == PHP_DIR_SEPARATOR) {
265
#ifdef PHP_WIN32
266
        if (strncasecmp(resolved_basedir, resolved_name, resolved_name_len) == 0) {
267
#else
268
25
        if (strncmp(resolved_basedir, resolved_name, resolved_name_len) == 0) {
269
23
#endif
270
23
          return 0;
271
23
        }
272
25
      }
273
34
      return -1;
274
57
    }
275
63
  } else {
276
    /* Unable to resolve the real path, return -1 */
277
0
    return -1;
278
0
  }
279
63
}
280
/* }}} */
281
282
PHPAPI int php_check_open_basedir(const char *path)
283
70
{
284
70
  return php_check_open_basedir_ex(path, 1);
285
70
}
286
287
/* {{{ php_check_open_basedir */
288
PHPAPI int php_check_open_basedir_ex(const char *path, int warn)
289
71
{
290
  /* Only check when open_basedir is available */
291
71
  if (PG(open_basedir) && *PG(open_basedir)) {
292
63
    char *pathbuf;
293
63
    char *ptr;
294
63
    char *end;
295
296
    /* Check if the path is too long so we can give a more useful error
297
    * message. */
298
63
    if (strlen(path) > (MAXPATHLEN - 1)) {
299
0
      php_error_docref(NULL, E_WARNING, "File name is longer than the maximum allowed path length on this platform (%d): %s", MAXPATHLEN, path);
300
0
      errno = EINVAL;
301
0
      return -1;
302
0
    }
303
304
63
    pathbuf = estrdup(PG(open_basedir));
305
306
63
    ptr = pathbuf;
307
308
97
    while (ptr && *ptr) {
309
63
      end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
310
63
      if (end != NULL) {
311
0
        *end = '\0';
312
0
        end++;
313
0
      }
314
315
63
      if (php_check_specific_open_basedir(ptr, path) == 0) {
316
29
        efree(pathbuf);
317
29
        return 0;
318
29
      }
319
320
34
      ptr = end;
321
34
    }
322
34
    if (warn) {
323
34
      php_error_docref(NULL, E_WARNING, "open_basedir restriction in effect. File(%s) is not within the allowed path(s): (%s)", path, PG(open_basedir));
324
34
    }
325
34
    efree(pathbuf);
326
34
    errno = EPERM; /* we deny permission to open it */
327
34
    return -1;
328
63
  }
329
330
  /* Nothing to check... */
331
8
  return 0;
332
71
}
333
/* }}} */
334
335
/* {{{ php_fopen_and_set_opened_path */
336
static FILE *php_fopen_and_set_opened_path(const char *path, const char *mode, zend_string **opened_path)
337
8
{
338
8
  FILE *fp;
339
340
8
  if (php_check_open_basedir((char *)path)) {
341
0
    return NULL;
342
0
  }
343
8
  fp = VCWD_FOPEN(path, mode);
344
8
  if (fp && opened_path) {
345
    //TODO :avoid reallocation
346
0
    char *tmp = expand_filepath_with_mode(path, NULL, NULL, 0, CWD_EXPAND);
347
0
    if (tmp) {
348
0
      *opened_path = zend_string_init(tmp, strlen(tmp), 0);
349
0
      efree(tmp);
350
0
    }
351
0
  }
352
8
  return fp;
353
8
}
354
/* }}} */
355
356
/* {{{ php_fopen_primary_script */
357
PHPAPI int php_fopen_primary_script(zend_file_handle *file_handle)
358
0
{
359
0
  char *path_info;
360
0
  zend_string *filename = NULL;
361
0
  zend_string *resolved_path = NULL;
362
0
  size_t length;
363
0
  bool orig_display_errors;
364
365
0
  memset(file_handle, 0, sizeof(zend_file_handle));
366
367
0
  path_info = SG(request_info).request_uri;
368
0
#ifdef HAVE_PWD_H
369
0
  if (PG(user_dir) && *PG(user_dir) && path_info && '/' == path_info[0] && '~' == path_info[1]) {
370
0
    char *s = strchr(path_info + 2, '/');
371
372
0
    if (s) {     /* if there is no path name after the file, do not bother */
373
0
      char user[32];      /* to try open the directory */
374
375
0
      length = s - (path_info + 2);
376
0
      if (length > sizeof(user) - 1) {
377
0
        length = sizeof(user) - 1;
378
0
      }
379
0
      memcpy(user, path_info + 2, length);
380
0
      user[length] = '\0';
381
382
0
      struct passwd *pw;
383
#if defined(ZTS) && defined(HAVE_GETPWNAM_R) && defined(_SC_GETPW_R_SIZE_MAX)
384
      struct passwd pwstruc;
385
      long pwbuflen = sysconf(_SC_GETPW_R_SIZE_MAX);
386
      char *pwbuf;
387
      int err;
388
389
      if (pwbuflen < 1) {
390
        pwbuflen = 1024;
391
      }
392
# if ZEND_DEBUG
393
      /* Test retry logic */
394
      pwbuflen = 1;
395
# endif
396
      pwbuf = emalloc(pwbuflen);
397
398
try_again:
399
      err = getpwnam_r(user, &pwstruc, pwbuf, pwbuflen, &pw);
400
      if (err) {
401
        if (err == ERANGE) {
402
          pwbuflen *= 2;
403
          pwbuf = erealloc(pwbuf, pwbuflen);
404
          goto try_again;
405
        }
406
        efree(pwbuf);
407
        return FAILURE;
408
      }
409
#else
410
0
      pw = getpwnam(user);
411
0
#endif
412
0
      if (pw && pw->pw_dir) {
413
0
        filename = zend_strpprintf(0, "%s%c%s%c%s", pw->pw_dir, PHP_DIR_SEPARATOR, PG(user_dir), PHP_DIR_SEPARATOR, s + 1); /* Safe */
414
0
      } else if (SG(request_info).path_translated) {
415
0
        filename = zend_string_init(SG(request_info).path_translated,
416
0
          strlen(SG(request_info).path_translated), 0);
417
0
      }
418
#if defined(ZTS) && defined(HAVE_GETPWNAM_R) && defined(_SC_GETPW_R_SIZE_MAX)
419
      efree(pwbuf);
420
#endif
421
0
    }
422
0
  } else
423
0
#endif
424
0
  if (PG(doc_root) && path_info && (length = strlen(PG(doc_root))) &&
425
0
    IS_ABSOLUTE_PATH(PG(doc_root), length)) {
426
0
    size_t path_len = strlen(path_info);
427
0
    filename = zend_string_alloc(length + path_len + 2, 0);
428
0
    memcpy(ZSTR_VAL(filename), PG(doc_root), length);
429
0
    if (!IS_SLASH(ZSTR_VAL(filename)[length - 1])) { /* length is never 0 */
430
0
      ZSTR_VAL(filename)[length++] = PHP_DIR_SEPARATOR;
431
0
    }
432
0
    if (IS_SLASH(path_info[0])) {
433
0
      length--;
434
0
    }
435
0
    strncpy(ZSTR_VAL(filename) + length, path_info, path_len + 1);
436
0
    ZSTR_LEN(filename) = length + path_len;
437
0
  } else if (SG(request_info).path_translated) {
438
0
    filename = zend_string_init(SG(request_info).path_translated,
439
0
      strlen(SG(request_info).path_translated), 0);
440
0
  }
441
442
443
0
  if (filename) {
444
0
    resolved_path = zend_resolve_path(filename);
445
0
  }
446
447
0
  if (!resolved_path) {
448
0
    if (filename) {
449
0
      zend_string_release(filename);
450
0
    }
451
    /* we have to free SG(request_info).path_translated here because
452
     * php_destroy_request_info assumes that it will get
453
     * freed when the include_names hash is emptied, but
454
     * we're not adding it in this case */
455
0
    if (SG(request_info).path_translated) {
456
0
      efree(SG(request_info).path_translated);
457
0
      SG(request_info).path_translated = NULL;
458
0
    }
459
0
    return FAILURE;
460
0
  }
461
0
  zend_string_release_ex(resolved_path, 0);
462
463
0
  orig_display_errors = PG(display_errors);
464
0
  PG(display_errors) = 0;
465
0
  zend_stream_init_filename_ex(file_handle, filename);
466
0
  file_handle->primary_script = 1;
467
0
  if (filename) {
468
0
    zend_string_delref(filename);
469
0
  }
470
0
  if (zend_stream_open(file_handle) == FAILURE) {
471
0
    PG(display_errors) = orig_display_errors;
472
0
    if (SG(request_info).path_translated) {
473
0
      efree(SG(request_info).path_translated);
474
0
      SG(request_info).path_translated = NULL;
475
0
    }
476
0
    return FAILURE;
477
0
  }
478
0
  PG(display_errors) = orig_display_errors;
479
480
0
  return SUCCESS;
481
0
}
482
/* }}} */
483
484
109
static zend_string *tsrm_realpath_str(const char *path) {
485
109
  char *realpath = tsrm_realpath(path, NULL);
486
109
  if (!realpath) {
487
101
    return NULL;
488
101
  }
489
8
  zend_string *realpath_str = zend_string_init(realpath, strlen(realpath), 0);
490
8
  efree(realpath);
491
8
  return realpath_str;
492
109
}
493
494
/* {{{ php_resolve_path
495
 * Returns the realpath for given filename according to include path
496
 */
497
PHPAPI zend_string *php_resolve_path(const char *filename, size_t filename_length, const char *path)
498
5.36k
{
499
5.36k
  zend_string *resolved_path;
500
5.36k
  char trypath[MAXPATHLEN];
501
5.36k
  const char *ptr, *end, *p;
502
5.36k
  const char *actual_path;
503
5.36k
  php_stream_wrapper *wrapper;
504
5.36k
  zend_string *exec_filename;
505
506
5.36k
  if (!filename || zend_char_has_nul_byte(filename, filename_length)) {
507
0
    return NULL;
508
0
  }
509
510
  /* Don't resolve paths which contain protocol (except of file://) */
511
11.6k
  for (p = filename; isalnum((unsigned char)*p) || *p == '+' || *p == '-' || *p == '.'; p++);
512
5.36k
  if ((*p == ':') && (p - filename > 1) && (p[1] == '/') && (p[2] == '/')) {
513
1.33k
    wrapper = php_stream_locate_url_wrapper(filename, &actual_path, STREAM_OPEN_FOR_INCLUDE);
514
1.33k
    if (wrapper == &php_plain_files_wrapper) {
515
1
      if ((resolved_path = tsrm_realpath_str(actual_path))) {
516
0
        return resolved_path;
517
0
      }
518
1
    }
519
1.33k
    return NULL;
520
1.33k
  }
521
522
4.03k
  if ((*filename == '.' &&
523
2
       (IS_SLASH(filename[1]) ||
524
2
        ((filename[1] == '.') && IS_SLASH(filename[2])))) ||
525
4.02k
      IS_ABSOLUTE_PATH(filename, filename_length) ||
526
#ifdef PHP_WIN32
527
    /* This should count as an absolute local path as well, however
528
       IS_ABSOLUTE_PATH doesn't care about this path form till now. It
529
       might be a big thing to extend, thus just a local handling for
530
       now. */
531
    (filename_length >=2 && IS_SLASH(filename[0]) && !IS_SLASH(filename[1])) ||
532
#endif
533
3.99k
      !path ||
534
3.99k
      !*path) {
535
32
    return tsrm_realpath_str(filename);
536
32
  }
537
538
3.99k
  ptr = path;
539
7.99k
  while (ptr && *ptr) {
540
    /* Check for stream wrapper */
541
4.00k
    int is_stream_wrapper = 0;
542
543
19.8k
    for (p = ptr; isalnum((unsigned char)*p) || *p == '+' || *p == '-' || *p == '.'; p++);
544
4.00k
    if ((*p == ':') && (p - ptr > 1) && (p[1] == '/') && (p[2] == '/')) {
545
      /* .:// or ..:// is not a stream wrapper */
546
3.96k
      if (p[-1] != '.' || p[-2] != '.' || p - 2 != ptr) {
547
3.96k
        p += 3;
548
3.96k
        is_stream_wrapper = 1;
549
3.96k
      }
550
3.96k
    }
551
4.00k
    end = strchr(p, DEFAULT_DIR_SEPARATOR);
552
4.00k
    if (end) {
553
38
      if (filename_length > (MAXPATHLEN - 2) || (end-ptr) > MAXPATHLEN || (end-ptr) + 1 + filename_length + 1 >= MAXPATHLEN) {
554
0
        ptr = end + 1;
555
0
        continue;
556
0
      }
557
38
      memcpy(trypath, ptr, end-ptr);
558
38
      trypath[end-ptr] = '/';
559
38
      memcpy(trypath+(end-ptr)+1, filename, filename_length+1);
560
38
      ptr = end+1;
561
3.96k
    } else {
562
3.96k
      size_t len = strlen(ptr);
563
564
3.96k
      if (filename_length > (MAXPATHLEN - 2) || len > MAXPATHLEN || len + 1 + filename_length + 1 >= MAXPATHLEN) {
565
0
        break;
566
0
      }
567
3.96k
      memcpy(trypath, ptr, len);
568
3.96k
      trypath[len] = '/';
569
3.96k
      memcpy(trypath+len+1, filename, filename_length+1);
570
3.96k
      ptr = NULL;
571
3.96k
    }
572
4.00k
    actual_path = trypath;
573
4.00k
    if (is_stream_wrapper) {
574
3.96k
      wrapper = php_stream_locate_url_wrapper(trypath, &actual_path, STREAM_OPEN_FOR_INCLUDE);
575
3.96k
      if (!wrapper) {
576
0
        continue;
577
3.96k
      } else if (wrapper != &php_plain_files_wrapper) {
578
3.96k
        if (wrapper->wops->url_stat) {
579
3.96k
          php_stream_statbuf ssb;
580
581
3.96k
          if (SUCCESS == wrapper->wops->url_stat(wrapper, trypath, PHP_STREAM_URL_STAT_QUIET, &ssb, NULL)) {
582
0
            return zend_string_init(trypath, strlen(trypath), 0);
583
0
          }
584
3.96k
          if (EG(exception)) {
585
0
            return NULL;
586
0
          }
587
3.96k
        }
588
3.96k
        continue;
589
3.96k
      }
590
3.96k
    }
591
39
    if ((resolved_path = tsrm_realpath_str(actual_path))) {
592
1
      return resolved_path;
593
1
    }
594
39
  } /* end provided path */
595
596
  /* check in calling scripts' current working directory as a fallback case
597
   */
598
3.99k
  if (zend_is_executing() &&
599
37
      (exec_filename = zend_get_executed_filename_ex()) != NULL) {
600
37
    const char *exec_fname = ZSTR_VAL(exec_filename);
601
37
    size_t exec_fname_length = ZSTR_LEN(exec_filename);
602
603
407
    while (exec_fname_length > 0) {
604
407
      --exec_fname_length;
605
407
      if (IS_SLASH(exec_fname[exec_fname_length])) {
606
37
        break;
607
37
      }
608
407
    }
609
610
37
    if (exec_fname_length > 0 &&
611
37
      filename_length < (MAXPATHLEN - 2) &&
612
37
        exec_fname_length + 1 + filename_length + 1 < MAXPATHLEN) {
613
37
      memcpy(trypath, exec_fname, exec_fname_length + 1);
614
37
      memcpy(trypath+exec_fname_length + 1, filename, filename_length+1);
615
37
      actual_path = trypath;
616
617
      /* Check for stream wrapper */
618
37
      for (p = trypath; isalnum((unsigned char)*p) || *p == '+' || *p == '-' || *p == '.'; p++);
619
37
      if ((*p == ':') && (p - trypath > 1) && (p[1] == '/') && (p[2] == '/')) {
620
0
        wrapper = php_stream_locate_url_wrapper(trypath, &actual_path, STREAM_OPEN_FOR_INCLUDE);
621
0
        if (!wrapper) {
622
0
          return NULL;
623
0
        } else if (wrapper != &php_plain_files_wrapper) {
624
0
          if (wrapper->wops->url_stat) {
625
0
            php_stream_statbuf ssb;
626
627
0
            if (SUCCESS == wrapper->wops->url_stat(wrapper, trypath, PHP_STREAM_URL_STAT_QUIET, &ssb, NULL)) {
628
0
              return zend_string_init(trypath, strlen(trypath), 0);
629
0
            }
630
0
            if (EG(exception)) {
631
0
              return NULL;
632
0
            }
633
0
          }
634
0
          return NULL;
635
0
        }
636
0
      }
637
638
37
      return tsrm_realpath_str(actual_path);
639
37
    }
640
37
  }
641
642
3.96k
  return NULL;
643
3.99k
}
644
/* }}} */
645
646
/* {{{ php_fopen_with_path
647
 * Tries to open a file with a PATH-style list of directories.
648
 * If the filename starts with "." or "/", the path is ignored.
649
 */
650
PHPAPI FILE *php_fopen_with_path(const char *filename, const char *mode, const char *path, zend_string **opened_path)
651
4
{
652
4
  char *pathbuf, *ptr, *end;
653
4
  char trypath[MAXPATHLEN];
654
4
  FILE *fp;
655
4
  size_t filename_length;
656
4
  zend_string *exec_filename;
657
658
4
  if (opened_path) {
659
4
    *opened_path = NULL;
660
4
  }
661
662
4
  if (!filename) {
663
0
    return NULL;
664
0
  }
665
666
4
  filename_length = strlen(filename);
667
4
#ifndef PHP_WIN32
668
4
  (void) filename_length;
669
4
#endif
670
671
  /* Relative path open */
672
4
  if ((*filename == '.')
673
  /* Absolute path open */
674
4
   || IS_ABSOLUTE_PATH(filename, filename_length)
675
4
   || (!path || !*path)
676
4
  ) {
677
0
    return php_fopen_and_set_opened_path(filename, mode, opened_path);
678
0
  }
679
680
  /* check in provided path */
681
  /* append the calling scripts' current working directory
682
   * as a fallback case
683
   */
684
4
  if (zend_is_executing() &&
685
0
      (exec_filename = zend_get_executed_filename_ex()) != NULL) {
686
0
    const char *exec_fname = ZSTR_VAL(exec_filename);
687
0
    size_t exec_fname_length = ZSTR_LEN(exec_filename);
688
689
0
    while ((--exec_fname_length < SIZE_MAX) && !IS_SLASH(exec_fname[exec_fname_length]));
690
0
    if ((exec_fname && exec_fname[0] == '[') || exec_fname_length <= 0) {
691
      /* [no active file] or no path */
692
0
      pathbuf = estrdup(path);
693
0
    } else {
694
0
      size_t path_length = strlen(path);
695
696
0
      pathbuf = (char *) emalloc(exec_fname_length + path_length + 1 + 1);
697
0
      memcpy(pathbuf, path, path_length);
698
0
      pathbuf[path_length] = DEFAULT_DIR_SEPARATOR;
699
0
      memcpy(pathbuf + path_length + 1, exec_fname, exec_fname_length);
700
0
      pathbuf[path_length + exec_fname_length + 1] = '\0';
701
0
    }
702
4
  } else {
703
4
    pathbuf = estrdup(path);
704
4
  }
705
706
4
  ptr = pathbuf;
707
708
12
  while (ptr && *ptr) {
709
8
    end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
710
8
    if (end != NULL) {
711
4
      *end = '\0';
712
4
      end++;
713
4
    }
714
8
    if (snprintf(trypath, MAXPATHLEN, "%s/%s", ptr, filename) >= MAXPATHLEN) {
715
0
      php_error_docref(NULL, E_NOTICE, "%s/%s path was truncated to %d", ptr, filename, MAXPATHLEN);
716
0
    }
717
8
    fp = php_fopen_and_set_opened_path(trypath, mode, opened_path);
718
8
    if (fp) {
719
0
      efree(pathbuf);
720
0
      return fp;
721
0
    }
722
8
    ptr = end;
723
8
  } /* end provided path */
724
725
4
  efree(pathbuf);
726
4
  return NULL;
727
4
}
728
/* }}} */
729
730
/* {{{ php_strip_url_passwd */
731
PHPAPI char *php_strip_url_passwd(char *url)
732
1.44k
{
733
1.44k
  char *p, *url_start;
734
735
1.44k
  if (url == NULL) {
736
0
    return "";
737
0
  }
738
739
1.44k
  p = url;
740
741
7.56k
  while (*p) {
742
7.46k
    if (*p == ':' && *(p + 1) == '/' && *(p + 2) == '/') {
743
      /* found protocol */
744
1.33k
      url_start = p = p + 3;
745
746
1.83k
      while (*p) {
747
497
        if (*p == '@') {
748
5
          int i;
749
750
14
          for (i = 0; i < 3 && url_start < p; i++, url_start++) {
751
9
            *url_start = '.';
752
9
          }
753
59
          for (; *p; p++) {
754
54
            *url_start++ = *p;
755
54
          }
756
5
          *url_start=0;
757
5
          break;
758
5
        }
759
492
        p++;
760
492
      }
761
1.33k
      return url;
762
1.33k
    }
763
6.12k
    p++;
764
6.12k
  }
765
102
  return url;
766
1.44k
}
767
/* }}} */
768
769
/* {{{ expand_filepath */
770
PHPAPI char *expand_filepath(const char *filepath, char *real_path)
771
150
{
772
150
  return expand_filepath_ex(filepath, real_path, NULL, 0);
773
150
}
774
/* }}} */
775
776
/* {{{ expand_filepath_ex */
777
PHPAPI char *expand_filepath_ex(const char *filepath, char *real_path, const char *relative_to, size_t relative_to_len)
778
150
{
779
150
  return expand_filepath_with_mode(filepath, real_path, relative_to, relative_to_len, CWD_FILEPATH);
780
150
}
781
/* }}} */
782
783
/* {{{ expand_filepath_use_realpath */
784
PHPAPI char *expand_filepath_with_mode(const char *filepath, char *real_path, const char *relative_to, size_t relative_to_len, int realpath_mode)
785
150
{
786
150
  cwd_state new_state;
787
150
  char cwd[MAXPATHLEN];
788
150
  size_t copy_len;
789
150
  size_t path_len;
790
791
150
  if (!filepath[0]) {
792
0
    return NULL;
793
0
  }
794
795
150
  path_len = strlen(filepath);
796
797
150
  if (IS_ABSOLUTE_PATH(filepath, path_len)) {
798
118
    cwd[0] = '\0';
799
118
  } else {
800
32
    const char *iam = SG(request_info).path_translated;
801
32
    const char *result;
802
32
    if (relative_to) {
803
0
      if (relative_to_len > MAXPATHLEN-1U) {
804
0
        return NULL;
805
0
      }
806
0
      result = relative_to;
807
0
      memcpy(cwd, relative_to, relative_to_len+1U);
808
32
    } else {
809
32
      result = VCWD_GETCWD(cwd, MAXPATHLEN);
810
32
    }
811
812
32
    if (!result && (iam != filepath)) {
813
0
      int fdtest = -1;
814
815
0
      fdtest = VCWD_OPEN(filepath, O_RDONLY);
816
0
      if (fdtest != -1) {
817
        /* return a relative file path if for any reason
818
         * we cannot getcwd() and the requested,
819
         * relatively referenced file is accessible */
820
0
        copy_len = path_len > MAXPATHLEN - 1 ? MAXPATHLEN - 1 : path_len;
821
0
        if (real_path) {
822
0
          memcpy(real_path, filepath, copy_len);
823
0
          real_path[copy_len] = '\0';
824
0
        } else {
825
0
          real_path = estrndup(filepath, copy_len);
826
0
        }
827
0
        close(fdtest);
828
0
        return real_path;
829
0
      } else {
830
0
        cwd[0] = '\0';
831
0
      }
832
32
    } else if (!result) {
833
0
      cwd[0] = '\0';
834
0
    }
835
32
  }
836
837
150
  new_state.cwd = estrdup(cwd);
838
150
  new_state.cwd_length = strlen(cwd);
839
840
150
  if (virtual_file_ex(&new_state, filepath, NULL, realpath_mode)) {
841
0
    efree(new_state.cwd);
842
0
    return NULL;
843
0
  }
844
845
150
  if (real_path) {
846
150
    copy_len = new_state.cwd_length > MAXPATHLEN - 1 ? MAXPATHLEN - 1 : new_state.cwd_length;
847
150
    memcpy(real_path, new_state.cwd, copy_len);
848
150
    real_path[copy_len] = '\0';
849
150
  } else {
850
0
    real_path = estrndup(new_state.cwd, new_state.cwd_length);
851
0
  }
852
150
  efree(new_state.cwd);
853
854
150
  return real_path;
855
150
}
856
/* }}} */