/*
  +----------------------------------------------------------------------+
  | phar php single-file executable PHP extension                        |
  +----------------------------------------------------------------------+
  | Copyright © The PHP Group and Contributors.                          |
  +----------------------------------------------------------------------+
  | This source file is subject to the Modified BSD License that is      |
  | bundled with this package in the file LICENSE, and is available      |
  | through the World Wide Web at <https://www.php.net/license/>.        |
  |                                                                      |
  | SPDX-License-Identifier: BSD-3-Clause                                |
  +----------------------------------------------------------------------+
  | Authors: Gregory Beaver <cellog@php.net>                             |
  |          Marcus Boerger <helly@php.net>                              |
  +----------------------------------------------------------------------+
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <time.h>
#include "php.h"
#include "tar.h"
#include "pharzip.h"
#include "zend_hash.h"
#include "ext/spl/spl_directory.h"

/* PHP_ because this is public information via MINFO */
#define PHP_PHAR_API_VERSION      "1.1.1"
/* x.y.z maps to 0xyz0 */
#define PHAR_API_VERSION          0x1110
/* if we bump PHAR_API_VERSION, change this from 0x1100 to PHAR_API_VERSION */
#define PHAR_API_VERSION_NODIR    0x1100
#define PHAR_API_MIN_DIR          0x1110
#define PHAR_API_MIN_READ         0x1000
#define PHAR_API_MAJORVERSION     0x1000
#define PHAR_API_MAJORVER_MASK    0xF000
#define PHAR_API_VER_MASK         0xFFF0

#define PHAR_HDR_SIGNATURE        0x00010000

/* flags for defining that the entire file should be compressed */
#define PHAR_FILE_COMPRESSION_MASK 0x00F00000
#define PHAR_FILE_COMPRESSED_NONE  0x00000000
#define PHAR_FILE_COMPRESSED_GZ    0x00100000
#define PHAR_FILE_COMPRESSED_BZ2   0x00200000

#define PHAR_SIG_MD5              0x0001
#define PHAR_SIG_SHA1             0x0002
#define PHAR_SIG_SHA256           0x0003
#define PHAR_SIG_SHA512           0x0004
#define PHAR_SIG_OPENSSL          0x0010
#define PHAR_SIG_OPENSSL_SHA256   0x0011
#define PHAR_SIG_OPENSSL_SHA512   0x0012

/* flags byte for each file adheres to these bitmasks.
   All unused values are reserved */
#define PHAR_ENT_COMPRESSION_MASK 0x0000F000
#define PHAR_ENT_COMPRESSED_NONE  0x00000000
#define PHAR_ENT_COMPRESSED_GZ    0x00001000
#define PHAR_ENT_COMPRESSED_BZ2   0x00002000

#define PHAR_ENT_PERM_MASK        0x000001FF
#define PHAR_ENT_PERM_MASK_USR    0x000001C0
#define PHAR_ENT_PERM_SHIFT_USR   6
#define PHAR_ENT_PERM_MASK_GRP    0x00000038
#define PHAR_ENT_PERM_SHIFT_GRP   3
#define PHAR_ENT_PERM_MASK_OTH    0x00000007
#define PHAR_ENT_PERM_DEF_FILE    0x000001B6
#define PHAR_ENT_PERM_DEF_DIR     0x000001FF

#define PHAR_FORMAT_SAME    0
#define PHAR_FORMAT_PHAR    1
#define PHAR_FORMAT_TAR     2
#define PHAR_FORMAT_ZIP     3

#define TAR_FILE    '0'
#define TAR_LINK    '1'
#define TAR_SYMLINK '2'
#define TAR_DIR     '5'
#define TAR_NEW     '8'
#define TAR_GLOBAL_HDR 'g'
#define TAR_FILE_HDR   'x'

#define PHAR_MUNG_PHP_SELF			(1<<0)
#define PHAR_MUNG_REQUEST_URI		(1<<1)
#define PHAR_MUNG_SCRIPT_NAME		(1<<2)
#define PHAR_MUNG_SCRIPT_FILENAME	(1<<3)

typedef struct _phar_entry_fp phar_entry_fp;
typedef struct _phar_archive_data phar_archive_data;

