mirror of
https://github.com/php-win-ext/php-rar.git
synced 2026-03-24 04:52:07 +01:00
Added extflow.txt.
Added technote.txt. Added rar_file_t::entries_idx. It's a hashtable that stores the entries in rar_file_t::entries indexed by name. _rar_raw_entries_to_files uses it, if available. Added an extension global, a per-request cache that will be used when directory streams are implemented. Eliminated dependency on SPL. Substituted several legacy macro names for new ones. Stream close operation operates differently when close_handle is FALSE (not that I actually know what the correct behaviour would be...) Added rar stream stat operation. Added wrapper, not with only an opener. The syntax is "rar://<urlencoded path to RAR archive>#<urlencoded entry name>". Context options should be under "rar" and are "open_password", "file_password" and "volume_callback" extract() and the wrapper opener should support RAR files with header passwords different from file passwords (but WinRAR does not generate them, so not tested). Avoid test 46 infinite loop on resource opening failure. git-svn-id: http://svn.php.net/repository/pecl/rar/trunk@298704 c90b9560-bf6c-de11-be94-00142212c4b1
This commit is contained in:
60
extflow.txt
Normal file
60
extflow.txt
Normal file
@@ -0,0 +1,60 @@
|
||||
rar_open/RarArchive::open()
|
||||
gives
|
||||
RarArchive object
|
||||
-
|
||||
. stores 2 open data structs (are used to tell the lib e.g. which file to open and the lib in return stores some data in them)
|
||||
- list_open_data has open mode RAR_OM_LIST_INCSPLIT and is used to list the contents of the archive
|
||||
- extract_open_data has open mode RAR_OM_EXTRACT and is used by RarEntry::extract
|
||||
. stores one opened archive handle, opened with the list_open_data struct. This handle remains
|
||||
open until the archive is closed or the object is destroyed
|
||||
. a RarArchive object is considered closed when the opened archive handle created here is set to NULL
|
||||
|
||||
|
||||
rar_list()/RarArchive::getEntries()
|
||||
gives
|
||||
RarEntry objects
|
||||
-
|
||||
. CALL _rar_list_files, which fills the lazy cache rar->entries by using the opened archive handle to retrieve ALL the RarHeaderDataEx headers
|
||||
. CALL _rar_raw_entries_to_files to turn the rar->entries RarHeaderDataEx headers into zvals
|
||||
- in turn, _rar_raw_entries_to_files creates the zval and sets the property that holds the zval reference to the RarArchive object (see below)
|
||||
- calculates the packed size by summing over all the headers that concern each file (a file may have more than one header in case there are volumes)
|
||||
- then CALLs _rar_entry_to_zval with the last header for each file and the packed size so that it can fill the remaining properties
|
||||
. each of the RarEntry objects store a zval referencing the RarArchive object. The RarArchive object is not destroyed until all its spawned RarEntry objects are destroyed (it can however be closed)
|
||||
|
||||
|
||||
rar_entry_get()/RarArchive::getEntry()
|
||||
gives
|
||||
RarEntry object
|
||||
-
|
||||
. CALL _rar_list_files, if it's necessary to fill the lazy cache rar->entries
|
||||
. CALL _rar_raw_entries_to_files, which traverses rar->entries until it finds the request filename, the header(s) are then converted into one zval
|
||||
. again, the RarEntry object stores a reference to the RarArchive object
|
||||
|
||||
|
||||
RarArchive traversal with an iterator
|
||||
gives
|
||||
RarEntry objects (one at a time)
|
||||
-
|
||||
. iterator creation CALLs_rar_list_files, if it's necessary to fill the lazy cache rar->entries
|
||||
. iterator stores the index (with respect to the rar->entries array) of the last header in rar->entries that is to be read (starts with 0)
|
||||
. iterator CALLs _rar_raw_entries_to_files, which here stops after reading each file and advances the index
|
||||
|
||||
|
||||
RarEntry::extract()
|
||||
extracts the file
|
||||
-
|
||||
. uses the extract_open_data that's stored in the parent RarArchive object
|
||||
. makes a shallow copy of parent RarArchive's rar->cb_userdata, eventually modified with the given file password.
|
||||
. passes them to _rar_find_file to open the file with RAR_OM_EXTRACT and skip to the desired entry
|
||||
. extracts the file
|
||||
. closes the rar handle
|
||||
|
||||
|
||||
RarEntry::getStream()
|
||||
obtains stream
|
||||
-
|
||||
. CALL php_stream_rar_open with the archive name (obtained from parent RarArchive object's extract_open_data->ArcName), the filename of the entry and a shallow copy of parent RarArchive's rar->cb_userdata, eventually modified with the given file password.
|
||||
. in turn, php_stream_rar_open CALLs _rar_find_file with a brand new rar open data struct with RAR_OM_EXTRACT. _rar_find_file opens the RAR archive and skips to the desired entry
|
||||
. the resulting stream has no connection to the original RarArchive object or to the RarEntry object
|
||||
. the rar archive is not closed until the stream is destroyed or closed
|
||||
|
||||
54
php_rar.h
54
php_rar.h
@@ -74,6 +74,8 @@ typedef struct rar {
|
||||
zend_object_handle id;
|
||||
int entry_count; //>= number of files
|
||||
struct RARHeaderDataEx **entries;
|
||||
//key: entry name, value: index in entries
|
||||
HashTable *entries_idx; /* TODO: merge into entries */
|
||||
struct RAROpenArchiveDataEx *list_open_data;
|
||||
struct RAROpenArchiveDataEx *extract_open_data;
|
||||
//archive handle opened with RAR_OM_LIST_INCSPLIT open mode
|
||||
@@ -82,8 +84,52 @@ typedef struct rar {
|
||||
rar_cb_user_data cb_userdata;
|
||||
} rar_file_t;
|
||||
|
||||
/* Per-request cache or make last the duration of the PHP lifespan?
|
||||
* - per-request advantages: we can re-use rar_open and store close RarArchive
|
||||
* objects. We store either pointers to the objects directly and manipulate
|
||||
* the refcount in the store or we store zvals. Either way, we must decrement
|
||||
* the refcounts on request shutdown. Also, the memory usage is best kept
|
||||
* in check because the memory is freed after each request.
|
||||
* - per PHP lifespan advantages: more cache hits. We can also re-use rar_open,
|
||||
* but then we have to copy rar->entries and rar->entries_idx into
|
||||
* persistently allocated buffers since the RarArchive objects cannot be made
|
||||
* persistent themselves.
|
||||
*
|
||||
* I'll go with per-request and store zval pointers together with modification
|
||||
* time.
|
||||
* I'll also go with a FIFO eviction policy because it's simpler to implement
|
||||
* (just delete the first element of the HashTable).
|
||||
*/
|
||||
#ifdef ZTS
|
||||
# define RAR_TSRMLS_TC , void ***
|
||||
#else
|
||||
# define RAR_TSRMLS_TC
|
||||
#endif
|
||||
|
||||
typedef struct _rar_contents_cache {
|
||||
int max_size;
|
||||
HashTable *data; //persistent HashTable, will hold rar_cache_entry
|
||||
/* args: cache key, cache key size, cached object) */
|
||||
void (*put)(const char *, uint, zval * RAR_TSRMLS_TC);
|
||||
zval *(*get)(const char *, uint RAR_TSRMLS_TC);
|
||||
} rar_contents_cache;
|
||||
|
||||
/* Module globals, currently used for dir wrappers cache */
|
||||
ZEND_BEGIN_MODULE_GLOBALS(rar)
|
||||
rar_contents_cache contents_cache;
|
||||
ZEND_END_MODULE_GLOBALS(rar)
|
||||
|
||||
ZEND_EXTERN_MODULE_GLOBALS(rar);
|
||||
|
||||
#ifdef ZTS
|
||||
# define RAR_G(v) TSRMG(rar_globals_id, zend_rar_globals *, v)
|
||||
#else
|
||||
# define RAR_G(v) (rar_globals.v)
|
||||
#endif
|
||||
|
||||
//PHP 5.2 compatibility
|
||||
#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 3
|
||||
# define STREAM_ASSUME_REALPATH 0
|
||||
# define ALLOC_PERMANENT_ZVAL(z) \
|
||||
(z) = (zval*) malloc(sizeof(zval));
|
||||
# define OPENBASEDIR_CHECKPATH(filename) \
|
||||
@@ -130,6 +176,7 @@ const char * _rar_error_to_string(int errcode);
|
||||
void minit_rarerror(TSRMLS_D);
|
||||
|
||||
/* rararch.c */
|
||||
int _rar_index_entries(rar_file_t *rar_file TSRMLS_DC);
|
||||
int _rar_get_file_resource(zval *zval_file, rar_file_t **rar_file TSRMLS_DC);
|
||||
int _rar_get_file_resource_ex(zval *zval_file, rar_file_t **rar_file, int silent TSRMLS_DC);
|
||||
void minit_rararch(TSRMLS_D);
|
||||
@@ -152,6 +199,13 @@ php_stream *php_stream_rar_open(char *arc_name,
|
||||
char *utf_file_name,
|
||||
rar_cb_user_data *cb_udata_ptr, /* will be copied */
|
||||
char *mode STREAMS_DC TSRMLS_DC);
|
||||
php_stream *php_stream_rar_opener(php_stream_wrapper *wrapper,
|
||||
char *filename,
|
||||
char *mode,
|
||||
int options,
|
||||
char **opened_path,
|
||||
php_stream_context *context STREAMS_DC TSRMLS_DC);
|
||||
extern php_stream_wrapper php_stream_rar_wrapper;
|
||||
|
||||
#endif /* PHP_RAR_H */
|
||||
|
||||
|
||||
134
rar.c
134
rar.c
@@ -201,6 +201,17 @@ void _rar_destroy_userdata(rar_cb_user_data *udata) /* {{{ */
|
||||
|
||||
/* WARNING: It's the caller who must close the archive and manage the lifecycle
|
||||
of cb_udata (must be valid while the archive is opened). */
|
||||
/*
|
||||
* This function opens a RAR file and looks for the file with the
|
||||
* name utf_file_name.
|
||||
* If the operation is sucessful, arc_handle is populated with the RAR file
|
||||
* handle, found is set to TRUE if the file is found and FALSE if it is not
|
||||
* found; additionaly, the optional header_data is populated with the first
|
||||
* header that corresponds to the request file. If the file is not found and
|
||||
* header_data is specified, its values are undefined.
|
||||
* Note that even when the file is not found, the caller must still close
|
||||
* the archive.
|
||||
*/
|
||||
int _rar_find_file(struct RAROpenArchiveDataEx *open_data, /* IN */
|
||||
const char *const utf_file_name, /* IN */
|
||||
rar_cb_user_data *cb_udata, /* IN, must be managed outside */
|
||||
@@ -269,7 +280,11 @@ cleanup:
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* Only processes password callbacks */
|
||||
/* An unRAR callback.
|
||||
* Processes requests for passwords and missing volumes
|
||||
* If there is (userland) volume find callback specified, try to use that
|
||||
* callback to retrieve the name of the missing volume. Otherwise, or if
|
||||
* the volume find callback returns null, cancel the operation. */
|
||||
int CALLBACK _rar_unrar_callback(UINT msg, LPARAM UserData, LPARAM P1, LPARAM P2) /* {{{ */
|
||||
{
|
||||
TSRMLS_FETCH();
|
||||
@@ -327,7 +342,7 @@ PHP_FUNCTION(rar_bogus_ctor) /* {{{ */
|
||||
/* This exception should not be thrown. The point is to add this as
|
||||
* a class constructor and make it private. This code would be able to
|
||||
* run only if the constructor were made public */
|
||||
zend_throw_exception(spl_ce_RuntimeException,
|
||||
zend_throw_exception(NULL,
|
||||
"An object of this type cannot be created with the new operator.",
|
||||
0 TSRMLS_CC);
|
||||
}
|
||||
@@ -335,6 +350,11 @@ PHP_FUNCTION(rar_bogus_ctor) /* {{{ */
|
||||
/* }}} */
|
||||
|
||||
/* {{{ Functions with internal linkage */
|
||||
/*
|
||||
* Only relevant when sizeof(wchar_t) > 2 (so not windows).
|
||||
* Removes the characters use value if > 0x10ffff; these are not
|
||||
* valid UTF characters.
|
||||
*/
|
||||
static void _rar_fix_wide(wchar_t *str, size_t max_size) /* {{{ */
|
||||
{
|
||||
wchar_t *write,
|
||||
@@ -352,7 +372,7 @@ static void _rar_fix_wide(wchar_t *str, size_t max_size) /* {{{ */
|
||||
/* called from the RAR callback; calls a user callback in case a volume was
|
||||
* not found
|
||||
* This function sends messages instead of calling _rar_handle_ext_error
|
||||
* because, in case we're using exception, we want to let an exception with
|
||||
* because, in case we're using exceptions, we want to let an exception with
|
||||
* error code ERAR_EOPEN to be thrown.
|
||||
*/
|
||||
static int _rar_unrar_volume_user_callback(char* dst_buffer,
|
||||
@@ -371,7 +391,7 @@ static int _rar_unrar_volume_user_callback(char* dst_buffer,
|
||||
fci->retval_ptr_ptr = &retval_ptr;
|
||||
fci->params = ¶ms;
|
||||
fci->param_count = 1;
|
||||
|
||||
|
||||
if (zend_call_function(fci, cache TSRMLS_CC) != SUCCESS ||
|
||||
fci->retval_ptr_ptr == NULL ||
|
||||
*fci->retval_ptr_ptr == NULL) {
|
||||
@@ -428,9 +448,7 @@ cleanup:
|
||||
/* }}} */
|
||||
|
||||
#ifdef COMPILE_DL_RAR
|
||||
BEGIN_EXTERN_C()
|
||||
ZEND_GET_MODULE(rar)
|
||||
END_EXTERN_C()
|
||||
#endif
|
||||
|
||||
/* {{{ arginfo */
|
||||
@@ -472,13 +490,82 @@ static zend_function_entry rar_functions[] = {
|
||||
};
|
||||
/* }}} */
|
||||
|
||||
/* {{{ PHP_MINIT_FUNCTION
|
||||
*/
|
||||
PHP_MINIT_FUNCTION(rar)
|
||||
/* {{{ Globals' related activities */
|
||||
/* actually, this is a tentative definition, since there's no initializer,
|
||||
* but it will in fact become a definition */
|
||||
ZEND_DECLARE_MODULE_GLOBALS(rar);
|
||||
|
||||
static int _rar_array_apply_remove_first(void *pDest TSRMLS_DC)
|
||||
{
|
||||
return (ZEND_HASH_APPLY_STOP | ZEND_HASH_APPLY_REMOVE);
|
||||
}
|
||||
|
||||
static void _rar_contents_cache_put(const char *key,
|
||||
uint key_len,
|
||||
zval *zv TSRMLS_DC)
|
||||
{
|
||||
rar_contents_cache *cc = &RAR_G(contents_cache);
|
||||
int cur_size;
|
||||
|
||||
cur_size = zend_hash_num_elements(cc->data);
|
||||
if (cur_size == cc->max_size) {
|
||||
zend_hash_apply(cc->data, _rar_array_apply_remove_first TSRMLS_CC);
|
||||
assert(zend_hash_num_elements(cc->data) == cur_size - 1);
|
||||
}
|
||||
zend_hash_update(cc->data, key, key_len, &zv, sizeof(zv), NULL);
|
||||
}
|
||||
|
||||
static zval *_rar_contents_cache_get(const char *key,
|
||||
uint key_len TSRMLS_DC)
|
||||
{
|
||||
rar_contents_cache *cc = &RAR_G(contents_cache);
|
||||
zval **element;
|
||||
zend_hash_find(cc->data, key, key_len, (void **) &element);
|
||||
|
||||
return *element;
|
||||
}
|
||||
|
||||
/* ZEND_MODULE_GLOBALS_CTOR_D declares it receiving zend_rar_globals*,
|
||||
* which is incompatible; once cast into ts_allocate_ctor by the macro,
|
||||
* ZEND_INIT_MODULE_GLOBALS, it cannot (per the spec) be used. */
|
||||
static void ZEND_MODULE_GLOBALS_CTOR_N(rar)(void *arg TSRMLS_DC) /* {{{ */
|
||||
{
|
||||
zend_rar_globals *rar_globals = arg;
|
||||
rar_globals->contents_cache.max_size = 5; /* TODO make configurable */
|
||||
rar_globals->contents_cache.put = _rar_contents_cache_put;
|
||||
rar_globals->contents_cache.get = _rar_contents_cache_get;
|
||||
rar_globals->contents_cache.data =
|
||||
pemalloc(sizeof *rar_globals->contents_cache.data, 1);
|
||||
zend_hash_init(rar_globals->contents_cache.data,
|
||||
rar_globals->contents_cache.max_size, NULL,
|
||||
ZVAL_PTR_DTOR, 1);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static void ZEND_MODULE_GLOBALS_DTOR_N(rar)(void *arg TSRMLS_DC) /* {{{ */
|
||||
{
|
||||
zend_rar_globals *rar_globals = arg;
|
||||
zend_hash_destroy(rar_globals->contents_cache.data);
|
||||
pefree(rar_globals->contents_cache.data, 1);
|
||||
}
|
||||
/* }}} */
|
||||
/* }}} */
|
||||
|
||||
/* {{{ ZEND_MODULE_STARTUP */
|
||||
ZEND_MODULE_STARTUP_D(rar)
|
||||
{
|
||||
minit_rararch(TSRMLS_C);
|
||||
minit_rarentry(TSRMLS_C);
|
||||
minit_rarerror(TSRMLS_C);
|
||||
|
||||
/* This doesn't work, it tries to call the destructor after the
|
||||
* module has been unloaded. This information is in the zend_module_entry
|
||||
* instead; that information is correctly used before the module is
|
||||
* unloaded */
|
||||
/* ZEND_INIT_MODULE_GLOBALS(rar, ZEND_MODULE_GLOBALS_CTOR_N(rar),
|
||||
ZEND_MODULE_GLOBALS_DTOR_N(rar)); */
|
||||
|
||||
php_register_url_stream_wrapper("rar", &php_stream_rar_wrapper TSRMLS_CC);
|
||||
|
||||
REGISTER_LONG_CONSTANT("RAR_HOST_MSDOS", HOST_MSDOS, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("RAR_HOST_OS2", HOST_OS2, CONST_CS | CONST_PERSISTENT);
|
||||
@@ -494,9 +581,18 @@ PHP_MINIT_FUNCTION(rar)
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ PHP_MINFO_FUNCTION
|
||||
*/
|
||||
PHP_MINFO_FUNCTION(rar)
|
||||
/* {{{ ZEND_MODULE_DEACTIVATE */
|
||||
ZEND_MODULE_DEACTIVATE_D(rar)
|
||||
{
|
||||
/* clean cache on request shutdown */
|
||||
zend_hash_clean(RAR_G(contents_cache).data);
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ ZEND_MODULE_INFO */
|
||||
ZEND_MODULE_INFO_D(rar)
|
||||
{
|
||||
char version[256];
|
||||
|
||||
@@ -525,13 +621,19 @@ zend_module_entry rar_module_entry = {
|
||||
STANDARD_MODULE_HEADER,
|
||||
"rar",
|
||||
rar_functions,
|
||||
PHP_MINIT(rar),
|
||||
ZEND_MODULE_STARTUP_N(rar),
|
||||
//ZEND_MODULE_SHUTDOWN_N(rar),
|
||||
NULL,
|
||||
//ZEND_MODULE_ACTIVATE_N(rar),
|
||||
NULL,
|
||||
NULL,
|
||||
PHP_MINFO(rar),
|
||||
ZEND_MODULE_DEACTIVATE_N(rar),
|
||||
ZEND_MODULE_INFO_N(rar),
|
||||
PHP_RAR_VERSION,
|
||||
STANDARD_MODULE_PROPERTIES
|
||||
ZEND_MODULE_GLOBALS(rar),
|
||||
ZEND_MODULE_GLOBALS_CTOR_N(rar),
|
||||
ZEND_MODULE_GLOBALS_DTOR_N(rar),
|
||||
NULL, //post_deactivate_func
|
||||
STANDARD_MODULE_PROPERTIES_EX,
|
||||
};
|
||||
/* }}} */
|
||||
|
||||
|
||||
461
rar_stream.c
461
rar_stream.c
@@ -37,7 +37,6 @@ extern "C" {
|
||||
#include "php.h"
|
||||
|
||||
#if HAVE_RAR
|
||||
#ifdef ZEND_ENGINE_2
|
||||
|
||||
#include <wchar.h>
|
||||
|
||||
@@ -45,20 +44,17 @@ extern "C" {
|
||||
#include "unrar/rartypes.hpp"
|
||||
|
||||
#include "php_streams.h"
|
||||
/* will be needed to implement a wrapper
|
||||
* #include "ext/standard/file.h"
|
||||
* #include "ext/standard/php_string.h"
|
||||
* #include "fopen_wrappers.h"
|
||||
* #include "ext/standard/url.h"
|
||||
*/
|
||||
#include "ext/standard/url.h"
|
||||
#include "ext/standard/php_string.h"
|
||||
|
||||
typedef struct php_rar_stream_data_t {
|
||||
struct RAROpenArchiveDataEx open_data;
|
||||
struct RARHeaderDataEx header_data;
|
||||
HANDLE rar_handle;
|
||||
/* TODO: consider encapsulating a php memory/tmpfile stream */
|
||||
unsigned char *buffer;
|
||||
size_t buffer_size;
|
||||
size_t buffer_cont_size;
|
||||
size_t buffer_cont_size; /* content size */
|
||||
size_t buffer_pos;
|
||||
uint64 cursor;
|
||||
int no_more_data;
|
||||
@@ -156,23 +152,23 @@ static int php_rar_ops_close(php_stream *stream, int close_handle TSRMLS_DC)
|
||||
{
|
||||
STREAM_DATA_FROM_STREAM
|
||||
|
||||
if (close_handle) {
|
||||
if (self->open_data.ArcName != NULL) {
|
||||
efree(self->open_data.ArcName);
|
||||
self->open_data.ArcName = NULL;
|
||||
}
|
||||
_rar_destroy_userdata(&self->cb_userdata);
|
||||
if (self->buffer != NULL) {
|
||||
efree(self->buffer);
|
||||
self->buffer = NULL;
|
||||
}
|
||||
if (self->rar_handle != NULL) {
|
||||
if (self->open_data.ArcName != NULL) {
|
||||
efree(self->open_data.ArcName);
|
||||
self->open_data.ArcName = NULL;
|
||||
}
|
||||
_rar_destroy_userdata(&self->cb_userdata);
|
||||
if (self->buffer != NULL) {
|
||||
efree(self->buffer);
|
||||
self->buffer = NULL;
|
||||
}
|
||||
if (self->rar_handle != NULL) {
|
||||
if (close_handle) {
|
||||
int res = RARCloseArchive(self->rar_handle);
|
||||
if (_rar_handle_error(res TSRMLS_CC) == FAILURE) {
|
||||
; //not much we can do...
|
||||
}
|
||||
self->rar_handle = NULL;
|
||||
}
|
||||
self->rar_handle = NULL;
|
||||
}
|
||||
efree(self);
|
||||
stream->abstract = NULL;
|
||||
@@ -187,18 +183,92 @@ static int php_rar_ops_flush(php_stream *stream TSRMLS_DC)
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
php_stream_ops php_stream_rario_ops = {
|
||||
/* {{{ php_rar_ops_flush */
|
||||
/* Fill ssb, return non-zero value on failure */
|
||||
static int php_rar_ops_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC)
|
||||
{
|
||||
STREAM_DATA_FROM_STREAM
|
||||
|
||||
uint64 unp_size = INT32TO64(self->header_data.UnpSizeHigh,
|
||||
self->header_data.UnpSize);
|
||||
|
||||
ssb->sb.st_dev = 0;
|
||||
ssb->sb.st_ino = 0;
|
||||
ssb->sb.st_mode = (self->header_data.FileAttr & 0xffff);
|
||||
ssb->sb.st_nlink = 1;
|
||||
/* RAR stores owner/group information (header type NEWSUB_HEAD and subtype
|
||||
* SUBHEAD_TYPE_UOWNER), but it is not exposed in unRAR */
|
||||
ssb->sb.st_uid = 0;
|
||||
ssb->sb.st_gid = 0;
|
||||
#ifdef HAVE_ST_RDEV
|
||||
ssb->sb.st_rdev = 0;
|
||||
#endif
|
||||
/* never mind signedness, we'll never get sizes big enough for that to
|
||||
* matter */
|
||||
if (sizeof(ssb->sb.st_size) == sizeof(unp_size))
|
||||
ssb->sb.st_size = (int64) unp_size;
|
||||
else {
|
||||
assert(sizeof(ssb->sb.st_size) == sizeof(long));
|
||||
if (unp_size > ((uint64) MAXLONG))
|
||||
ssb->sb.st_size = MAXLONG;
|
||||
else
|
||||
ssb->sb.st_size = (long) unp_size;
|
||||
}
|
||||
|
||||
/* Creation/access time are also available in (recent) versions of RAR,
|
||||
* but unexposed */
|
||||
{
|
||||
struct tm time_s = {0};
|
||||
time_t time;
|
||||
unsigned dos_time = self->header_data.FileTime;
|
||||
|
||||
time_s.tm_mday = 1; //this one starts on 1, not 0
|
||||
time_s.tm_year = 70; /* default to 1970-01-01 00:00 */
|
||||
if ((time = mktime(&time_s)) == -1)
|
||||
return FAILURE;
|
||||
|
||||
ssb->sb.st_atime = time;
|
||||
ssb->sb.st_ctime = time;
|
||||
|
||||
time_s.tm_sec = (dos_time & 0x1f)*2;
|
||||
time_s.tm_min = (dos_time>>5) & 0x3f;
|
||||
time_s.tm_hour = (dos_time>>11) & 0x1f;
|
||||
time_s.tm_mday = (dos_time>>16) & 0x1f;
|
||||
time_s.tm_mon = ((dos_time>>21) & 0x0f) - 1;
|
||||
time_s.tm_year = (dos_time>>25) + 80;
|
||||
if ((time = mktime(&time_s)) == -1)
|
||||
return FAILURE;
|
||||
ssb->sb.st_mtime = time;
|
||||
}
|
||||
|
||||
#ifdef HAVE_ST_BLKSIZE
|
||||
ssb->sb.st_blksize = 0;
|
||||
#endif
|
||||
#ifdef HAVE_ST_BLOCKS
|
||||
ssb->sb.st_blocks = 0;
|
||||
#endif
|
||||
/* php_stat in filestat.c doesn't check this one, so don't touch it */
|
||||
//ssb->sb.st_attr = ;
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static php_stream_ops php_stream_rario_ops = {
|
||||
php_rar_ops_write, php_rar_ops_read,
|
||||
php_rar_ops_close, php_rar_ops_flush,
|
||||
"rar",
|
||||
NULL, /* seek */
|
||||
NULL, /* cast */
|
||||
NULL, /* stat */
|
||||
php_rar_ops_stat, /* stat */
|
||||
NULL /* set_option */
|
||||
};
|
||||
|
||||
/* {{{ php_stream_rar_open */
|
||||
/* callback user data does NOT need to be managed outside */
|
||||
/* callback user data does NOT need to be managed outside
|
||||
* No openbasedir etc checks; this is called from RarEntry::getStream and
|
||||
* RarEntry objects cannot be instantiation or tampered with; the check
|
||||
* was already done in RarArchive::open */
|
||||
php_stream *php_stream_rar_open(char *arc_name,
|
||||
char *utf_file_name,
|
||||
rar_cb_user_data *cb_udata_ptr, /* will be copied */
|
||||
@@ -209,7 +279,8 @@ php_stream *php_stream_rar_open(char *arc_name,
|
||||
int result,
|
||||
found;
|
||||
|
||||
if (strncmp(mode, "r", strlen("r")) != 0) {
|
||||
//mode must be exactly "r"
|
||||
if (strncmp(mode, "r", sizeof("r")) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
@@ -234,8 +305,7 @@ php_stream *php_stream_rar_open(char *arc_name,
|
||||
if (!found)
|
||||
_rar_handle_ext_error("Can't find file %s in archive %s" TSRMLS_CC,
|
||||
utf_file_name, arc_name);
|
||||
|
||||
{
|
||||
else {
|
||||
//no need to allocate a buffer bigger than the file uncomp size
|
||||
size_t buffer_size = (size_t)
|
||||
MIN((uint64) RAR_CHUNK_BUFFER_SIZE,
|
||||
@@ -269,7 +339,344 @@ cleanup:
|
||||
return stream;
|
||||
}
|
||||
/* }}} */
|
||||
#endif /* ZEND_ENGINE_2 */
|
||||
|
||||
/* {{{ Wrapper stuff */
|
||||
|
||||
#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 3
|
||||
/* PHP 5.2 has no zend_resolve_path. Adapted from 5.3's php_resolve_path */
|
||||
static char *zend_resolve_path(const char *filename,
|
||||
int filename_length TSRMLS_DC) /* {{{ */
|
||||
{
|
||||
const char *path = PG(include_path);
|
||||
char resolved_path[MAXPATHLEN];
|
||||
char trypath[MAXPATHLEN];
|
||||
const char *ptr, *end;
|
||||
char *actual_path;
|
||||
|
||||
if (filename == NULL || filename[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* do not use the include path in these circumstances */
|
||||
if ((*filename == '.' && (IS_SLASH(filename[1]) ||
|
||||
((filename[1] == '.') && IS_SLASH(filename[2])))) ||
|
||||
IS_ABSOLUTE_PATH(filename, filename_length) ||
|
||||
path == NULL || path[0] == '\0') {
|
||||
if (tsrm_realpath(filename, resolved_path TSRMLS_CC)) {
|
||||
return estrdup(resolved_path);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
ptr = path;
|
||||
while (ptr && *ptr) {
|
||||
end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
|
||||
if (end) {
|
||||
if ((end-ptr) + 1 + filename_length + 1 >= MAXPATHLEN) {
|
||||
ptr = end + 1;
|
||||
continue;
|
||||
}
|
||||
memcpy(trypath, ptr, end-ptr);
|
||||
trypath[end-ptr] = '/';
|
||||
memcpy(trypath+(end-ptr)+1, filename, filename_length+1);
|
||||
ptr = end+1;
|
||||
} else {
|
||||
int len = strlen(ptr);
|
||||
|
||||
if (len + 1 + filename_length + 1 >= MAXPATHLEN) {
|
||||
break;
|
||||
}
|
||||
memcpy(trypath, ptr, len);
|
||||
trypath[len] = '/';
|
||||
memcpy(trypath+len+1, filename, filename_length+1);
|
||||
ptr = NULL;
|
||||
}
|
||||
actual_path = trypath;
|
||||
if (tsrm_realpath(actual_path, resolved_path TSRMLS_CC)) {
|
||||
return estrdup(resolved_path);
|
||||
}
|
||||
} /* end provided path */
|
||||
|
||||
return NULL;
|
||||
}
|
||||
/* }}} */
|
||||
#endif
|
||||
|
||||
/* {{{ php_rar_process_context */
|
||||
/* memory is to be managed externally */
|
||||
static void php_rar_process_context(php_stream_context *context,
|
||||
php_stream_wrapper *wrapper,
|
||||
int options,
|
||||
char **open_password,
|
||||
char **file_password,
|
||||
zval **volume_cb TSRMLS_DC)
|
||||
{
|
||||
zval **ctx_opt = NULL;
|
||||
|
||||
assert(context != NULL);
|
||||
assert(open_password != NULL);
|
||||
assert(file_password != NULL);
|
||||
assert(volume_cb != NULL);
|
||||
|
||||
/* TODO: don't know if I can log errors and not fail. check that */
|
||||
|
||||
if (php_stream_context_get_option(context, "rar", "open_password", &ctx_opt) ==
|
||||
SUCCESS) {
|
||||
if (Z_TYPE_PP(ctx_opt) != IS_STRING)
|
||||
php_stream_wrapper_log_error(wrapper, options TSRMLS_CC,
|
||||
"RAR open password was provided, but not a string.");
|
||||
else
|
||||
*open_password = Z_STRVAL_PP(ctx_opt);
|
||||
}
|
||||
|
||||
if (php_stream_context_get_option(context, "rar", "file_password", &ctx_opt) ==
|
||||
SUCCESS) {
|
||||
if (Z_TYPE_PP(ctx_opt) != IS_STRING)
|
||||
php_stream_wrapper_log_error(wrapper, options TSRMLS_CC,
|
||||
"RAR file password was provided, but not a string.");
|
||||
else
|
||||
*file_password = Z_STRVAL_PP(ctx_opt);
|
||||
}
|
||||
|
||||
if (php_stream_context_get_option(context, "rar", "volume_callback",
|
||||
&ctx_opt) == SUCCESS) {
|
||||
#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION == 2
|
||||
if (zend_is_callable(*ctx_opt, IS_CALLABLE_STRICT, NULL)) {
|
||||
#else
|
||||
if (zend_is_callable(*ctx_opt, IS_CALLABLE_STRICT, NULL TSRMLS_CC)) {
|
||||
#endif
|
||||
*volume_cb = *ctx_opt;
|
||||
}
|
||||
else
|
||||
php_stream_wrapper_log_error(wrapper, options TSRMLS_CC,
|
||||
"RAR volume find callback was provided, but invalid.");
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* _rar_get_archive_and_fragment {{{ */
|
||||
/* calculate fragment and archive from url
|
||||
* *archive and *fragment should be free'd by the parent, even on failure */
|
||||
static int _rar_get_archive_and_fragment(php_stream_wrapper *wrapper,
|
||||
char *filename,
|
||||
int options,
|
||||
char **archive,
|
||||
char **fragment TSRMLS_DC)
|
||||
{
|
||||
char *tmp_fragment,
|
||||
*tmp_archive = NULL;
|
||||
int tmp_arch_len;
|
||||
int ret = FAILURE;
|
||||
|
||||
/* php_stream_open_wrapper_ex calls php_stream_locate_url_wrapper,
|
||||
* which strips the prefix in path_for_open, but check anyway */
|
||||
if (strncmp(filename, "rar://", sizeof("rar://") - 1) == 0) {
|
||||
filename += sizeof("rar://") - 1;
|
||||
}
|
||||
|
||||
tmp_fragment = strchr(filename, '#');
|
||||
if (tmp_fragment == NULL || strlen(tmp_fragment) == 1 ||
|
||||
tmp_fragment == filename) {
|
||||
php_stream_wrapper_log_error(wrapper, options TSRMLS_CC,
|
||||
"The url must contain a path and a non-empty fragment; it must be "
|
||||
"must in the form \"rar://<urlencoded path to RAR archive>#"
|
||||
"<urlencoded entry name>\"");
|
||||
goto cleanup;
|
||||
}
|
||||
tmp_arch_len = tmp_fragment - filename;
|
||||
tmp_archive = emalloc(tmp_arch_len + 1);
|
||||
strlcpy(tmp_archive, filename, tmp_arch_len + 1);
|
||||
*fragment = estrdup(tmp_fragment + 1); //+ 1 to skip # character
|
||||
php_raw_url_decode(tmp_archive, tmp_arch_len);
|
||||
php_raw_url_decode(*fragment, strlen(*fragment));
|
||||
|
||||
if (!(options & STREAM_ASSUME_REALPATH)) {
|
||||
if (options & USE_PATH) {
|
||||
*archive = zend_resolve_path(tmp_archive, tmp_arch_len TSRMLS_CC);
|
||||
}
|
||||
if (*archive == NULL) {
|
||||
if ((*archive = expand_filepath(tmp_archive, NULL TSRMLS_CC))
|
||||
== NULL) {
|
||||
php_stream_wrapper_log_error(wrapper, options TSRMLS_CC,
|
||||
"Could not expand the path %s", archive);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!(options & STREAM_DISABLE_OPEN_BASEDIR) &&
|
||||
php_check_open_basedir(*archive TSRMLS_CC)) {
|
||||
//php_check_open_basedir already emits the error
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if ((options & ENFORCE_SAFE_MODE) && PG(safe_mode) &&
|
||||
(!php_checkuid(*archive, "r", CHECKUID_CHECK_MODE_PARAM))) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = SUCCESS;
|
||||
cleanup:
|
||||
if (tmp_archive != NULL)
|
||||
efree(tmp_archive);
|
||||
return ret;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ php_stream_rar_opener */
|
||||
php_stream *php_stream_rar_opener(php_stream_wrapper *wrapper,
|
||||
char *filename,
|
||||
char *mode,
|
||||
int options,
|
||||
char **opened_path,
|
||||
php_stream_context *context STREAMS_DC TSRMLS_DC)
|
||||
{
|
||||
char *fragment = NULL,
|
||||
/* used to hold the pointer that may be copied to opened_path */
|
||||
*tmp_open_path = NULL,
|
||||
*open_passwd = NULL,
|
||||
*file_passwd = NULL;
|
||||
char const *rar_error;
|
||||
int rar_result,
|
||||
file_found;
|
||||
zval *volume_cb = NULL;
|
||||
php_rar_stream_data_P self = NULL;
|
||||
php_stream *stream = NULL;
|
||||
|
||||
/* {{{ preliminaries */
|
||||
if (options & STREAM_OPEN_PERSISTENT) {
|
||||
/* TODO: add support for opening RAR files in a persisten fashion */
|
||||
php_stream_wrapper_log_error(wrapper, options TSRMLS_CC,
|
||||
"No support for opening RAR files persistently yet");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//mode must be exactly "r"
|
||||
if (strncmp(mode, "r", sizeof("r")) != 0) {
|
||||
php_stream_wrapper_log_error(wrapper, options TSRMLS_CC,
|
||||
"Only the \"r\" open mode is permitted, given %s", mode);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (_rar_get_archive_and_fragment(wrapper, filename, options,
|
||||
&tmp_open_path, &fragment TSRMLS_CC) == FAILURE) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (context != NULL) {
|
||||
php_rar_process_context(context, wrapper, options, &open_passwd,
|
||||
&file_passwd, &volume_cb TSRMLS_CC);
|
||||
}
|
||||
|
||||
self = ecalloc(1, sizeof *self);
|
||||
self->open_data.ArcName = estrdup(tmp_open_path);
|
||||
self->open_data.OpenMode = RAR_OM_EXTRACT;
|
||||
if (open_passwd != NULL)
|
||||
self->cb_userdata.password = estrdup(open_passwd);
|
||||
if (volume_cb != NULL) {
|
||||
self->cb_userdata.callable = volume_cb;
|
||||
zval_add_ref(&self->cb_userdata.callable);
|
||||
SEPARATE_ZVAL(&self->cb_userdata.callable);
|
||||
}
|
||||
|
||||
rar_result = _rar_find_file(&self->open_data, fragment, &self->cb_userdata,
|
||||
&self->rar_handle, &file_found, &self->header_data);
|
||||
|
||||
if ((rar_error = _rar_error_to_string(rar_result)) != NULL) {
|
||||
php_stream_wrapper_log_error(wrapper, options TSRMLS_CC,
|
||||
"Error opening RAR archive %s: %s", tmp_open_path, rar_error);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!file_found) {
|
||||
php_stream_wrapper_log_error(wrapper, options TSRMLS_CC,
|
||||
"Can't file %s in RAR archive %s", fragment, tmp_open_path);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* once found, the password that matters is the file level password.
|
||||
* we will NOT default on the open password if no file level password is
|
||||
* given, but an open password is. This behaviour is differs from that of
|
||||
* RarEntry::extract() */
|
||||
if (self->cb_userdata.password != NULL)
|
||||
efree(self->cb_userdata.password);
|
||||
|
||||
if (file_passwd == NULL)
|
||||
self->cb_userdata.password = NULL;
|
||||
else
|
||||
self->cb_userdata.password = estrdup(file_passwd);
|
||||
|
||||
|
||||
{
|
||||
//no need to allocate a buffer bigger than the file uncomp size
|
||||
size_t buffer_size = (size_t)
|
||||
MIN((uint64) RAR_CHUNK_BUFFER_SIZE,
|
||||
INT32TO64(self->header_data.UnpSizeHigh,
|
||||
self->header_data.UnpSize));
|
||||
rar_result = RARProcessFileChunkInit(self->rar_handle);
|
||||
|
||||
if ((rar_error = _rar_error_to_string(rar_result)) != NULL) {
|
||||
php_stream_wrapper_log_error(wrapper, options TSRMLS_CC,
|
||||
"Error opening file %s inside RAR archive %s: %s",
|
||||
fragment, tmp_open_path, rar_error);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
self->buffer = emalloc(buffer_size);
|
||||
self->buffer_size = buffer_size;
|
||||
stream = php_stream_alloc(&php_stream_rario_ops, self, NULL, mode);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
|
||||
if (tmp_open_path != NULL) {
|
||||
if (opened_path != NULL)
|
||||
*opened_path = tmp_open_path;
|
||||
else
|
||||
efree(tmp_open_path);
|
||||
}
|
||||
if (fragment != NULL)
|
||||
efree(fragment);
|
||||
|
||||
if (stream == NULL) { //failed
|
||||
if (self != NULL) {
|
||||
if (self->open_data.ArcName != NULL)
|
||||
efree(self->open_data.ArcName);
|
||||
_rar_destroy_userdata(&self->cb_userdata);
|
||||
if (self->buffer != NULL)
|
||||
efree(self->buffer);
|
||||
if (self->rar_handle != NULL)
|
||||
RARCloseArchive(self->rar_handle);
|
||||
efree(self);
|
||||
}
|
||||
}
|
||||
|
||||
return stream; /* may be null */
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static php_stream_wrapper_ops rar_stream_wops = {
|
||||
php_stream_rar_opener,
|
||||
NULL, /* close */
|
||||
NULL, /* fstat */
|
||||
NULL, /* stat */
|
||||
NULL, /* opendir */
|
||||
"rar wrapper",
|
||||
NULL, /* unlink */
|
||||
NULL, /* rename */
|
||||
NULL, /* mkdir */
|
||||
NULL /* rmdir */
|
||||
};
|
||||
|
||||
extern php_stream_wrapper php_stream_rar_wrapper = {
|
||||
&rar_stream_wops,
|
||||
NULL,
|
||||
0 /* is_url */
|
||||
};
|
||||
|
||||
/* end wrapper stuff }}} */
|
||||
|
||||
#endif /* HAVE_RAR */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
68
rararch.c
68
rararch.c
@@ -72,6 +72,51 @@ int _rar_get_file_resource(zval *zval_file, rar_file_t **rar_file TSRMLS_DC) /*
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* creates a hashtable whose keys are names of the files inside the RAR
|
||||
* archive and the values are indexes (with respect to rar_file->entries)
|
||||
* of the first entry of the file with that name */
|
||||
int _rar_index_entries(rar_file_t *rar_file TSRMLS_DC) /* {{{ */
|
||||
{
|
||||
int i;
|
||||
int first_file_check;
|
||||
|
||||
assert(rar_file->entries_idx == NULL);
|
||||
|
||||
if (rar_file->entries == NULL)
|
||||
_rar_list_files(rar_file TSRMLS_CC);
|
||||
|
||||
ALLOC_HASHTABLE(rar_file->entries_idx);
|
||||
if (zend_hash_init(rar_file->entries_idx, rar_file->entry_count, NULL,
|
||||
NULL, 0) == FAILURE) {
|
||||
FREE_HASHTABLE(rar_file->entries_idx);
|
||||
rar_file->entries_idx = NULL;
|
||||
return FAILURE;
|
||||
}
|
||||
|
||||
for (i = 0, first_file_check = TRUE; i < rar_file->entry_count; i++) {
|
||||
struct RARHeaderDataEx *entry;
|
||||
const wchar_t *cur_name;
|
||||
|
||||
entry = rar_file->entries[i];
|
||||
cur_name = entry->FileNameW;
|
||||
|
||||
/* skip files continued from the last volume */
|
||||
if (first_file_check) {
|
||||
if (entry->Flags & 0x01)
|
||||
continue;
|
||||
else
|
||||
first_file_check = FALSE;
|
||||
}
|
||||
|
||||
zend_hash_add(rar_file->entries_idx, (const char *) cur_name,
|
||||
(wcsnlen(cur_name, sizeof entry->FileNameW) + 1) * sizeof *cur_name,
|
||||
&i, sizeof(i), NULL);
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* Receives archive zval, returns object struct.
|
||||
* If silent is FALSE, it checks whether the archive is alredy closed, and if it
|
||||
* is, an exception/error is raised and 0 is returned
|
||||
@@ -125,10 +170,15 @@ static int _rar_list_files(rar_file_t *rar TSRMLS_DC) /* {{{ */
|
||||
rar->entry_count++;
|
||||
}
|
||||
}
|
||||
//uncomment to have always have index
|
||||
/* if (rar->entries != NULL)
|
||||
_rar_index_entries(rar TSRMLS_CC); */
|
||||
return result;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* Read the rar->entries lazy cache and create either one or more zvals from
|
||||
* those entries */
|
||||
static int _rar_raw_entries_to_files(rar_file_t *rar,
|
||||
const wchar_t * const file, //can be NULL
|
||||
int *index, //start index, can be NULL
|
||||
@@ -140,10 +190,19 @@ static int _rar_raw_entries_to_files(rar_file_t *rar,
|
||||
unsigned long packed_size = 0UL;
|
||||
struct RARHeaderDataEx *last_entry = NULL;
|
||||
int any_commit = FALSE;
|
||||
int first_file_check = (index == NULL) || (*index == 0);
|
||||
int target_is_obj = (file != NULL || index != NULL);
|
||||
int first_file_check;
|
||||
int target_is_obj;
|
||||
int i;
|
||||
|
||||
if (index == NULL && file != NULL && rar->entries_idx != NULL) {
|
||||
if (zend_hash_find(rar->entries_idx, (const char *) file,
|
||||
(wcslen(file) + 1) * sizeof *file, &index) == FAILURE)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
first_file_check = (index == NULL) || (*index == 0);
|
||||
target_is_obj = (file != NULL || index != NULL);
|
||||
|
||||
assert(rar->entry_count == 0 || rar->entries != NULL);
|
||||
for (i = (index == NULL ? 0 : *index); i <= rar->entry_count; i++) {
|
||||
struct RARHeaderDataEx *entry;
|
||||
@@ -310,6 +369,10 @@ static void rararch_ce_free_object_storage(ze_rararch_object *object TSRMLS_DC)
|
||||
efree(rar->entries);
|
||||
rar->entry_count = 0;
|
||||
}
|
||||
if (rar->entries_idx != NULL) {
|
||||
zend_hash_destroy(rar->entries_idx);
|
||||
FREE_HASHTABLE(rar->entries_idx);
|
||||
}
|
||||
efree(rar->list_open_data->ArcName);
|
||||
efree(rar->list_open_data->CmtBuf);
|
||||
efree(rar->list_open_data);
|
||||
@@ -379,6 +442,7 @@ PHP_FUNCTION(rar_open)
|
||||
rar->cb_userdata.password = NULL;
|
||||
rar->cb_userdata.callable = NULL;
|
||||
rar->entries = NULL;
|
||||
rar->entries_idx = NULL;
|
||||
rar->entry_count = 0;
|
||||
|
||||
rar->arch_handle = RAROpenArchiveEx(rar->list_open_data);
|
||||
|
||||
17
rarentry.c
17
rarentry.c
@@ -246,7 +246,8 @@ PHP_METHOD(rarentry, extract)
|
||||
/* Decide where to extract */
|
||||
with_second_arg = (filepath_len != 0);
|
||||
|
||||
//the arguments are mutually exclusive. If the second is specified, must ignore the first
|
||||
/* the arguments are mutually exclusive.
|
||||
* If the second is specified, we ignore the first */
|
||||
if (!with_second_arg) {
|
||||
if (dir_len == 0) //both params empty
|
||||
dir = ".";
|
||||
@@ -266,10 +267,13 @@ PHP_METHOD(rarentry, extract)
|
||||
/* Find file inside archive */
|
||||
RAR_GET_PROPERTY(tmp_name, "name");
|
||||
|
||||
//use rar_open password (stored in rar->cb_userdata) by default
|
||||
/* don't set the new password now because maybe the headers are
|
||||
* encrypted with a password different from this file's (though WinRAR
|
||||
* does not support that: if you encrypt the headers, you must encrypt
|
||||
* the files with the same password). By not replacing the password
|
||||
* now, we're using the password given to rar_open, if any (which must
|
||||
* have enabled decrypting the headers or else we wouldn't be here */
|
||||
memcpy(&cb_udata, &rar->cb_userdata, sizeof cb_udata);
|
||||
if (password != NULL)
|
||||
cb_udata.password = password;
|
||||
|
||||
result = _rar_find_file(rar->extract_open_data, Z_STRVAL_PP(tmp_name),
|
||||
&cb_udata, &extract_handle, &found, &entry);
|
||||
@@ -287,6 +291,11 @@ PHP_METHOD(rarentry, extract)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* now use the password given to this method. If none was given, we're
|
||||
* still stuck with the password given to rar_open, if any */
|
||||
if (password != NULL)
|
||||
cb_udata.password = password;
|
||||
|
||||
/* Do extraction */
|
||||
if (!with_second_arg)
|
||||
result = RARProcessFile(extract_handle, RAR_EXTRACT,
|
||||
|
||||
275
technote.txt
Normal file
275
technote.txt
Normal file
@@ -0,0 +1,275 @@
|
||||
|
||||
RAR version 3.93 - Technical information
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
THE ARCHIVE FORMAT DESCRIBED BELOW IS ONLY VALID FOR VERSIONS SINCE 1.50
|
||||
|
||||
==========================================================================
|
||||
RAR archive file format
|
||||
==========================================================================
|
||||
|
||||
Archive file consists of variable length blocks. The order of these
|
||||
blocks may vary, but the first block must be a marker block followed by
|
||||
an archive header block.
|
||||
|
||||
Each block begins with the following fields:
|
||||
|
||||
HEAD_CRC 2 bytes CRC of total block or block part
|
||||
HEAD_TYPE 1 byte Block type
|
||||
HEAD_FLAGS 2 bytes Block flags
|
||||
HEAD_SIZE 2 bytes Block size
|
||||
ADD_SIZE 4 bytes Optional field - added block size
|
||||
|
||||
Field ADD_SIZE present only if (HEAD_FLAGS & 0x8000) != 0
|
||||
|
||||
Total block size is HEAD_SIZE if (HEAD_FLAGS & 0x8000) == 0
|
||||
and HEAD_SIZE+ADD_SIZE if the field ADD_SIZE is present - when
|
||||
(HEAD_FLAGS & 0x8000) != 0.
|
||||
|
||||
In each block the followings bits in HEAD_FLAGS have the same meaning:
|
||||
|
||||
0x4000 - if set, older RAR versions will ignore the block
|
||||
and remove it when the archive is updated.
|
||||
if clear, the block is copied to the new archive
|
||||
file when the archive is updated;
|
||||
|
||||
0x8000 - if set, ADD_SIZE field is present and the full block
|
||||
size is HEAD_SIZE+ADD_SIZE.
|
||||
|
||||
Declared block types:
|
||||
|
||||
HEAD_TYPE=0x72 marker block
|
||||
HEAD_TYPE=0x73 archive header
|
||||
HEAD_TYPE=0x74 file header
|
||||
HEAD_TYPE=0x75 old style comment header
|
||||
HEAD_TYPE=0x76 old style authenticity information
|
||||
HEAD_TYPE=0x77 old style subblock
|
||||
HEAD_TYPE=0x78 old style recovery record
|
||||
HEAD_TYPE=0x79 old style authenticity information
|
||||
HEAD_TYPE=0x7a subblock
|
||||
|
||||
Comment block is actually used only within other blocks and doesn't
|
||||
exist separately.
|
||||
|
||||
Archive processing is made in the following manner:
|
||||
|
||||
1. Read and check marker block
|
||||
2. Read archive header
|
||||
3. Read or skip HEAD_SIZE-sizeof(MAIN_HEAD) bytes
|
||||
4. If end of archive encountered then terminate archive processing,
|
||||
else read 7 bytes into fields HEAD_CRC, HEAD_TYPE, HEAD_FLAGS,
|
||||
HEAD_SIZE.
|
||||
5. Check HEAD_TYPE.
|
||||
if HEAD_TYPE==0x74
|
||||
read file header ( first 7 bytes already read )
|
||||
read or skip HEAD_SIZE-sizeof(FILE_HEAD) bytes
|
||||
if (HEAD_FLAGS & 0x100)
|
||||
read or skip HIGH_PACK_SIZE*0x100000000+PACK_SIZE bytes
|
||||
else
|
||||
read or skip PACK_SIZE bytes
|
||||
else
|
||||
read corresponding HEAD_TYPE block:
|
||||
read HEAD_SIZE-7 bytes
|
||||
if (HEAD_FLAGS & 0x8000)
|
||||
read ADD_SIZE bytes
|
||||
6. go to 4.
|
||||
|
||||
|
||||
==========================================================================
|
||||
Block Formats
|
||||
==========================================================================
|
||||
|
||||
|
||||
Marker block ( MARK_HEAD )
|
||||
|
||||
|
||||
HEAD_CRC Always 0x6152
|
||||
2 bytes
|
||||
|
||||
HEAD_TYPE Header type: 0x72
|
||||
1 byte
|
||||
|
||||
HEAD_FLAGS Always 0x1a21
|
||||
2 bytes
|
||||
|
||||
HEAD_SIZE Block size = 0x0007
|
||||
2 bytes
|
||||
|
||||
The marker block is actually considered as a fixed byte
|
||||
sequence: 0x52 0x61 0x72 0x21 0x1a 0x07 0x00
|
||||
|
||||
|
||||
|
||||
Archive header ( MAIN_HEAD )
|
||||
|
||||
|
||||
HEAD_CRC CRC of fields HEAD_TYPE to RESERVED2
|
||||
2 bytes
|
||||
|
||||
HEAD_TYPE Header type: 0x73
|
||||
1 byte
|
||||
|
||||
HEAD_FLAGS Bit flags:
|
||||
2 bytes
|
||||
0x0001 - Volume attribute (archive volume)
|
||||
0x0002 - Archive comment present
|
||||
RAR 3.x uses the separate comment block
|
||||
and does not set this flag.
|
||||
|
||||
0x0004 - Archive lock attribute
|
||||
0x0008 - Solid attribute (solid archive)
|
||||
0x0010 - New volume naming scheme ('volname.partN.rar')
|
||||
0x0020 - Authenticity information present
|
||||
RAR 3.x does not set this flag.
|
||||
|
||||
0x0040 - Recovery record present
|
||||
0x0080 - Block headers are encrypted
|
||||
0x0100 - First volume (set only by RAR 3.0 and later)
|
||||
|
||||
other bits in HEAD_FLAGS are reserved for
|
||||
internal use
|
||||
|
||||
HEAD_SIZE Archive header total size including archive comments
|
||||
2 bytes
|
||||
|
||||
RESERVED1 Reserved
|
||||
2 bytes
|
||||
|
||||
RESERVED2 Reserved
|
||||
4 bytes
|
||||
|
||||
|
||||
|
||||
File header (File in archive)
|
||||
|
||||
|
||||
HEAD_CRC CRC of fields from HEAD_TYPE to FILEATTR
|
||||
2 bytes and file name
|
||||
|
||||
HEAD_TYPE Header type: 0x74
|
||||
1 byte
|
||||
|
||||
HEAD_FLAGS Bit flags:
|
||||
2 bytes
|
||||
0x01 - file continued from previous volume
|
||||
0x02 - file continued in next volume
|
||||
0x04 - file encrypted with password
|
||||
|
||||
0x08 - file comment present
|
||||
RAR 3.x uses the separate comment block
|
||||
and does not set this flag.
|
||||
|
||||
0x10 - information from previous files is used (solid flag)
|
||||
(for RAR 2.0 and later)
|
||||
|
||||
bits 7 6 5 (for RAR 2.0 and later)
|
||||
|
||||
0 0 0 - dictionary size 64 KB
|
||||
0 0 1 - dictionary size 128 KB
|
||||
0 1 0 - dictionary size 256 KB
|
||||
0 1 1 - dictionary size 512 KB
|
||||
1 0 0 - dictionary size 1024 KB
|
||||
1 0 1 - dictionary size 2048 KB
|
||||
1 1 0 - dictionary size 4096 KB
|
||||
1 1 1 - file is directory
|
||||
|
||||
0x100 - HIGH_PACK_SIZE and HIGH_UNP_SIZE fields
|
||||
are present. These fields are used to archive
|
||||
only very large files (larger than 2Gb),
|
||||
for smaller files these fields are absent.
|
||||
|
||||
0x200 - FILE_NAME contains both usual and encoded
|
||||
Unicode name separated by zero. In this case
|
||||
NAME_SIZE field is equal to the length
|
||||
of usual name plus encoded Unicode name plus 1.
|
||||
|
||||
If this flag is present, but FILE_NAME does not
|
||||
contain zero bytes, it means that file name
|
||||
is encoded using UTF-8.
|
||||
|
||||
0x400 - the header contains additional 8 bytes
|
||||
after the file name, which are required to
|
||||
increase encryption security (so called 'salt').
|
||||
|
||||
0x800 - Version flag. It is an old file version,
|
||||
a version number is appended to file name as ';n'.
|
||||
|
||||
0x1000 - Extended time field present.
|
||||
|
||||
0x8000 - this bit always is set, so the complete
|
||||
block size is HEAD_SIZE + PACK_SIZE
|
||||
(and plus HIGH_PACK_SIZE, if bit 0x100 is set)
|
||||
|
||||
HEAD_SIZE File header full size including file name and comments
|
||||
2 bytes
|
||||
|
||||
PACK_SIZE Compressed file size
|
||||
4 bytes
|
||||
|
||||
UNP_SIZE Uncompressed file size
|
||||
4 bytes
|
||||
|
||||
HOST_OS Operating system used for archiving
|
||||
1 byte 0 - MS DOS
|
||||
1 - OS/2
|
||||
2 - Win32
|
||||
3 - Unix
|
||||
4 - Mac OS
|
||||
5 - BeOS
|
||||
|
||||
FILE_CRC File CRC
|
||||
4 bytes
|
||||
|
||||
FTIME Date and time in standard MS DOS format
|
||||
4 bytes
|
||||
|
||||
UNP_VER RAR version needed to extract file
|
||||
1 byte
|
||||
Version number is encoded as
|
||||
10 * Major version + minor version.
|
||||
|
||||
METHOD Packing method
|
||||
1 byte
|
||||
0x30 - storing
|
||||
0x31 - fastest compression
|
||||
0x32 - fast compression
|
||||
0x33 - normal compression
|
||||
0x34 - good compression
|
||||
0x35 - best compression
|
||||
|
||||
NAME_SIZE File name size
|
||||
2 bytes
|
||||
|
||||
ATTR File attributes
|
||||
4 bytes
|
||||
|
||||
HIGH_PACK_SIZE High 4 bytes of 64 bit value of compressed file size.
|
||||
4 bytes Optional value, presents only if bit 0x100 in HEAD_FLAGS
|
||||
is set.
|
||||
|
||||
HIGH_UNP_SIZE High 4 bytes of 64 bit value of uncompressed file size.
|
||||
4 bytes Optional value, presents only if bit 0x100 in HEAD_FLAGS
|
||||
is set.
|
||||
|
||||
FILE_NAME File name - string of NAME_SIZE bytes size
|
||||
|
||||
SALT present if (HEAD_FLAGS & 0x400) != 0
|
||||
8 bytes
|
||||
|
||||
EXT_TIME present if (HEAD_FLAGS & 0x1000) != 0
|
||||
variable size
|
||||
|
||||
other new fields may appear here.
|
||||
|
||||
|
||||
==========================================================================
|
||||
Application notes
|
||||
==========================================================================
|
||||
|
||||
1. To process an SFX archive you need to skip the SFX module searching
|
||||
for the marker block in the archive. There is no marker block sequence (0x52
|
||||
0x61 0x72 0x21 0x1a 0x07 0x00) in the SFX module itself.
|
||||
|
||||
2. The CRC is calculated using the standard polynomial 0xEDB88320. In
|
||||
case the size of the CRC is less than 4 bytes, only the low order bytes
|
||||
are used.
|
||||
@@ -19,7 +19,7 @@ foreach ($rar_file1 as $e) {
|
||||
$stream = $e->getStream();
|
||||
echo $e->getName().": ";
|
||||
$a = "";
|
||||
while (!feof($stream)) {
|
||||
while (is_resource($stream) && !feof($stream)) {
|
||||
$a .= fread($stream, 8192);
|
||||
}
|
||||
echo strlen($a)." bytes, CRC ";
|
||||
|
||||
16
tests/050.phpt
Normal file
16
tests/050.phpt
Normal file
@@ -0,0 +1,16 @@
|
||||
--TEST--
|
||||
Stream wrapper basic test
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if(!extension_loaded("rar")) die("skip");
|
||||
--FILE--
|
||||
<?php
|
||||
$stream = fopen("rar://" .
|
||||
dirname(__FILE__) . '/latest_winrar.rar' .
|
||||
"#1.txt", "r");
|
||||
var_dump(stream_get_contents($stream));
|
||||
|
||||
echo "Done.\n";
|
||||
--EXPECTF--
|
||||
string(5) "11111"
|
||||
Done.
|
||||
62
tests/051.phpt
Normal file
62
tests/051.phpt
Normal file
@@ -0,0 +1,62 @@
|
||||
--TEST--
|
||||
Stream wrapper relative path test
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if(!extension_loaded("rar")) die("skip");
|
||||
--CLEAN--
|
||||
<?php
|
||||
unlink(dirname(__FILE__) . '/temp/tmp.rar');
|
||||
rmdir(dirname(__FILE__) . "/temp");
|
||||
--FILE--
|
||||
<?php
|
||||
mkdir(dirname(__FILE__) . "/temp");
|
||||
chdir(dirname(__FILE__) . "/temp");
|
||||
|
||||
echo "Test relative to working dir:\n";
|
||||
$stream = fopen("rar://" .
|
||||
'../latest_winrar.rar' .
|
||||
"#1.txt", "r");
|
||||
var_dump(stream_get_contents($stream));
|
||||
|
||||
echo "\nTest with include path:\n";
|
||||
copy(dirname(__FILE__) . '/latest_winrar.rar',
|
||||
dirname(__FILE__) . '/temp/tmp.rar');
|
||||
chdir(dirname(__FILE__));
|
||||
|
||||
//now with include
|
||||
echo "Should fail (not in include):\n";
|
||||
$stream = fopen("rar://" .
|
||||
'tmp.rar' .
|
||||
"#1.txt", "r");
|
||||
|
||||
echo "\nShould fail (include unused):\n";
|
||||
|
||||
set_include_path(dirname(__FILE__). '/temp');
|
||||
$stream = fopen("rar://" .
|
||||
'tmp.rar' .
|
||||
"#1.txt", "r");
|
||||
|
||||
echo "\nShould succeed:\n";
|
||||
$stream = fopen("rar://" .
|
||||
'tmp.rar' .
|
||||
"#1.txt", "r", true);
|
||||
var_dump(stream_get_contents($stream));
|
||||
|
||||
echo "Done.\n";
|
||||
--EXPECTF--
|
||||
Test relative to working dir:
|
||||
string(5) "11111"
|
||||
|
||||
Test with include path:
|
||||
Should fail (not in include):
|
||||
|
||||
Warning: fopen(rar://tmp.rar#1.txt): failed to open stream: Error opening RAR archive %stmp.rar: ERAR_EOPEN (file open error) in %s on line %d
|
||||
|
||||
Should fail (include unused):
|
||||
|
||||
Warning: fopen(rar://tmp.rar#1.txt): failed to open stream: Error opening RAR archive %stmp.rar: ERAR_EOPEN (file open error) in %s on line %d
|
||||
|
||||
Should succeed:
|
||||
string(5) "11111"
|
||||
Done.
|
||||
|
||||
28
tests/052.phpt
Normal file
28
tests/052.phpt
Normal file
@@ -0,0 +1,28 @@
|
||||
--TEST--
|
||||
Stream wrapper archive/file not found
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if(!extension_loaded("rar")) die("skip");
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
echo "Archive not found :\n";
|
||||
$stream = fopen("rar://" .
|
||||
dirname(__FILE__) . "/not_found.rar" .
|
||||
"#1.txt", "r");
|
||||
|
||||
echo "\nFile not found :\n";
|
||||
$stream = fopen("rar://" .
|
||||
dirname(__FILE__) . "/latest_winrar.rar" .
|
||||
"#not_found.txt", "r");
|
||||
|
||||
echo "Done.\n";
|
||||
--EXPECTF--
|
||||
Archive not found :
|
||||
|
||||
Warning: fopen(rar://%snot_found.rar#1.txt): failed to open stream: Error opening RAR archive %snot_found.rar: ERAR_EOPEN (file open error) in %s on line %d
|
||||
|
||||
File not found :
|
||||
|
||||
Warning: fopen(rar://%slatest_winrar.rar#not_found.txt): failed to open stream: Can't file not_found.txt in RAR archive %s on line %d
|
||||
Done.
|
||||
47
tests/053.phpt
Normal file
47
tests/053.phpt
Normal file
@@ -0,0 +1,47 @@
|
||||
--TEST--
|
||||
Stream wrapper malformed url
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if(!extension_loaded("rar")) die("skip");
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
echo "Test empty:\n";
|
||||
$stream = fopen("rar://", "r");
|
||||
|
||||
echo "\nTest no fragment:\n";
|
||||
$stream = fopen("rar://file.rar", "r");
|
||||
|
||||
echo "\nTest empty fragment:\n";
|
||||
$stream = fopen("rar://file.rar#", "r");
|
||||
|
||||
echo "\nTest no path:\n";
|
||||
|
||||
$stream = fopen("rar://#frag", "r");
|
||||
|
||||
echo "\nTest no path and empty fragment:\n";
|
||||
|
||||
$stream = fopen("rar://#", "r");
|
||||
|
||||
echo "Done.\n";
|
||||
--EXPECTF--
|
||||
Test empty:
|
||||
|
||||
Warning: fopen(rar://): failed to open stream: The url must contain a path and a non-empty fragment; it must be must in the form "rar://<urlencoded path to RAR archive>#<urlencoded entry name>" in %s on line %d
|
||||
|
||||
Test no fragment:
|
||||
|
||||
Warning: fopen(rar://file.rar): failed to open stream: The url must contain a path and a non-empty fragment; it must be must in the form "rar://<urlencoded path to RAR archive>#<urlencoded entry name>" in %s on line %d
|
||||
|
||||
Test empty fragment:
|
||||
|
||||
Warning: fopen(rar://file.rar#): failed to open stream: The url must contain a path and a non-empty fragment; it must be must in the form "rar://<urlencoded path to RAR archive>#<urlencoded entry name>" in %s on line %d
|
||||
|
||||
Test no path:
|
||||
|
||||
Warning: fopen(rar://#frag): failed to open stream: The url must contain a path and a non-empty fragment; it must be must in the form "rar://<urlencoded path to RAR archive>#<urlencoded entry name>" in %s on line %d
|
||||
|
||||
Test no path and empty fragment:
|
||||
|
||||
Warning: fopen(rar://#): failed to open stream: The url must contain a path and a non-empty fragment; it must be must in the form "rar://<urlencoded path to RAR archive>#<urlencoded entry name>" in %s on line %d
|
||||
Done.
|
||||
79
tests/054.phpt
Normal file
79
tests/054.phpt
Normal file
@@ -0,0 +1,79 @@
|
||||
--TEST--
|
||||
Stream wrapper with header or file level passwords
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if(!extension_loaded("rar")) die("skip");
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
echo "Headers: should not work (no password):\n";
|
||||
$stream = fopen("rar://" .
|
||||
dirname(__FILE__) . '/encrypted_headers.rar' .
|
||||
"#encfile1.txt", "r");
|
||||
|
||||
|
||||
echo "\nHeaders: should not work (password given was file_password):\n";
|
||||
|
||||
$stream = fopen("rar://" .
|
||||
dirname(__FILE__) . '/encrypted_headers.rar' .
|
||||
"#encfile1.txt", "r", false,
|
||||
stream_context_create(array('rar'=>array('file_password'=>'samplepassword'))));
|
||||
|
||||
echo "\nHeaders: should work (password given was open_password):\n";
|
||||
|
||||
$stream = fopen("rar://" .
|
||||
dirname(__FILE__) . '/encrypted_headers.rar' .
|
||||
"#encfile1.txt", "r", false,
|
||||
stream_context_create(array('rar'=>array('open_password'=>'samplepassword'))));
|
||||
var_dump(stream_get_contents($stream));
|
||||
|
||||
//////////////////////
|
||||
|
||||
echo "\n\nFiles: should not work (no password):\n";
|
||||
$stream = fopen("rar://" .
|
||||
dirname(__FILE__) . '/encrypted_only_files.rar' .
|
||||
"#encfile1.txt", "r");
|
||||
|
||||
|
||||
echo "\nFiles: should not work (password given was open_password):\n";
|
||||
|
||||
$stream = fopen("rar://" .
|
||||
dirname(__FILE__) . '/encrypted_only_files.rar' .
|
||||
"#encfile1.txt", "r", false,
|
||||
stream_context_create(array('rar'=>array('open_password'=>'samplepassword'))));
|
||||
|
||||
echo "\nFiles: should work (password given was file_password):\n";
|
||||
|
||||
$stream = fopen("rar://" .
|
||||
dirname(__FILE__) . '/encrypted_only_files.rar' .
|
||||
"#encfile1.txt", "r", false,
|
||||
stream_context_create(array('rar'=>array('file_password'=>'samplepassword'))));
|
||||
var_dump(stream_get_contents($stream));
|
||||
|
||||
echo "\nDone.\n";
|
||||
|
||||
--EXPECTF--
|
||||
Headers: should not work (no password):
|
||||
|
||||
Warning: fopen(rar://%sencrypted_headers.rar#encfile1.txt): failed to open stream: Error opening RAR archive %sencrypted_headers.rar: ERAR_MISSING_PASSWORD (password needed but not specified) in %s on line %d
|
||||
|
||||
Headers: should not work (password given was file_password):
|
||||
|
||||
Warning: fopen(rar://%sencrypted_headers.rar#encfile1.txt): failed to open stream: Error opening RAR archive %sencrypted_headers.rar: ERAR_MISSING_PASSWORD (password needed but not specified) in %s on line %d
|
||||
|
||||
Headers: should work (password given was open_password):
|
||||
string(26) "Encrypted file 1 contents."
|
||||
|
||||
|
||||
Files: should not work (no password):
|
||||
|
||||
Warning: fopen(rar://%sencrypted_only_files.rar#encfile1.txt): failed to open stream: Error opening file encfile1.txt inside RAR archive %sencrypted_only_files.rar: ERAR_MISSING_PASSWORD (password needed but not specified) in %s on line %d
|
||||
|
||||
Files: should not work (password given was open_password):
|
||||
|
||||
Warning: fopen(rar://%sencrypted_only_files.rar#encfile1.txt): failed to open stream: Error opening file encfile1.txt inside RAR archive %sencrypted_only_files.rar: ERAR_MISSING_PASSWORD (password needed but not specified) in %s on line %d
|
||||
|
||||
Files: should work (password given was file_password):
|
||||
string(26) "Encrypted file 1 contents."
|
||||
|
||||
Done.
|
||||
30
tests/055.phpt
Normal file
30
tests/055.phpt
Normal file
@@ -0,0 +1,30 @@
|
||||
--TEST--
|
||||
Stream wrapper with volume find callback
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if(!extension_loaded("rar")) die("skip");
|
||||
--FILE--
|
||||
<?php
|
||||
function resolve($vol) {
|
||||
if (preg_match('/_broken/', $vol))
|
||||
return str_replace('_broken', '', $vol);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
function int32_to_hex($value) {
|
||||
$value &= 0xffffffff;
|
||||
return str_pad(strtoupper(dechex($value)), 8, "0", STR_PAD_LEFT);
|
||||
}
|
||||
$stream = fopen("rar://" .
|
||||
dirname(__FILE__) . '/multi_broken.part1.rar' .
|
||||
"#file2.txt", "r", false,
|
||||
stream_context_create(array('rar'=>array('volume_callback'=>'resolve'))));
|
||||
$a = stream_get_contents($stream);
|
||||
echo strlen($a)." bytes, CRC ";
|
||||
echo int32_to_hex(crc32($a))."\n\n"; //you can confirm they're equal to those given by $e->getCrc()
|
||||
echo "Done.\n";
|
||||
--EXPECTF--
|
||||
17704 bytes, CRC F2C79881
|
||||
|
||||
Done.
|
||||
|
||||
69
tests/056.phpt
Normal file
69
tests/056.phpt
Normal file
@@ -0,0 +1,69 @@
|
||||
--TEST--
|
||||
RAR stream stat
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if(!extension_loaded("rar")) die("skip");
|
||||
--FILE--
|
||||
<?php
|
||||
$stream = fopen("rar://" .
|
||||
dirname(__FILE__) . '/latest_winrar.rar' .
|
||||
"#1.txt", "r");
|
||||
var_dump(fstat($stream));
|
||||
|
||||
echo "Done.\n";
|
||||
--EXPECTF--
|
||||
array(26) {
|
||||
[0]=>
|
||||
int(0)
|
||||
[1]=>
|
||||
int(0)
|
||||
[2]=>
|
||||
int(32)
|
||||
[3]=>
|
||||
int(1)
|
||||
[4]=>
|
||||
int(0)
|
||||
[5]=>
|
||||
int(0)
|
||||
[6]=>
|
||||
int(0)
|
||||
[7]=>
|
||||
int(5)
|
||||
[8]=>
|
||||
int(0)
|
||||
[9]=>
|
||||
int(1086948438)
|
||||
[10]=>
|
||||
int(0)
|
||||
[11]=>
|
||||
int(-1)
|
||||
[12]=>
|
||||
int(-1)
|
||||
["dev"]=>
|
||||
int(0)
|
||||
["ino"]=>
|
||||
int(0)
|
||||
["mode"]=>
|
||||
int(32)
|
||||
["nlink"]=>
|
||||
int(1)
|
||||
["uid"]=>
|
||||
int(0)
|
||||
["gid"]=>
|
||||
int(0)
|
||||
["rdev"]=>
|
||||
int(0)
|
||||
["size"]=>
|
||||
int(5)
|
||||
["atime"]=>
|
||||
int(0)
|
||||
["mtime"]=>
|
||||
int(1086948438)
|
||||
["ctime"]=>
|
||||
int(0)
|
||||
["blksize"]=>
|
||||
int(-1)
|
||||
["blocks"]=>
|
||||
int(-1)
|
||||
}
|
||||
Done.
|
||||
Reference in New Issue
Block a user