Coverage Report

Created: 2025-06-13 06:43

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