ZEND_BEGIN_MODULE_GLOBALS(phar)
	/* a list of phar_archive_data objects that reference a cached phar, so
	   that if copy-on-write is performed, we can swap them out for the new value */
	HashTable   phar_persist_map;
	HashTable   phar_fname_map;
	/* for cached phars, this is a per-process store of fp/ufp */
	phar_entry_fp *cached_fp;
	HashTable   phar_alias_map;
	char*       cache_list;
	int         phar_SERVER_mung_list;
	bool        readonly;
	bool        manifest_cached;
	bool        persist;
	bool        has_zlib;
	bool        has_bz2;
	bool   readonly_orig;
	bool   require_hash_orig;
	bool   intercepted;
	bool        request_init;
	bool        require_hash;
	bool        request_done;
	bool        request_ends;
	zif_handler orig_fopen;
	zif_handler orig_file_get_contents;
	zif_handler orig_is_file;
	zif_handler orig_is_link;
	zif_handler orig_is_dir;
	zif_handler orig_opendir;
	zif_handler orig_file_exists;
	zif_handler orig_fileperms;
	zif_handler orig_fileinode;
	zif_handler orig_filesize;
	zif_handler orig_fileowner;
	zif_handler orig_filegroup;
	zif_handler orig_fileatime;
	zif_handler orig_filemtime;
	zif_handler orig_filectime;
	zif_handler orig_filetype;
	zif_handler orig_is_writable;
	zif_handler orig_is_readable;
	zif_handler orig_is_executable;
	zif_handler orig_lstat;
	zif_handler orig_readfile;
	zif_handler orig_stat;
	/* used for includes with . in them inside front controller */
	char*       cwd;
	uint32_t    cwd_len;
	bool        cwd_init;
	char        *openssl_privatekey;
	uint32_t    openssl_privatekey_len;
	/* phar_get_archive cache */
	const zend_string *last_phar_name;
	uint32_t    last_alias_len;
	const char* last_alias;
	phar_archive_data* last_phar;
	HashTable mime_types;
ZEND_END_MODULE_GLOBALS(phar)

ZEND_EXTERN_MODULE_GLOBALS(phar)
#define PHAR_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(phar, v)

#if defined(ZTS) && defined(COMPILE_DL_PHAR)
ZEND_TSRMLS_CACHE_EXTERN()
#endif

typedef union _phar_archive_object  phar_archive_object;
typedef union _phar_entry_object    phar_entry_object;

/*
 * used in phar_entry_info->fp_type to
 */
enum phar_fp_type {
	/* regular file pointer phar_archive_data->fp */
	PHAR_FP,
	/* uncompressed file pointer phar_archive_data->uncompressed_fp */
	PHAR_UFP,
	/* modified file pointer phar_entry_info->fp */
	PHAR_MOD,
	/* temporary manifest entry (file outside of the phar mapped to a location inside the phar)
	   this entry stores the stream to open in link (normally used for tars, but we steal it here) */
	PHAR_TMP
};

/*
 * Represents the metadata of the phar file or a file entry within the phar.
 * Can contain any combination of serialized data and the value as needed.
 */
typedef struct _phar_metadata_tracker {
	/* Can be IS_UNDEF or a regular value */
	zval val;
	/* Nullable string with the serialized value, if the serialization was performed or read from a file. */
	zend_string *str;
} phar_metadata_tracker;

/* entry for one file in a phar file */
typedef struct _phar_entry_info {
	/* first bytes are exactly as in file */
	uint32_t                 uncompressed_filesize;
	/* modification time */
	uint32_t                 timestamp;
	uint32_t                 compressed_filesize;
	uint32_t                 crc32;
	uint32_t                 flags;
	/* remainder */
	/* when changing compression, save old flags in case fp is NULL */
	uint32_t                 old_flags;
	phar_metadata_tracker metadata_tracker;
	zend_string              *filename;
	/* offset within original phar file of the file contents */
	zend_long                     offset_abs;
	/* offset within fp of the file contents */
	zend_long                     offset;
	/* offset within original phar file of the file header (for zip-based/tar-based) */
	zend_long                     header_offset;
	php_stream               *fp;
	php_stream               *cfp;
	enum phar_fp_type        fp_type;
	int                      fp_refcount;
	unsigned int             fileinfo_lock_count;
	char                     *tmp;
	phar_archive_data        *phar;
	zend_string              *symlink;
	char                     tar_type;
	/* position in the manifest */
	uint32_t                     manifest_pos;
	/* for stat */
	unsigned short           inode;

	bool             is_crc_checked:1;
	bool             is_modified:1;
	bool             is_deleted:1;
	bool             is_dir:1;
	/* this flag is used for mounted entries (external files mapped to location
	   inside a phar */
	bool             is_mounted:1;
	/* used when iterating */
	bool             is_temp_dir:1;
	/* tar-based phar file stuff */
	bool             is_tar:1;
	/* zip-based phar file stuff */
	bool             is_zip:1;
	/* for cached phar entries */
	bool             is_persistent:1;
} phar_entry_info;

