xref: /php-src/main/php_open_temporary_file.c (revision 01b3fc03)
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    | Author: Zeev Suraski <zeev@php.net>                                  |
14    +----------------------------------------------------------------------+
15  */
16 
17 #include "php.h"
18 #include "php_open_temporary_file.h"
19 
20 #include <errno.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <fcntl.h>
24 
25 #ifdef PHP_WIN32
26 #define O_RDONLY _O_RDONLY
27 #include "win32/param.h"
28 #include "win32/winutil.h"
29 #else
30 #include <sys/param.h>
31 #include <sys/socket.h>
32 #include <netinet/in.h>
33 #include <netdb.h>
34 #if HAVE_ARPA_INET_H
35 #include <arpa/inet.h>
36 #endif
37 #endif
38 #ifdef HAVE_SYS_TIME_H
39 #include <sys/time.h>
40 #endif
41 
42 #ifdef HAVE_SYS_FILE_H
43 #include <sys/file.h>
44 #endif
45 
46 #if !defined(P_tmpdir)
47 #define P_tmpdir ""
48 #endif
49 
50 /* {{{ php_do_open_temporary_file */
51 
52 /* Loosely based on a tempnam() implementation by UCLA */
53 
54 /*
55  * Copyright (c) 1988, 1993
56  *      The Regents of the University of California.  All rights reserved.
57  *
58  * Redistribution and use in source and binary forms, with or without
59  * modification, are permitted provided that the following conditions
60  * are met:
61  * 1. Redistributions of source code must retain the above copyright
62  *    notice, this list of conditions and the following disclaimer.
63  * 2. Redistributions in binary form must reproduce the above copyright
64  *    notice, this list of conditions and the following disclaimer in the
65  *    documentation and/or other materials provided with the distribution.
66  * 3. All advertising materials mentioning features or use of this software
67  *    must display the following acknowledgement:
68  *      This product includes software developed by the University of
69  *      California, Berkeley and its contributors.
70  * 4. Neither the name of the University nor the names of its contributors
71  *    may be used to endorse or promote products derived from this software
72  *    without specific prior written permission.
73  *
74  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
75  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
76  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
77  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
78  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
79  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
80  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
81  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
82  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
83  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
84  * SUCH DAMAGE.
85  */
86 
php_do_open_temporary_file(const char * path,const char * pfx,zend_string ** opened_path_p)87 static int php_do_open_temporary_file(const char *path, const char *pfx, zend_string **opened_path_p)
88 {
89 #ifdef PHP_WIN32
90 	char *opened_path = NULL;
91 	size_t opened_path_len;
92 	wchar_t *cwdw, *pfxw, pathw[MAXPATHLEN];
93 #else
94 	char opened_path[MAXPATHLEN];
95 	char *trailing_slash;
96 #endif
97 	char cwd[MAXPATHLEN];
98 	cwd_state new_state;
99 	int fd = -1;
100 #ifndef HAVE_MKSTEMP
101 	int open_flags = O_CREAT | O_TRUNC | O_RDWR
102 #ifdef PHP_WIN32
103 		| _O_BINARY
104 #endif
105 		;
106 #endif
107 
108 	if (!path || !path[0]) {
109 		return -1;
110 	}
111 
112 #ifdef PHP_WIN32
113 	if (!php_win32_check_trailing_space(pfx, strlen(pfx))) {
114 		SetLastError(ERROR_INVALID_NAME);
115 		return -1;
116 	}
117 #endif
118 
119 	if (!VCWD_GETCWD(cwd, MAXPATHLEN)) {
120 		cwd[0] = '\0';
121 	}
122 
123 	new_state.cwd = estrdup(cwd);
124 	new_state.cwd_length = strlen(cwd);
125 
126 	if (virtual_file_ex(&new_state, path, NULL, CWD_REALPATH)) {
127 		efree(new_state.cwd);
128 		return -1;
129 	}
130 
131 #ifndef PHP_WIN32
132 	if (IS_SLASH(new_state.cwd[new_state.cwd_length - 1])) {
133 		trailing_slash = "";
134 	} else {
135 		trailing_slash = "/";
136 	}
137 
138 	if (snprintf(opened_path, MAXPATHLEN, "%s%s%sXXXXXX", new_state.cwd, trailing_slash, pfx) >= MAXPATHLEN) {
139 		efree(new_state.cwd);
140 		return -1;
141 	}
142 #endif
143 
144 #ifdef PHP_WIN32
145 	cwdw = php_win32_ioutil_any_to_w(new_state.cwd);
146 	pfxw = php_win32_ioutil_any_to_w(pfx);
147 	if (!cwdw || !pfxw) {
148 		free(cwdw);
149 		free(pfxw);
150 		efree(new_state.cwd);
151 		return -1;
152 	}
153 
154 	if (GetTempFileNameW(cwdw, pfxw, 0, pathw)) {
155 		opened_path = php_win32_ioutil_conv_w_to_any(pathw, PHP_WIN32_CP_IGNORE_LEN, &opened_path_len);
156 		if (!opened_path || opened_path_len >= MAXPATHLEN) {
157 			free(cwdw);
158 			free(pfxw);
159 			efree(new_state.cwd);
160 			return -1;
161 		}
162 		assert(strlen(opened_path) == opened_path_len);
163 
164 		/* Some versions of windows set the temp file to be read-only,
165 		 * which means that opening it will fail... */
166 		if (VCWD_CHMOD(opened_path, 0600)) {
167 			free(cwdw);
168 			free(pfxw);
169 			efree(new_state.cwd);
170 			free(opened_path);
171 			return -1;
172 		}
173 		fd = VCWD_OPEN_MODE(opened_path, open_flags, 0600);
174 	}
175 
176 	free(cwdw);
177 	free(pfxw);
178 #elif defined(HAVE_MKSTEMP)
179 	fd = mkstemp(opened_path);
180 #else
181 	if (mktemp(opened_path)) {
182 		fd = VCWD_OPEN(opened_path, open_flags);
183 	}
184 #endif
185 
186 #ifdef PHP_WIN32
187 	if (fd != -1 && opened_path_p) {
188 		*opened_path_p = zend_string_init(opened_path, opened_path_len, 0);
189 	}
190 	free(opened_path);
191 #else
192 	if (fd != -1 && opened_path_p) {
193 		*opened_path_p = zend_string_init(opened_path, strlen(opened_path), 0);
194 	}
195 #endif
196 	efree(new_state.cwd);
197 	return fd;
198 }
199 /* }}} */
200 
201 /*
202  *  Determine where to place temporary files.
203  */
php_get_temporary_directory(void)204 PHPAPI const char* php_get_temporary_directory(void)
205 {
206 	/* Did we determine the temporary directory already? */
207 	if (PG(php_sys_temp_dir)) {
208 		return PG(php_sys_temp_dir);
209 	}
210 
211 	/* Is there a temporary directory "sys_temp_dir" in .ini defined? */
212 	{
213 		char *sys_temp_dir = PG(sys_temp_dir);
214 		if (sys_temp_dir) {
215 			size_t len = strlen(sys_temp_dir);
216 			if (len >= 2 && sys_temp_dir[len - 1] == DEFAULT_SLASH) {
217 				PG(php_sys_temp_dir) = estrndup(sys_temp_dir, len - 1);
218 				return PG(php_sys_temp_dir);
219 			} else if (len >= 1 && sys_temp_dir[len - 1] != DEFAULT_SLASH) {
220 				PG(php_sys_temp_dir) = estrndup(sys_temp_dir, len);
221 				return PG(php_sys_temp_dir);
222 			}
223 		}
224 	}
225 
226 #ifdef PHP_WIN32
227 	/* We can't count on the environment variables TEMP or TMP,
228 	 * and so must make the Win32 API call to get the default
229 	 * directory for temporary files.  Note this call checks
230 	 * the environment values TMP and TEMP (in order) first.
231 	 */
232 	{
233 		wchar_t sTemp[MAXPATHLEN];
234 		char *tmp;
235 		size_t len = GetTempPathW(MAXPATHLEN, sTemp);
236 
237 		if (!len) {
238 			return NULL;
239 		}
240 
241 		if (NULL == (tmp = php_win32_ioutil_conv_w_to_any(sTemp, len, &len))) {
242 			return NULL;
243 		}
244 
245 		PG(php_sys_temp_dir) = estrndup(tmp, len - 1);
246 
247 		free(tmp);
248 		return PG(php_sys_temp_dir);
249 	}
250 #else
251 	/* On Unix use the (usual) TMPDIR environment variable. */
252 	{
253 		char* s = getenv("TMPDIR");
254 		if (s && *s) {
255 			size_t len = strlen(s);
256 
257 			if (s[len - 1] == DEFAULT_SLASH) {
258 				PG(php_sys_temp_dir) = estrndup(s, len - 1);
259 			} else {
260 				PG(php_sys_temp_dir) = estrndup(s, len);
261 			}
262 
263 			return PG(php_sys_temp_dir);
264 		}
265 	}
266 #ifdef P_tmpdir
267 	/* Use the standard default temporary directory. */
268 	if (P_tmpdir) {
269 		PG(php_sys_temp_dir) = estrdup(P_tmpdir);
270 		return PG(php_sys_temp_dir);
271 	}
272 #endif
273 	/* Shouldn't ever(!) end up here ... last ditch default. */
274 	PG(php_sys_temp_dir) = estrdup("/tmp");
275 	return PG(php_sys_temp_dir);
276 #endif
277 }
278 
279 /* {{{ php_open_temporary_file
280  *
281  * Unlike tempnam(), the supplied dir argument takes precedence
282  * over the TMPDIR environment variable
283  * This function should do its best to return a file pointer to a newly created
284  * unique file, on every platform.
285  */
php_open_temporary_fd_ex(const char * dir,const char * pfx,zend_string ** opened_path_p,uint32_t flags)286 PHPAPI int php_open_temporary_fd_ex(const char *dir, const char *pfx, zend_string **opened_path_p, uint32_t flags)
287 {
288 	int fd;
289 	const char *temp_dir;
290 
291 	if (!pfx) {
292 		pfx = "tmp.";
293 	}
294 	if (opened_path_p) {
295 		*opened_path_p = NULL;
296 	}
297 
298 	if (!dir || *dir == '\0') {
299 def_tmp:
300 		temp_dir = php_get_temporary_directory();
301 
302 		if (temp_dir &&
303 		    *temp_dir != '\0' &&
304 		    (!(flags & PHP_TMP_FILE_OPEN_BASEDIR_CHECK_ON_FALLBACK) || !php_check_open_basedir(temp_dir))) {
305 			return php_do_open_temporary_file(temp_dir, pfx, opened_path_p);
306 		} else {
307 			return -1;
308 		}
309 	}
310 
311 	if ((flags & PHP_TMP_FILE_OPEN_BASEDIR_CHECK_ON_EXPLICIT_DIR) && php_check_open_basedir(dir)) {
312 		return -1;
313 	}
314 
315 	/* Try the directory given as parameter. */
316 	fd = php_do_open_temporary_file(dir, pfx, opened_path_p);
317 	if (fd == -1) {
318 		/* Use default temporary directory. */
319 		if (!(flags & PHP_TMP_FILE_SILENT)) {
320 			php_error_docref(NULL, E_NOTICE, "file created in the system's temporary directory");
321 		}
322 		goto def_tmp;
323 	}
324 	return fd;
325 }
326 
php_open_temporary_fd(const char * dir,const char * pfx,zend_string ** opened_path_p)327 PHPAPI int php_open_temporary_fd(const char *dir, const char *pfx, zend_string **opened_path_p)
328 {
329 	return php_open_temporary_fd_ex(dir, pfx, opened_path_p, PHP_TMP_FILE_DEFAULT);
330 }
331 
php_open_temporary_file(const char * dir,const char * pfx,zend_string ** opened_path_p)332 PHPAPI FILE *php_open_temporary_file(const char *dir, const char *pfx, zend_string **opened_path_p)
333 {
334 	FILE *fp;
335 	int fd = php_open_temporary_fd(dir, pfx, opened_path_p);
336 
337 	if (fd == -1) {
338 		return NULL;
339 	}
340 
341 	fp = fdopen(fd, "r+b");
342 	if (fp == NULL) {
343 		close(fd);
344 	}
345 
346 	return fp;
347 }
348 /* }}} */
349