stream.c 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002
  1. /*
  2. +----------------------------------------------------------------------+
  3. | phar:// stream wrapper support |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 2005-2016 The PHP Group |
  6. +----------------------------------------------------------------------+
  7. | This source file is subject to version 3.01 of the PHP license, |
  8. | that is bundled with this package in the file LICENSE, and is |
  9. | available through the world-wide-web at the following url: |
  10. | http://www.php.net/license/3_01.txt. |
  11. | If you did not receive a copy of the PHP license and are unable to |
  12. | obtain it through the world-wide-web, please send a note to |
  13. | license@php.net so we can mail you a copy immediately. |
  14. +----------------------------------------------------------------------+
  15. | Authors: Gregory Beaver <cellog@php.net> |
  16. | Marcus Boerger <helly@php.net> |
  17. +----------------------------------------------------------------------+
  18. */
  19. #define PHAR_STREAM 1
  20. #include "phar_internal.h"
  21. #include "stream.h"
  22. #include "dirstream.h"
  23. php_stream_ops phar_ops = {
  24. phar_stream_write, /* write */
  25. phar_stream_read, /* read */
  26. phar_stream_close, /* close */
  27. phar_stream_flush, /* flush */
  28. "phar stream",
  29. phar_stream_seek, /* seek */
  30. NULL, /* cast */
  31. phar_stream_stat, /* stat */
  32. NULL, /* set option */
  33. };
  34. php_stream_wrapper_ops phar_stream_wops = {
  35. phar_wrapper_open_url,
  36. NULL, /* phar_wrapper_close */
  37. NULL, /* phar_wrapper_stat, */
  38. phar_wrapper_stat, /* stat_url */
  39. phar_wrapper_open_dir, /* opendir */
  40. "phar",
  41. phar_wrapper_unlink, /* unlink */
  42. phar_wrapper_rename, /* rename */
  43. phar_wrapper_mkdir, /* create directory */
  44. phar_wrapper_rmdir, /* remove directory */
  45. };
  46. php_stream_wrapper php_stream_phar_wrapper = {
  47. &phar_stream_wops,
  48. NULL,
  49. 0 /* is_url */
  50. };
  51. /**
  52. * Open a phar file for streams API
  53. */
  54. php_url* phar_parse_url(php_stream_wrapper *wrapper, const char *filename, const char *mode, int options TSRMLS_DC) /* {{{ */
  55. {
  56. php_url *resource;
  57. char *arch = NULL, *entry = NULL, *error;
  58. int arch_len, entry_len;
  59. if (strlen(filename) < 7 || strncasecmp(filename, "phar://", 7)) {
  60. return NULL;
  61. }
  62. if (mode[0] == 'a') {
  63. if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
  64. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: open mode append not supported");
  65. }
  66. return NULL;
  67. }
  68. if (phar_split_fname(filename, strlen(filename), &arch, &arch_len, &entry, &entry_len, 2, (mode[0] == 'w' ? 2 : 0) TSRMLS_CC) == FAILURE) {
  69. if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
  70. if (arch && !entry) {
  71. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: no directory in \"%s\", must have at least phar://%s/ for root directory (always use full path to a new phar)", filename, arch);
  72. arch = NULL;
  73. } else {
  74. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: invalid url or non-existent phar \"%s\"", filename);
  75. }
  76. }
  77. return NULL;
  78. }
  79. resource = ecalloc(1, sizeof(php_url));
  80. resource->scheme = estrndup("phar", 4);
  81. resource->host = arch;
  82. resource->path = entry;
  83. #if MBO_0
  84. if (resource) {
  85. fprintf(stderr, "Alias: %s\n", alias);
  86. fprintf(stderr, "Scheme: %s\n", resource->scheme);
  87. /* fprintf(stderr, "User: %s\n", resource->user);*/
  88. /* fprintf(stderr, "Pass: %s\n", resource->pass ? "***" : NULL);*/
  89. fprintf(stderr, "Host: %s\n", resource->host);
  90. /* fprintf(stderr, "Port: %d\n", resource->port);*/
  91. fprintf(stderr, "Path: %s\n", resource->path);
  92. /* fprintf(stderr, "Query: %s\n", resource->query);*/
  93. /* fprintf(stderr, "Fragment: %s\n", resource->fragment);*/
  94. }
  95. #endif
  96. if (mode[0] == 'w' || (mode[0] == 'r' && mode[1] == '+')) {
  97. phar_archive_data **pphar = NULL, *phar;
  98. if (PHAR_GLOBALS->request_init && PHAR_GLOBALS->phar_fname_map.arBuckets && FAILURE == zend_hash_find(&(PHAR_GLOBALS->phar_fname_map), arch, arch_len, (void **)&pphar)) {
  99. pphar = NULL;
  100. }
  101. if (PHAR_G(readonly) && (!pphar || !(*pphar)->is_data)) {
  102. if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
  103. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: write operations disabled by the php.ini setting phar.readonly");
  104. }
  105. php_url_free(resource);
  106. return NULL;
  107. }
  108. if (phar_open_or_create_filename(resource->host, arch_len, NULL, 0, 0, options, &phar, &error TSRMLS_CC) == FAILURE)
  109. {
  110. if (error) {
  111. if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
  112. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "%s", error);
  113. }
  114. efree(error);
  115. }
  116. php_url_free(resource);
  117. return NULL;
  118. }
  119. if (phar->is_persistent && FAILURE == phar_copy_on_write(&phar TSRMLS_CC)) {
  120. if (error) {
  121. spprintf(&error, 0, "Cannot open cached phar '%s' as writeable, copy on write failed", resource->host);
  122. if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
  123. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "%s", error);
  124. }
  125. efree(error);
  126. }
  127. php_url_free(resource);
  128. return NULL;
  129. }
  130. } else {
  131. if (phar_open_from_filename(resource->host, arch_len, NULL, 0, options, NULL, &error TSRMLS_CC) == FAILURE)
  132. {
  133. if (error) {
  134. if (!(options & PHP_STREAM_URL_STAT_QUIET)) {
  135. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "%s", error);
  136. }
  137. efree(error);
  138. }
  139. php_url_free(resource);
  140. return NULL;
  141. }
  142. }
  143. return resource;
  144. }
  145. /* }}} */
  146. /**
  147. * used for fopen('phar://...') and company
  148. */
  149. static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC) /* {{{ */
  150. {
  151. phar_archive_data *phar;
  152. phar_entry_data *idata;
  153. char *internal_file;
  154. char *error;
  155. HashTable *pharcontext;
  156. php_url *resource = NULL;
  157. php_stream *fpf;
  158. zval **pzoption, *metadata;
  159. uint host_len;
  160. if ((resource = phar_parse_url(wrapper, path, mode, options TSRMLS_CC)) == NULL) {
  161. return NULL;
  162. }
  163. /* we must have at the very least phar://alias.phar/internalfile.php */
  164. if (!resource->scheme || !resource->host || !resource->path) {
  165. php_url_free(resource);
  166. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: invalid url \"%s\"", path);
  167. return NULL;
  168. }
  169. if (strcasecmp("phar", resource->scheme)) {
  170. php_url_free(resource);
  171. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: not a phar stream url \"%s\"", path);
  172. return NULL;
  173. }
  174. host_len = strlen(resource->host);
  175. phar_request_initialize(TSRMLS_C);
  176. /* strip leading "/" */
  177. internal_file = estrdup(resource->path + 1);
  178. if (mode[0] == 'w' || (mode[0] == 'r' && mode[1] == '+')) {
  179. if (NULL == (idata = phar_get_or_create_entry_data(resource->host, host_len, internal_file, strlen(internal_file), mode, 0, &error, 1 TSRMLS_CC))) {
  180. if (error) {
  181. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "%s", error);
  182. efree(error);
  183. } else {
  184. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: file \"%s\" could not be created in phar \"%s\"", internal_file, resource->host);
  185. }
  186. efree(internal_file);
  187. php_url_free(resource);
  188. return NULL;
  189. }
  190. if (error) {
  191. efree(error);
  192. }
  193. fpf = php_stream_alloc(&phar_ops, idata, NULL, mode);
  194. php_url_free(resource);
  195. efree(internal_file);
  196. if (context && context->options && zend_hash_find(HASH_OF(context->options), "phar", sizeof("phar"), (void**)&pzoption) == SUCCESS) {
  197. pharcontext = HASH_OF(*pzoption);
  198. if (idata->internal_file->uncompressed_filesize == 0
  199. && idata->internal_file->compressed_filesize == 0
  200. && zend_hash_find(pharcontext, "compress", sizeof("compress"), (void**)&pzoption) == SUCCESS
  201. && Z_TYPE_PP(pzoption) == IS_LONG
  202. && (Z_LVAL_PP(pzoption) & ~PHAR_ENT_COMPRESSION_MASK) == 0
  203. ) {
  204. idata->internal_file->flags &= ~PHAR_ENT_COMPRESSION_MASK;
  205. idata->internal_file->flags |= Z_LVAL_PP(pzoption);
  206. }
  207. if (zend_hash_find(pharcontext, "metadata", sizeof("metadata"), (void**)&pzoption) == SUCCESS) {
  208. if (idata->internal_file->metadata) {
  209. zval_ptr_dtor(&idata->internal_file->metadata);
  210. idata->internal_file->metadata = NULL;
  211. }
  212. MAKE_STD_ZVAL(idata->internal_file->metadata);
  213. metadata = *pzoption;
  214. ZVAL_ZVAL(idata->internal_file->metadata, metadata, 1, 0);
  215. idata->phar->is_modified = 1;
  216. }
  217. }
  218. if (opened_path) {
  219. spprintf(opened_path, MAXPATHLEN, "phar://%s/%s", idata->phar->fname, idata->internal_file->filename);
  220. }
  221. return fpf;
  222. } else {
  223. if (!*internal_file && (options & STREAM_OPEN_FOR_INCLUDE)) {
  224. /* retrieve the stub */
  225. if (FAILURE == phar_get_archive(&phar, resource->host, host_len, NULL, 0, NULL TSRMLS_CC)) {
  226. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "file %s is not a valid phar archive", resource->host);
  227. efree(internal_file);
  228. php_url_free(resource);
  229. return NULL;
  230. }
  231. if (phar->is_tar || phar->is_zip) {
  232. if ((FAILURE == phar_get_entry_data(&idata, resource->host, host_len, ".phar/stub.php", sizeof(".phar/stub.php")-1, "r", 0, &error, 0 TSRMLS_CC)) || !idata) {
  233. goto idata_error;
  234. }
  235. efree(internal_file);
  236. if (opened_path) {
  237. spprintf(opened_path, MAXPATHLEN, "%s", phar->fname);
  238. }
  239. php_url_free(resource);
  240. goto phar_stub;
  241. } else {
  242. phar_entry_info *entry;
  243. entry = (phar_entry_info *) ecalloc(1, sizeof(phar_entry_info));
  244. entry->is_temp_dir = 1;
  245. entry->filename = estrndup("", 0);
  246. entry->filename_len = 0;
  247. entry->phar = phar;
  248. entry->offset = entry->offset_abs = 0;
  249. entry->compressed_filesize = entry->uncompressed_filesize = phar->halt_offset;
  250. entry->is_crc_checked = 1;
  251. idata = (phar_entry_data *) ecalloc(1, sizeof(phar_entry_data));
  252. idata->fp = phar_get_pharfp(phar TSRMLS_CC);
  253. idata->phar = phar;
  254. idata->internal_file = entry;
  255. if (!phar->is_persistent) {
  256. ++(entry->phar->refcount);
  257. }
  258. ++(entry->fp_refcount);
  259. php_url_free(resource);
  260. if (opened_path) {
  261. spprintf(opened_path, MAXPATHLEN, "%s", phar->fname);
  262. }
  263. efree(internal_file);
  264. goto phar_stub;
  265. }
  266. }
  267. /* read-only access is allowed to magic files in .phar directory */
  268. if ((FAILURE == phar_get_entry_data(&idata, resource->host, host_len, internal_file, strlen(internal_file), "r", 0, &error, 0 TSRMLS_CC)) || !idata) {
  269. idata_error:
  270. if (error) {
  271. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "%s", error);
  272. efree(error);
  273. } else {
  274. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: \"%s\" is not a file in phar \"%s\"", internal_file, resource->host);
  275. }
  276. efree(internal_file);
  277. php_url_free(resource);
  278. return NULL;
  279. }
  280. }
  281. php_url_free(resource);
  282. #if MBO_0
  283. fprintf(stderr, "Pharname: %s\n", idata->phar->filename);
  284. fprintf(stderr, "Filename: %s\n", internal_file);
  285. fprintf(stderr, "Entry: %s\n", idata->internal_file->filename);
  286. fprintf(stderr, "Size: %u\n", idata->internal_file->uncompressed_filesize);
  287. fprintf(stderr, "Compressed: %u\n", idata->internal_file->flags);
  288. fprintf(stderr, "Offset: %u\n", idata->internal_file->offset_within_phar);
  289. fprintf(stderr, "Cached: %s\n", idata->internal_file->filedata ? "yes" : "no");
  290. #endif
  291. /* check length, crc32 */
  292. if (!idata->internal_file->is_crc_checked && phar_postprocess_file(idata, idata->internal_file->crc32, &error, 2 TSRMLS_CC) != SUCCESS) {
  293. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "%s", error);
  294. efree(error);
  295. phar_entry_delref(idata TSRMLS_CC);
  296. efree(internal_file);
  297. return NULL;
  298. }
  299. if (!PHAR_G(cwd_init) && options & STREAM_OPEN_FOR_INCLUDE) {
  300. char *entry = idata->internal_file->filename, *cwd;
  301. PHAR_G(cwd_init) = 1;
  302. if ((idata->phar->is_tar || idata->phar->is_zip) && idata->internal_file->filename_len == sizeof(".phar/stub.php")-1 && !strncmp(idata->internal_file->filename, ".phar/stub.php", sizeof(".phar/stub.php")-1)) {
  303. /* we're executing the stub, which doesn't count as a file */
  304. PHAR_G(cwd_init) = 0;
  305. } else if ((cwd = strrchr(entry, '/'))) {
  306. PHAR_G(cwd_len) = cwd - entry;
  307. PHAR_G(cwd) = estrndup(entry, PHAR_G(cwd_len));
  308. } else {
  309. /* root directory */
  310. PHAR_G(cwd_len) = 0;
  311. PHAR_G(cwd) = NULL;
  312. }
  313. }
  314. if (opened_path) {
  315. spprintf(opened_path, MAXPATHLEN, "phar://%s/%s", idata->phar->fname, idata->internal_file->filename);
  316. }
  317. efree(internal_file);
  318. phar_stub:
  319. fpf = php_stream_alloc(&phar_ops, idata, NULL, mode);
  320. return fpf;
  321. }
  322. /* }}} */
  323. /**
  324. * Used for fclose($fp) where $fp is a phar archive
  325. */
  326. static int phar_stream_close(php_stream *stream, int close_handle TSRMLS_DC) /* {{{ */
  327. {
  328. phar_entry_delref((phar_entry_data *)stream->abstract TSRMLS_CC);
  329. return 0;
  330. }
  331. /* }}} */
  332. /**
  333. * used for fread($fp) and company on a fopen()ed phar file handle
  334. */
  335. static size_t phar_stream_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) /* {{{ */
  336. {
  337. phar_entry_data *data = (phar_entry_data *)stream->abstract;
  338. size_t got;
  339. phar_entry_info *entry;
  340. if (data->internal_file->link) {
  341. entry = phar_get_link_source(data->internal_file TSRMLS_CC);
  342. } else {
  343. entry = data->internal_file;
  344. }
  345. if (entry->is_deleted) {
  346. stream->eof = 1;
  347. return 0;
  348. }
  349. /* use our proxy position */
  350. php_stream_seek(data->fp, data->position + data->zero, SEEK_SET);
  351. got = php_stream_read(data->fp, buf, MIN(count, entry->uncompressed_filesize - data->position));
  352. data->position = php_stream_tell(data->fp) - data->zero;
  353. stream->eof = (data->position == (off_t) entry->uncompressed_filesize);
  354. return got;
  355. }
  356. /* }}} */
  357. /**
  358. * Used for fseek($fp) on a phar file handle
  359. */
  360. static int phar_stream_seek(php_stream *stream, off_t offset, int whence, off_t *newoffset TSRMLS_DC) /* {{{ */
  361. {
  362. phar_entry_data *data = (phar_entry_data *)stream->abstract;
  363. phar_entry_info *entry;
  364. int res;
  365. off_t temp;
  366. if (data->internal_file->link) {
  367. entry = phar_get_link_source(data->internal_file TSRMLS_CC);
  368. } else {
  369. entry = data->internal_file;
  370. }
  371. switch (whence) {
  372. case SEEK_END :
  373. temp = data->zero + entry->uncompressed_filesize + offset;
  374. break;
  375. case SEEK_CUR :
  376. temp = data->zero + data->position + offset;
  377. break;
  378. case SEEK_SET :
  379. temp = data->zero + offset;
  380. break;
  381. default:
  382. temp = 0;
  383. }
  384. if (temp > data->zero + (off_t) entry->uncompressed_filesize) {
  385. *newoffset = -1;
  386. return -1;
  387. }
  388. if (temp < data->zero) {
  389. *newoffset = -1;
  390. return -1;
  391. }
  392. res = php_stream_seek(data->fp, temp, SEEK_SET);
  393. *newoffset = php_stream_tell(data->fp) - data->zero;
  394. data->position = *newoffset;
  395. return res;
  396. }
  397. /* }}} */
  398. /**
  399. * Used for writing to a phar file
  400. */
  401. static size_t phar_stream_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC) /* {{{ */
  402. {
  403. phar_entry_data *data = (phar_entry_data *) stream->abstract;
  404. php_stream_seek(data->fp, data->position, SEEK_SET);
  405. if (count != php_stream_write(data->fp, buf, count)) {
  406. php_stream_wrapper_log_error(stream->wrapper, stream->flags TSRMLS_CC, "phar error: Could not write %d characters to \"%s\" in phar \"%s\"", (int) count, data->internal_file->filename, data->phar->fname);
  407. return -1;
  408. }
  409. data->position = php_stream_tell(data->fp);
  410. if (data->position > (off_t)data->internal_file->uncompressed_filesize) {
  411. data->internal_file->uncompressed_filesize = data->position;
  412. }
  413. data->internal_file->compressed_filesize = data->internal_file->uncompressed_filesize;
  414. data->internal_file->old_flags = data->internal_file->flags;
  415. data->internal_file->is_modified = 1;
  416. return count;
  417. }
  418. /* }}} */
  419. /**
  420. * Used to save work done on a writeable phar
  421. */
  422. static int phar_stream_flush(php_stream *stream TSRMLS_DC) /* {{{ */
  423. {
  424. char *error;
  425. int ret;
  426. phar_entry_data *data = (phar_entry_data *) stream->abstract;
  427. if (data->internal_file->is_modified) {
  428. data->internal_file->timestamp = time(0);
  429. ret = phar_flush(data->phar, 0, 0, 0, &error TSRMLS_CC);
  430. if (error) {
  431. php_stream_wrapper_log_error(stream->wrapper, REPORT_ERRORS TSRMLS_CC, "%s", error);
  432. efree(error);
  433. }
  434. return ret;
  435. } else {
  436. return EOF;
  437. }
  438. }
  439. /* }}} */
  440. /* {{{ phar_dostat */
  441. /**
  442. * stat an opened phar file handle stream, used by phar_stat()
  443. */
  444. void phar_dostat(phar_archive_data *phar, phar_entry_info *data, php_stream_statbuf *ssb, zend_bool is_temp_dir TSRMLS_DC)
  445. {
  446. memset(ssb, 0, sizeof(php_stream_statbuf));
  447. if (!is_temp_dir && !data->is_dir) {
  448. ssb->sb.st_size = data->uncompressed_filesize;
  449. ssb->sb.st_mode = data->flags & PHAR_ENT_PERM_MASK;
  450. ssb->sb.st_mode |= S_IFREG; /* regular file */
  451. /* timestamp is just the timestamp when this was added to the phar */
  452. #ifdef NETWARE
  453. ssb->sb.st_mtime.tv_sec = data->timestamp;
  454. ssb->sb.st_atime.tv_sec = data->timestamp;
  455. ssb->sb.st_ctime.tv_sec = data->timestamp;
  456. #else
  457. ssb->sb.st_mtime = data->timestamp;
  458. ssb->sb.st_atime = data->timestamp;
  459. ssb->sb.st_ctime = data->timestamp;
  460. #endif
  461. } else if (!is_temp_dir && data->is_dir) {
  462. ssb->sb.st_size = 0;
  463. ssb->sb.st_mode = data->flags & PHAR_ENT_PERM_MASK;
  464. ssb->sb.st_mode |= S_IFDIR; /* regular directory */
  465. /* timestamp is just the timestamp when this was added to the phar */
  466. #ifdef NETWARE
  467. ssb->sb.st_mtime.tv_sec = data->timestamp;
  468. ssb->sb.st_atime.tv_sec = data->timestamp;
  469. ssb->sb.st_ctime.tv_sec = data->timestamp;
  470. #else
  471. ssb->sb.st_mtime = data->timestamp;
  472. ssb->sb.st_atime = data->timestamp;
  473. ssb->sb.st_ctime = data->timestamp;
  474. #endif
  475. } else {
  476. ssb->sb.st_size = 0;
  477. ssb->sb.st_mode = 0777;
  478. ssb->sb.st_mode |= S_IFDIR; /* regular directory */
  479. #ifdef NETWARE
  480. ssb->sb.st_mtime.tv_sec = phar->max_timestamp;
  481. ssb->sb.st_atime.tv_sec = phar->max_timestamp;
  482. ssb->sb.st_ctime.tv_sec = phar->max_timestamp;
  483. #else
  484. ssb->sb.st_mtime = phar->max_timestamp;
  485. ssb->sb.st_atime = phar->max_timestamp;
  486. ssb->sb.st_ctime = phar->max_timestamp;
  487. #endif
  488. }
  489. if (!phar->is_writeable) {
  490. ssb->sb.st_mode = (ssb->sb.st_mode & 0555) | (ssb->sb.st_mode & ~0777);
  491. }
  492. ssb->sb.st_nlink = 1;
  493. ssb->sb.st_rdev = -1;
  494. /* this is only for APC, so use /dev/null device - no chance of conflict there! */
  495. ssb->sb.st_dev = 0xc;
  496. /* generate unique inode number for alias/filename, so no phars will conflict */
  497. if (!is_temp_dir) {
  498. ssb->sb.st_ino = data->inode;
  499. }
  500. #ifndef PHP_WIN32
  501. ssb->sb.st_blksize = -1;
  502. ssb->sb.st_blocks = -1;
  503. #endif
  504. }
  505. /* }}}*/
  506. /**
  507. * Stat an opened phar file handle
  508. */
  509. static int phar_stream_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC) /* {{{ */
  510. {
  511. phar_entry_data *data = (phar_entry_data *)stream->abstract;
  512. /* If ssb is NULL then someone is misbehaving */
  513. if (!ssb) {
  514. return -1;
  515. }
  516. phar_dostat(data->phar, data->internal_file, ssb, 0 TSRMLS_CC);
  517. return 0;
  518. }
  519. /* }}} */
  520. /**
  521. * Stream wrapper stat implementation of stat()
  522. */
  523. static int phar_wrapper_stat(php_stream_wrapper *wrapper, const char *url, int flags,
  524. php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC) /* {{{ */
  525. {
  526. php_url *resource = NULL;
  527. char *internal_file, *error;
  528. phar_archive_data *phar;
  529. phar_entry_info *entry;
  530. uint host_len;
  531. int internal_file_len;
  532. if ((resource = phar_parse_url(wrapper, url, "r", flags|PHP_STREAM_URL_STAT_QUIET TSRMLS_CC)) == NULL) {
  533. return FAILURE;
  534. }
  535. /* we must have at the very least phar://alias.phar/internalfile.php */
  536. if (!resource->scheme || !resource->host || !resource->path) {
  537. php_url_free(resource);
  538. return FAILURE;
  539. }
  540. if (strcasecmp("phar", resource->scheme)) {
  541. php_url_free(resource);
  542. return FAILURE;
  543. }
  544. host_len = strlen(resource->host);
  545. phar_request_initialize(TSRMLS_C);
  546. internal_file = resource->path + 1; /* strip leading "/" */
  547. /* find the phar in our trusty global hash indexed by alias (host of phar://blah.phar/file.whatever) */
  548. if (FAILURE == phar_get_archive(&phar, resource->host, host_len, NULL, 0, &error TSRMLS_CC)) {
  549. php_url_free(resource);
  550. if (error) {
  551. efree(error);
  552. }
  553. return FAILURE;
  554. }
  555. if (error) {
  556. efree(error);
  557. }
  558. if (*internal_file == '\0') {
  559. /* root directory requested */
  560. phar_dostat(phar, NULL, ssb, 1 TSRMLS_CC);
  561. php_url_free(resource);
  562. return SUCCESS;
  563. }
  564. if (!phar->manifest.arBuckets) {
  565. php_url_free(resource);
  566. return FAILURE;
  567. }
  568. internal_file_len = strlen(internal_file);
  569. /* search through the manifest of files, and if we have an exact match, it's a file */
  570. if (SUCCESS == zend_hash_find(&phar->manifest, internal_file, internal_file_len, (void**)&entry)) {
  571. phar_dostat(phar, entry, ssb, 0 TSRMLS_CC);
  572. php_url_free(resource);
  573. return SUCCESS;
  574. }
  575. if (zend_hash_exists(&(phar->virtual_dirs), internal_file, internal_file_len)) {
  576. phar_dostat(phar, NULL, ssb, 1 TSRMLS_CC);
  577. php_url_free(resource);
  578. return SUCCESS;
  579. }
  580. /* check for mounted directories */
  581. if (phar->mounted_dirs.arBuckets && zend_hash_num_elements(&phar->mounted_dirs)) {
  582. char *str_key;
  583. ulong unused;
  584. uint keylen;
  585. HashPosition pos;
  586. for (zend_hash_internal_pointer_reset_ex(&phar->mounted_dirs, &pos);
  587. HASH_KEY_NON_EXISTENT != zend_hash_get_current_key_ex(&phar->mounted_dirs, &str_key, &keylen, &unused, 0, &pos);
  588. zend_hash_move_forward_ex(&phar->mounted_dirs, &pos)
  589. ) {
  590. if ((int)keylen >= internal_file_len || strncmp(str_key, internal_file, keylen)) {
  591. continue;
  592. } else {
  593. char *test;
  594. int test_len;
  595. php_stream_statbuf ssbi;
  596. if (SUCCESS != zend_hash_find(&phar->manifest, str_key, keylen, (void **) &entry)) {
  597. goto free_resource;
  598. }
  599. if (!entry->tmp || !entry->is_mounted) {
  600. goto free_resource;
  601. }
  602. test_len = spprintf(&test, MAXPATHLEN, "%s%s", entry->tmp, internal_file + keylen);
  603. if (SUCCESS != php_stream_stat_path(test, &ssbi)) {
  604. efree(test);
  605. continue;
  606. }
  607. /* mount the file/directory just in time */
  608. if (SUCCESS != phar_mount_entry(phar, test, test_len, internal_file, internal_file_len TSRMLS_CC)) {
  609. efree(test);
  610. goto free_resource;
  611. }
  612. efree(test);
  613. if (SUCCESS != zend_hash_find(&phar->manifest, internal_file, internal_file_len, (void**)&entry)) {
  614. goto free_resource;
  615. }
  616. phar_dostat(phar, entry, ssb, 0 TSRMLS_CC);
  617. php_url_free(resource);
  618. return SUCCESS;
  619. }
  620. }
  621. }
  622. free_resource:
  623. php_url_free(resource);
  624. return FAILURE;
  625. }
  626. /* }}} */
  627. /**
  628. * Unlink a file within a phar archive
  629. */
  630. static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context TSRMLS_DC) /* {{{ */
  631. {
  632. php_url *resource;
  633. char *internal_file, *error;
  634. int internal_file_len;
  635. phar_entry_data *idata;
  636. phar_archive_data **pphar;
  637. uint host_len;
  638. if ((resource = phar_parse_url(wrapper, url, "rb", options TSRMLS_CC)) == NULL) {
  639. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: unlink failed");
  640. return 0;
  641. }
  642. /* we must have at the very least phar://alias.phar/internalfile.php */
  643. if (!resource->scheme || !resource->host || !resource->path) {
  644. php_url_free(resource);
  645. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: invalid url \"%s\"", url);
  646. return 0;
  647. }
  648. if (strcasecmp("phar", resource->scheme)) {
  649. php_url_free(resource);
  650. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: not a phar stream url \"%s\"", url);
  651. return 0;
  652. }
  653. host_len = strlen(resource->host);
  654. phar_request_initialize(TSRMLS_C);
  655. if (FAILURE == zend_hash_find(&(PHAR_GLOBALS->phar_fname_map), resource->host, host_len, (void **) &pphar)) {
  656. pphar = NULL;
  657. }
  658. if (PHAR_G(readonly) && (!pphar || !(*pphar)->is_data)) {
  659. php_url_free(resource);
  660. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: write operations disabled by the php.ini setting phar.readonly");
  661. return 0;
  662. }
  663. /* need to copy to strip leading "/", will get touched again */
  664. internal_file = estrdup(resource->path + 1);
  665. internal_file_len = strlen(internal_file);
  666. if (FAILURE == phar_get_entry_data(&idata, resource->host, host_len, internal_file, internal_file_len, "r", 0, &error, 1 TSRMLS_CC)) {
  667. /* constraints of fp refcount were not met */
  668. if (error) {
  669. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "unlink of \"%s\" failed: %s", url, error);
  670. efree(error);
  671. } else {
  672. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "unlink of \"%s\" failed, file does not exist", url);
  673. }
  674. efree(internal_file);
  675. php_url_free(resource);
  676. return 0;
  677. }
  678. if (error) {
  679. efree(error);
  680. }
  681. if (idata->internal_file->fp_refcount > 1) {
  682. /* more than just our fp resource is open for this file */
  683. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "phar error: \"%s\" in phar \"%s\", has open file pointers, cannot unlink", internal_file, resource->host);
  684. efree(internal_file);
  685. php_url_free(resource);
  686. phar_entry_delref(idata TSRMLS_CC);
  687. return 0;
  688. }
  689. php_url_free(resource);
  690. efree(internal_file);
  691. phar_entry_remove(idata, &error TSRMLS_CC);
  692. if (error) {
  693. php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "%s", error);
  694. efree(error);
  695. }
  696. return 1;
  697. }
  698. /* }}} */
  699. static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context TSRMLS_DC) /* {{{ */
  700. {
  701. php_url *resource_from, *resource_to;
  702. char *error;
  703. phar_archive_data *phar, *pfrom, *pto;
  704. phar_entry_info *entry;
  705. uint host_len;
  706. int is_dir = 0;
  707. int is_modified = 0;
  708. error = NULL;
  709. if ((resource_from = phar_parse_url(wrapper, url_from, "wb", options|PHP_STREAM_URL_STAT_QUIET TSRMLS_CC)) == NULL) {
  710. php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"", url_from, url_to, url_from);
  711. return 0;
  712. }
  713. if (SUCCESS != phar_get_archive(&pfrom, resource_from->host, strlen(resource_from->host), NULL, 0, &error TSRMLS_CC)) {
  714. pfrom = NULL;
  715. if (error) {
  716. efree(error);
  717. }
  718. }
  719. if (PHAR_G(readonly) && (!pfrom || !pfrom->is_data)) {
  720. php_url_free(resource_from);
  721. php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: Write operations disabled by the php.ini setting phar.readonly");
  722. return 0;
  723. }
  724. if ((resource_to = phar_parse_url(wrapper, url_to, "wb", options|PHP_STREAM_URL_STAT_QUIET TSRMLS_CC)) == NULL) {
  725. php_url_free(resource_from);
  726. php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid or non-writable url \"%s\"", url_from, url_to, url_to);
  727. return 0;
  728. }
  729. if (SUCCESS != phar_get_archive(&pto, resource_to->host, strlen(resource_to->host), NULL, 0, &error TSRMLS_CC)) {
  730. if (error) {
  731. efree(error);
  732. }
  733. pto = NULL;
  734. }
  735. if (PHAR_G(readonly) && (!pto || !pto->is_data)) {
  736. php_url_free(resource_from);
  737. php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: Write operations disabled by the php.ini setting phar.readonly");
  738. return 0;
  739. }
  740. if (strcmp(resource_from->host, resource_to->host)) {
  741. php_url_free(resource_from);
  742. php_url_free(resource_to);
  743. php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\", not within the same phar archive", url_from, url_to);
  744. return 0;
  745. }
  746. /* we must have at the very least phar://alias.phar/internalfile.php */
  747. if (!resource_from->scheme || !resource_from->host || !resource_from->path) {
  748. php_url_free(resource_from);
  749. php_url_free(resource_to);
  750. php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid url \"%s\"", url_from, url_to, url_from);
  751. return 0;
  752. }
  753. if (!resource_to->scheme || !resource_to->host || !resource_to->path) {
  754. php_url_free(resource_from);
  755. php_url_free(resource_to);
  756. php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": invalid url \"%s\"", url_from, url_to, url_to);
  757. return 0;
  758. }
  759. if (strcasecmp("phar", resource_from->scheme)) {
  760. php_url_free(resource_from);
  761. php_url_free(resource_to);
  762. php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": not a phar stream url \"%s\"", url_from, url_to, url_from);
  763. return 0;
  764. }
  765. if (strcasecmp("phar", resource_to->scheme)) {
  766. php_url_free(resource_from);
  767. php_url_free(resource_to);
  768. php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": not a phar stream url \"%s\"", url_from, url_to, url_to);
  769. return 0;
  770. }
  771. host_len = strlen(resource_from->host);
  772. if (SUCCESS != phar_get_archive(&phar, resource_from->host, host_len, NULL, 0, &error TSRMLS_CC)) {
  773. php_url_free(resource_from);
  774. php_url_free(resource_to);
  775. php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
  776. efree(error);
  777. return 0;
  778. }
  779. if (phar->is_persistent && FAILURE == phar_copy_on_write(&phar TSRMLS_CC)) {
  780. php_url_free(resource_from);
  781. php_url_free(resource_to);
  782. php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": could not make cached phar writeable", url_from, url_to);
  783. return 0;
  784. }
  785. if (SUCCESS == zend_hash_find(&(phar->manifest), resource_from->path+1, strlen(resource_from->path)-1, (void **)&entry)) {
  786. phar_entry_info new, *source;
  787. /* perform rename magic */
  788. if (entry->is_deleted) {
  789. php_url_free(resource_from);
  790. php_url_free(resource_to);
  791. php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\" from extracted phar archive, source has been deleted", url_from, url_to);
  792. return 0;
  793. }
  794. /* transfer all data over to the new entry */
  795. memcpy((void *) &new, (void *) entry, sizeof(phar_entry_info));
  796. /* mark the old one for deletion */
  797. entry->is_deleted = 1;
  798. entry->fp = NULL;
  799. entry->metadata = 0;
  800. entry->link = entry->tmp = NULL;
  801. source = entry;
  802. /* add to the manifest, and then store the pointer to the new guy in entry */
  803. zend_hash_add(&(phar->manifest), resource_to->path+1, strlen(resource_to->path)-1, (void **)&new, sizeof(phar_entry_info), (void **) &entry);
  804. entry->filename = estrdup(resource_to->path+1);
  805. if (FAILURE == phar_copy_entry_fp(source, entry, &error TSRMLS_CC)) {
  806. php_url_free(resource_from);
  807. php_url_free(resource_to);
  808. php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
  809. efree(error);
  810. zend_hash_del(&(phar->manifest), entry->filename, strlen(entry->filename));
  811. return 0;
  812. }
  813. is_modified = 1;
  814. entry->is_modified = 1;
  815. entry->filename_len = strlen(entry->filename);
  816. is_dir = entry->is_dir;
  817. } else {
  818. is_dir = zend_hash_exists(&(phar->virtual_dirs), resource_from->path+1, strlen(resource_from->path)-1);
  819. if (!is_dir) {
  820. /* file does not exist */
  821. php_url_free(resource_from);
  822. php_url_free(resource_to);
  823. php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\" from extracted phar archive, source does not exist", url_from, url_to);
  824. return 0;
  825. }
  826. }
  827. /* Rename directory. Update all nested paths */
  828. if (is_dir) {
  829. int key_type;
  830. char *str_key, *new_str_key;
  831. uint key_len, new_key_len;
  832. ulong unused;
  833. uint from_len = strlen(resource_from->path+1);
  834. uint to_len = strlen(resource_to->path+1);
  835. for (zend_hash_internal_pointer_reset(&phar->manifest);
  836. HASH_KEY_NON_EXISTENT != (key_type = zend_hash_get_current_key_ex(&phar->manifest, &str_key, &key_len, &unused, 0, NULL)) &&
  837. SUCCESS == zend_hash_get_current_data(&phar->manifest, (void **) &entry);
  838. zend_hash_move_forward(&phar->manifest)
  839. ) {
  840. if (!entry->is_deleted &&
  841. key_len > from_len &&
  842. memcmp(str_key, resource_from->path+1, from_len) == 0 &&
  843. IS_SLASH(str_key[from_len])) {
  844. new_key_len = key_len + to_len - from_len;
  845. new_str_key = emalloc(new_key_len+1);
  846. memcpy(new_str_key, resource_to->path + 1, to_len);
  847. memcpy(new_str_key + to_len, str_key + from_len, key_len - from_len);
  848. new_str_key[new_key_len] = 0;
  849. is_modified = 1;
  850. entry->is_modified = 1;
  851. efree(entry->filename);
  852. entry->filename = new_str_key;
  853. entry->filename_len = new_key_len;
  854. zend_hash_update_current_key_ex(&phar->manifest, key_type, new_str_key, new_key_len, 0, HASH_UPDATE_KEY_ANYWAY, NULL);
  855. }
  856. }
  857. for (zend_hash_internal_pointer_reset(&phar->virtual_dirs);
  858. HASH_KEY_NON_EXISTENT != (key_type = zend_hash_get_current_key_ex(&phar->virtual_dirs, &str_key, &key_len, &unused, 0, NULL));
  859. zend_hash_move_forward(&phar->virtual_dirs)
  860. ) {
  861. if (key_len >= from_len &&
  862. memcmp(str_key, resource_from->path+1, from_len) == 0 &&
  863. (key_len == from_len || IS_SLASH(str_key[from_len]))) {
  864. new_key_len = key_len + to_len - from_len;
  865. new_str_key = emalloc(new_key_len+1);
  866. memcpy(new_str_key, resource_to->path + 1, to_len);
  867. memcpy(new_str_key + to_len, str_key + from_len, key_len - from_len);
  868. new_str_key[new_key_len] = 0;
  869. zend_hash_update_current_key_ex(&phar->virtual_dirs, key_type, new_str_key, new_key_len, 0, HASH_UPDATE_KEY_ANYWAY, NULL);
  870. efree(new_str_key);
  871. }
  872. }
  873. for (zend_hash_internal_pointer_reset(&phar->mounted_dirs);
  874. HASH_KEY_NON_EXISTENT != (key_type = zend_hash_get_current_key_ex(&phar->mounted_dirs, &str_key, &key_len, &unused, 0, NULL)) &&
  875. SUCCESS == zend_hash_get_current_data(&phar->mounted_dirs, (void **) &entry);
  876. zend_hash_move_forward(&phar->mounted_dirs)
  877. ) {
  878. if (key_len >= from_len &&
  879. memcmp(str_key, resource_from->path+1, from_len) == 0 &&
  880. (key_len == from_len || IS_SLASH(str_key[from_len]))) {
  881. new_key_len = key_len + to_len - from_len;
  882. new_str_key = emalloc(new_key_len+1);
  883. memcpy(new_str_key, resource_to->path + 1, to_len);
  884. memcpy(new_str_key + to_len, str_key + from_len, key_len - from_len);
  885. new_str_key[new_key_len] = 0;
  886. zend_hash_update_current_key_ex(&phar->mounted_dirs, key_type, new_str_key, new_key_len, 0, HASH_UPDATE_KEY_ANYWAY, NULL);
  887. efree(new_str_key);
  888. }
  889. }
  890. }
  891. if (is_modified) {
  892. phar_flush(phar, 0, 0, 0, &error TSRMLS_CC);
  893. if (error) {
  894. php_url_free(resource_from);
  895. php_url_free(resource_to);
  896. php_error_docref(NULL TSRMLS_CC, E_WARNING, "phar error: cannot rename \"%s\" to \"%s\": %s", url_from, url_to, error);
  897. efree(error);
  898. return 0;
  899. }
  900. }
  901. php_url_free(resource_from);
  902. php_url_free(resource_to);
  903. return 1;
  904. }
  905. /* }}} */
  906. /*
  907. * Local variables:
  908. * tab-width: 4
  909. * c-basic-offset: 4
  910. * End:
  911. * vim600: noet sw=4 ts=4 fdm=marker
  912. * vim<600: noet sw=4 ts=4
  913. */