/* information about a phar file (the archive itself) */
struct _phar_archive_data {
	zend_string             *fname;
	/* The ext field stores the location of the file extension from the fname field, and thus should never be freed. */
	uint32_t                 ext_len;
	const char               *ext;
	char                     *alias;
	uint32_t                 alias_len;
	char                     version[12];
	size_t                   halt_offset;
	HashTable                manifest;
	/* hash of virtual directories, as in path/to/file.txt has path/to and path as virtual directories */
	HashTable                virtual_dirs;
	/* hash of mounted directory paths */
	HashTable                mounted_dirs;
	uint32_t                 flags;
	uint32_t                 max_timestamp;
	int                      refcount;
	php_stream               *fp;
	/* decompressed file contents are stored here */
	php_stream               *ufp;
	uint32_t                 sig_flags;
	uint32_t                 sig_len;
	char                     *signature;
	phar_metadata_tracker metadata_tracker;
	uint32_t                 phar_pos;
	/* if 1, then this alias was manually specified by the user and is not a permanent alias */
	bool             is_temporary_alias:1;
	bool             is_modified:1;
	bool             is_writeable:1;
	bool             is_brandnew:1;
	/* defer phar creation */
	bool             donotflush:1;
	/* zip-based phar variables */
	bool             is_zip:1;
	/* tar-based phar variables */
	bool             is_tar:1;
	/* PharData variables       */
	bool             is_data:1;
	/* for cached phar manifests */
	bool             is_persistent:1;
};

typedef struct _phar_entry_fp_info {
	enum phar_fp_type        fp_type;
	/* offset within fp of the file contents */
	zend_long                     offset;
} phar_entry_fp_info;

struct _phar_entry_fp {
	php_stream *fp;
	php_stream *ufp;
	phar_entry_fp_info *manifest;
};

static inline php_stream *phar_get_entrypfp(const phar_entry_info *entry)
{
	if (!entry->is_persistent) {
		return entry->phar->fp;
	}
	return PHAR_G(cached_fp)[entry->phar->phar_pos].fp;
}

static inline php_stream *phar_get_pharfp(const phar_archive_data *phar)
{
	if (!phar->is_persistent) {
		return phar->fp;
	}
	return PHAR_G(cached_fp)[phar->phar_pos].fp;
}

static inline enum phar_fp_type phar_get_fp_type(const phar_entry_info *entry)
{
	if (!entry->is_persistent) {
		return entry->fp_type;
	}
	return PHAR_G(cached_fp)[entry->phar->phar_pos].manifest[entry->manifest_pos].fp_type;
}

#define PHAR_MIME_PHP '\0'
#define PHAR_MIME_PHPS '\1'
#define PHAR_MIME_OTHER '\2'

typedef struct _phar_mime_type {
	char *mime;
	uint32_t len;
	/* one of PHAR_MIME_* */
	char type;
} phar_mime_type;

/* stream access data for one file entry in a phar file */
typedef struct _phar_entry_data {
	phar_archive_data        *phar;
	php_stream               *fp;
	/* stream position proxy, allows multiple open streams referring to the same fp */
	zend_off_t                    position;
	/* for copies of the phar fp, defines where 0 is */
	zend_off_t                    zero;
	phar_entry_info          *internal_file;
} phar_entry_data;

/* archive php object */
union _phar_archive_object {
	spl_filesystem_object    spl;
	phar_archive_data        *archive;
};

/* entry php object */
union _phar_entry_object {
	spl_filesystem_object    spl;
	phar_entry_info          *entry;
};

BEGIN_EXTERN_C()

#ifdef PHP_WIN32
static inline void phar_unixify_path_separators(char *path, size_t path_len)
{
	char *s;

	/* unixify win paths */
	for (s = path; (size_t)(s - path) < path_len; ++s) {
		if (*s == '\\') {
			*s = '/';
		}
	}
}
#endif

static inline bool phar_validate_alias(const char *alias, size_t alias_len) /* {{{ */
{
	return !(memchr(alias, '/', alias_len) || memchr(alias, '\\', alias_len) || memchr(alias, ':', alias_len) ||
		memchr(alias, ';', alias_len) || memchr(alias, '\n', alias_len) || memchr(alias, '\r', alias_len));
}
/* }}} */

static inline bool phar_path_is_magic_phar_ex(const char *path, size_t path_len) /* {{{ */
{
	if (path_len > 0 && path[0] == '/') {
		path++;
		path_len--;
	}

	if (path_len < sizeof(".phar") - 1 || memcmp(path, ".phar", sizeof(".phar") - 1) != 0) {
		return false;
	}

	if (path_len == sizeof(".phar") - 1) {
		return true;
	}

	return path[sizeof(".phar") - 1] == '/' || path[sizeof(".phar") - 1] == '\\';
}
/* }}} */

