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:
cataphract
2010-04-28 15:42:06 +00:00
parent f92f731da3
commit a88a995ca0
15 changed files with 1352 additions and 50 deletions

60
extflow.txt Normal file
View 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

View File

@@ -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
View File

@@ -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 = &params;
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,
};
/* }}} */

View File

@@ -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

View File

@@ -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);

View File

@@ -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
View 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.

View File

@@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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.