static inline bool phar_is_magic_phar(const zend_string *path) /* {{{ */
{
	return phar_path_is_magic_phar_ex(ZSTR_VAL(path), ZSTR_LEN(path));
}
/* }}} */

static inline void phar_set_inode(phar_entry_info *entry) /* {{{ */
{
	if (entry->phar->fname) {
		char tmp[MAXPATHLEN];
		size_t tmp_len = MIN(MAXPATHLEN, ZSTR_LEN(entry->filename) + ZSTR_LEN(entry->phar->fname));

		size_t len1 = MIN(ZSTR_LEN(entry->phar->fname), tmp_len);
		memcpy(tmp, ZSTR_VAL(entry->phar->fname), len1);

		size_t len2 = MIN(tmp_len - len1, ZSTR_LEN(entry->filename));
		memcpy(tmp + len1, entry->filename, len2);

		entry->inode = (unsigned short) zend_hash_func(tmp, tmp_len);
	} else {
		entry->inode = (unsigned short) zend_string_hash_val(entry->filename);
	}
}
/* }}} */

static inline bool phar_entry_can_remove(const phar_entry_info *entry)
{
	return entry->fp_refcount == 0 && entry->fileinfo_lock_count == 0;
}

void phar_request_initialize(void);

void phar_object_init(void);
void phar_destroy_phar_data(phar_archive_data *phar);

ZEND_ATTRIBUTE_NONNULL zend_result phar_postprocess_file(phar_entry_data *idata, uint32_t crc32, char **error, int process_zip);
zend_result phar_open_from_filename(char *fname, size_t fname_len, /* copyable & hash update */ zend_string *alias, uint32_t options, phar_archive_data** pphar, char **error);
ZEND_ATTRIBUTE_NONNULL_ARGS(1, 5, 6) zend_result phar_open_or_create_filename(zend_string *fname, /* copyable & hash update */ zend_string *alias, bool is_data, uint32_t options, phar_archive_data** pphar, char **error);
ZEND_ATTRIBUTE_NONNULL_ARGS(1, 5, 6) zend_result phar_create_or_parse_filename(zend_string *fname, /* copyable & hash update */ zend_string *alias, bool is_data, uint32_t options, phar_archive_data** pphar, char **error);
ZEND_ATTRIBUTE_NONNULL_ARGS(2) zend_result phar_open_executed_filename(/* copyable & hash update */ zend_string *alias, char **error);
zend_result phar_free_alias(const phar_archive_data *phar);
phar_archive_data* phar_get_archive(const char *fname, size_t fname_len, const char *alias, size_t alias_len, char **error);
zend_result phar_verify_signature(php_stream *fp, size_t end_of_phar, uint32_t sig_type, const char *sig, size_t sig_len, const char *fname, char **signature, size_t *signature_len, char **error);
ZEND_ATTRIBUTE_NONNULL zend_string* phar_create_signature(phar_archive_data *phar, php_stream *fp, char **error);

/* utility functions */
zend_string *phar_create_default_stub(const zend_string *php_index_str, const zend_string *web_index_str, char **error);
const char *phar_get_decompress_filter_name(const phar_entry_info *entry);
const char *phar_get_compress_filter_name(const phar_entry_info *entry);

/* void phar_remove_virtual_dirs(phar_archive_data *phar, char *filename, size_t filename_len); */
void phar_add_virtual_dirs(phar_archive_data *phar, const char *filename, size_t filename_len);
zend_result phar_mount_entry(phar_archive_data *phar, const char *filename, size_t filename_len, char *path, size_t path_len);
	zend_string *phar_find_in_include_path(const zend_string *file);
zend_string* phar_fix_filepath(const char *path, size_t path_length, bool use_cwd);
ZEND_ATTRIBUTE_NONNULL phar_entry_info * phar_open_jit(const phar_archive_data *phar, phar_entry_info *entry, char **error);
void phar_parse_metadata_lazy(const char *buffer, phar_metadata_tracker *tracker, uint32_t zip_metadata_len, bool persistent);
bool phar_metadata_tracker_has_data(const phar_metadata_tracker* tracker, bool persistent);
/* If this has data, free it and set all values to undefined. */
void phar_metadata_tracker_free(phar_metadata_tracker* val, bool persistent);
void phar_metadata_tracker_copy(phar_metadata_tracker* dest, const phar_metadata_tracker *source, bool persistent);
void phar_metadata_tracker_clone(phar_metadata_tracker* tracker);
void phar_metadata_tracker_try_ensure_has_serialized_data(phar_metadata_tracker* tracker, bool persistent);
zend_result phar_metadata_tracker_unserialize_or_copy(phar_metadata_tracker* tracker, zval *value, bool persistent, HashTable *unserialize_options, const char* method_name);
void destroy_phar_manifest_entry(zval *zv);
int phar_seek_efp(phar_entry_info *entry, zend_off_t offset, int whence, zend_off_t position, bool follow_links);
php_stream *phar_get_efp(phar_entry_info *entry, bool follow_links);
ZEND_ATTRIBUTE_NONNULL zend_result phar_copy_entry_fp(phar_entry_info *source, phar_entry_info *dest, char **error);
ZEND_ATTRIBUTE_NONNULL zend_result phar_open_entry_fp(phar_entry_info *entry, char **error, bool follow_links);
phar_entry_info *phar_get_link_source(phar_entry_info *entry);
php_stream *phar_open_archive_fp(phar_archive_data *phar);
zend_result phar_copy_on_write(phar_archive_data **pphar);

/* tar functions in tar.c */
bool phar_is_tar(const char *buf, const char *fname);
zend_result phar_parse_tarfile(php_stream* fp, const char *fname, size_t fname_len, /* copyable & hash update */ zend_string *alias, phar_archive_data** pphar, uint32_t compression, char **error);
ZEND_ATTRIBUTE_NONNULL_ARGS(1, 5, 6) zend_result phar_open_or_create_tar(zend_string *fname, /* copyable & hash update */ zend_string *alias, bool is_data, uint32_t options, phar_archive_data** pphar, char **error);
ZEND_ATTRIBUTE_NONNULL_ARGS(1, 4) int phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_default_stub, char **error);

/* zip functions in zip.c */
zend_result phar_parse_zipfile(php_stream *fp, const char *fname, size_t fname_len, /* copyable & hash update */ zend_string *alias, phar_archive_data** pphar, char **error);
ZEND_ATTRIBUTE_NONNULL_ARGS(1, 5, 6) zend_result phar_open_or_create_zip(zend_string *fname, /* copyable & hash update */ zend_string *alias, bool is_data, uint32_t options, phar_archive_data** pphar, char **error);
ZEND_ATTRIBUTE_NONNULL_ARGS(1, 4) int phar_zip_flush(phar_archive_data *archive, zend_string *user_stub, bool is_default_stub, char **error);

#ifdef PHAR_MAIN
extern const php_stream_wrapper php_stream_phar_wrapper;
#else
extern HashTable cached_phars;
extern HashTable cached_alias;
#endif

bool phar_archive_delref(phar_archive_data *phar);
void phar_entry_delref(phar_entry_data *idata);

phar_entry_info *phar_get_entry_info(phar_archive_data *phar, char *path, size_t path_len, char **error, bool security);
phar_entry_info *phar_get_entry_info_dir(phar_archive_data *phar, char *path, size_t path_len, char dir, char **error, bool security);
ZEND_ATTRIBUTE_NONNULL phar_entry_data *phar_get_or_create_entry_data(zend_string *fname, char *path, size_t path_len, const char *mode, char allow_dir, char **error, bool security, uint32_t timestamp);
ZEND_ATTRIBUTE_NONNULL zend_result phar_get_entry_data(phar_entry_data **ret, const zend_string *fname, char *path, size_t path_len, const char *mode, char allow_dir, char **error, bool security);
ZEND_ATTRIBUTE_NONNULL_ARGS(1, 4) int phar_flush_ex(phar_archive_data *archive, zend_string *user_stub, bool is_default_stub, char **error);
ZEND_ATTRIBUTE_NONNULL int phar_flush(phar_archive_data *archive, char **error);
zend_result phar_detect_phar_fname_ext(const char *filename, size_t filename_len, const char **ext_str, size_t *ext_len, int executable, int for_create, bool is_complete);
zend_string* phar_split_fname_ex(const char *filename, size_t filename_len, zend_string **entry, int executable, int for_create, const char **error);
zend_string* phar_split_fname(const char *filename, size_t filename_len, zend_string **entry, int executable, int for_create);

typedef enum {
	pcr_use_query,
	pcr_is_ok,
	pcr_err_double_slash,
	pcr_err_up_dir,
	pcr_err_curr_dir,
	pcr_err_back_slash,
	pcr_err_star,
	pcr_err_illegal_char,
	pcr_err_empty_entry
} phar_path_check_result;

ZEND_ATTRIBUTE_NONNULL phar_path_check_result phar_path_check(char **p, size_t *len, const char **error);

END_EXTERN_C()
