1/*
2  +----------------------------------------------------------------------+
3  | PHP Version 5                                                        |
4  +----------------------------------------------------------------------+
5  | Copyright (c) 1997-2009 The PHP Group                                |
6  +----------------------------------------------------------------------+
7  | This source file is subject to version 3.0 of the PHP license,       |
8  | that is bundled with this package in the file LICENSE, and is        |
9  | available through the world-wide-web at the following url:           |
10  | http://www.php.net/license/3_0.txt.                                  |
11  | If you did not receive a copy of the PHP license and are unable to   |
12  | obtain it through the world-wide-web, please send a note to          |
13  | license@php.net so we can mail you a copy immediately.               |
14  |                                                                      |
15  |                        **** WARNING ****                             |
16  |                                                                      |
17  | This module makes use of unRAR - free utility for RAR archives.      |
18  | Its license states that you MUST NOT use its code to develop         |
19  | a RAR (WinRAR) compatible archiver.                                  |
20  | Please, read unRAR license for full information.                     |
21  | unRAR & RAR copyrights are owned by Eugene Roshal                    |
22  +----------------------------------------------------------------------+
23  | Author: Antony Dovgal <tony@daylessday.org>                          |
24  | Author: Gustavo Lopes <cataphract@php.net>                           |
25  +----------------------------------------------------------------------+
26*/
27
28/* $Id$ */
29
30#ifdef HAVE_CONFIG_H
31#include "config.h"
32#endif
33
34#ifdef __cplusplus
35extern "C" {
36#endif
37
38#define _GNU_SOURCE
39#include <string.h>
40
41#ifdef PHP_WIN32
42# include <math.h>
43#endif
44
45#include <wchar.h>
46
47#include <php.h>
48#include <php_ini.h>
49#include <zend_exceptions.h>
50#include <ext/standard/info.h>
51#include <ext/spl/spl_exceptions.h>
52
53#if HAVE_RAR
54
55#include "php_rar.h"
56
57/* {{{ Function prototypes for functions with internal linkage */
58static void _rar_fix_wide(wchar_t *str, size_t max_size);
59static int _rar_unrar_volume_user_callback(char* dst_buffer,
60                                           zend_fcall_info *fci,
61                                           zend_fcall_info_cache *cache
62                                           TSRMLS_DC);
63static int _rar_make_userdata_fcall(zval *callable,
64                             zend_fcall_info *fci,
65                             zend_fcall_info_cache *cache TSRMLS_DC);
66/* }}} */
67
68/* {{{ Functions with external linkage */
69#if !defined(HAVE_STRNLEN) || !HAVE_STRNLEN
70size_t _rar_strnlen(const char *s, size_t maxlen) /* {{{ */
71{
72    char *r = memchr(s, '\0', maxlen);
73    return r ? r-s : maxlen;
74}
75/* }}} */
76#endif
77
78/* From unicode.cpp
79 * I can't use that one directy because it takes a const wchar, not wchar_t.
80 * And I shouldn't because it's not a public API.
81 */
82void _rar_wide_to_utf(const wchar_t *src, char *dest, size_t dest_size) /* {{{ */
83{
84    long dsize= (long) dest_size;
85    dsize--;
86    while (*src != 0 && --dsize >= 0) {
87        uint c =*(src++);
88        if (c < 0x80)
89            *(dest++) = (char) c;
90        else if (c < 0x800 && --dsize >= 0) {
91            *(dest++) = (char) (0xc0 | (c >> 6));
92            *(dest++) = (0x80 | (c & 0x3f));
93        }
94        else if (c < 0x10000 && (dsize -= 2) >= 0) {
95            *(dest++) = (char) (0xe0 | (c >> 12));
96            *(dest++) = (0x80 | ((c >> 6) & 0x3f));
97            *(dest++) =  (0x80 | (c & 0x3f));
98        }
99        else if (c < 0x200000 && (dsize -= 3) >= 0) {
100            *(dest++) = (char) (0xf0 | (c >> 18));
101            *(dest++) = (0x80 | ((c >> 12) & 0x3f));
102            *(dest++) = (0x80 | ((c >> 6) & 0x3f));
103            *(dest++) = (0x80 | (c & 0x3f));
104        }
105    }
106    *dest = 0;
107}
108/* }}} */
109
110/* idem */
111void _rar_utf_to_wide(const char *src, wchar_t *dest, size_t dest_size) /* {{{ */
112{
113    long dsize = (long) dest_size;
114    dsize--;
115    while (*src != 0) {
116        uint c = (unsigned char) *(src++),
117             d;
118        if (c < 0x80)
119            d = c;
120        else if ((c >> 5) == 6) {
121            if ((*src & 0xc0) != 0x80)
122                break;
123            d=((c & 0x1f) << 6)|(*src & 0x3f);
124            src++;
125        }
126        else if ((c>>4)==14) {
127            if ((src[0] & 0xc0) != 0x80 || (src[1] & 0xc0) != 0x80)
128                break;
129            d = ((c & 0xf) << 12) | ((src[0] & 0x3f) << 6) | (src[1] & 0x3f);
130            src += 2;
131        }
132        else if ((c>>3)==30) {
133            if ((src[0] & 0xc0) != 0x80 || (src[1] & 0xc0) != 0x80 || (src[2] & 0xc0) != 0x80)
134                break;
135            d = ((c & 7) << 18) | ((src[0] & 0x3f) << 12) | ((src[1] & 0x3f) << 6) | (src[2] & 0x3f);
136            src += 3;
137        }
138        else
139            break;
140        if (--dsize < 0)
141            break;
142        if (d > 0xffff) {
143            if (--dsize < 0 || d > 0x10ffff)
144                break;
145            *(dest++) = (wchar_t) (((d - 0x10000) >> 10) + 0xd800);
146            *(dest++) = (d & 0x3ff) + 0xdc00;
147        }
148        else
149            *(dest++) = (wchar_t) d;
150    }
151    *dest = 0;
152}
153/* }}} */
154
155void _rar_destroy_userdata(rar_cb_user_data *udata) /* {{{ */
156{
157    assert(udata != NULL);
158
159    if (udata->password != NULL) {
160        efree(udata->password);
161    }
162
163    if (udata->callable != NULL)
164        zval_ptr_dtor(&udata->callable);
165
166    udata->password = NULL;
167    udata->callable = NULL;
168}
169/* }}} */
170
171int _rar_find_file(struct RAROpenArchiveDataEx *open_data, /* IN */
172                   const char *const utf_file_name, /* IN */
173                   rar_cb_user_data *cb_udata, /* IN, must be managed outside */
174                   void **arc_handle, /* OUT: where to store rar archive handle  */
175                   int *found, /* OUT */
176                   struct RARHeaderDataEx *header_data /* OUT, can be null */
177                   ) /* {{{ */
178{
179    wchar_t *file_name = NULL;
180    size_t utf_file_name_len = strlen(utf_file_name);
181    int ret;
182
183    file_name = ecalloc(utf_file_name_len + 1, sizeof *file_name);
184    _rar_utf_to_wide(utf_file_name, file_name, utf_file_name_len + 1);
185    ret = _rar_find_file_w(open_data, file_name, cb_udata, arc_handle, found,
186        header_data);
187    efree(file_name);
188    return ret;
189}
190/* }}} */
191
192/* WARNING: It's the caller who must close the archive and manage the lifecycle
193of cb_udata (must be valid while the archive is opened). */
194/*
195 * This function opens a RAR file and looks for the file with the
196 * name utf_file_name.
197 * If the operation is sucessful, arc_handle is populated with the RAR file
198 * handle, found is set to TRUE if the file is found and FALSE if it is not
199 * found; additionaly, the optional header_data is populated with the first
200 * header that corresponds to the request file. If the file is not found and
201 * header_data is specified, its values are undefined.
202 * Note that even when the file is not found, the caller must still close
203 * the archive.
204 */
205int _rar_find_file_w(struct RAROpenArchiveDataEx *open_data, /* IN */
206                     const wchar_t *const file_name, /* IN */
207                     rar_cb_user_data *cb_udata, /* IN, must be managed outside */
208                     void **arc_handle, /* OUT: where to store rar archive handle  */
209                     int *found, /* OUT */
210                     struct RARHeaderDataEx *header_data /* OUT, can be null */
211                     ) /* {{{ */
212{
213    int                     result,
214                            process_result;
215    struct RARHeaderDataEx  *used_header_data;
216    int                     retval = 0; /* success in rar parlance */
217
218    assert(open_data != NULL);
219    assert(file_name != NULL);
220    assert(arc_handle != NULL);
221    assert(found != NULL);
222    *found = FALSE;
223    *arc_handle = NULL;
224    used_header_data = header_data != NULL ?
225        header_data :
226        ecalloc(1, sizeof *used_header_data);
227
228    *arc_handle = RAROpenArchiveEx(open_data);
229    if (*arc_handle == NULL) {
230        retval = open_data->OpenResult;
231        goto cleanup;
232    }
233    RARSetCallback(*arc_handle, _rar_unrar_callback, (LPARAM) cb_udata);
234
235    while ((result = RARReadHeaderEx(*arc_handle, used_header_data)) == 0) {
236#if WCHAR_MAX > 0xffff
237            _rar_fix_wide(used_header_data->FileNameW, NM);
238#endif
239
240        if (wcsncmp(used_header_data->FileNameW, file_name, NM) == 0) {
241            *found = TRUE;
242            goto cleanup;
243        } else {
244            process_result = RARProcessFile(*arc_handle, RAR_SKIP, NULL, NULL);
245        }
246        if (process_result != 0) {
247            retval = process_result;
248            goto cleanup;
249        }
250    }
251
252    if (result != 0 && result != 1) {
253        /* 0 indicates success, 1 indicates normal end of file */
254        retval = result;
255        goto cleanup;
256    }
257
258cleanup:
259    if (header_data == NULL)
260        efree(used_header_data);
261
262    return retval;
263}
264/* }}} */
265
266int _rar_find_file_p(struct RAROpenArchiveDataEx *open_data, /* IN */
267                     size_t position, /* IN */
268                     rar_cb_user_data *cb_udata, /* IN, must be managed outside */
269                     void **arc_handle, /* OUT: where to store rar archive handle  */
270                     int *found, /* OUT */
271                     struct RARHeaderDataEx *header_data /* OUT, can be null */
272                     ) /* {{{ */
273{
274    int                     result,
275                            process_result;
276    struct RARHeaderDataEx  *used_header_data;
277    int                     retval = 0; /* success in rar parlance */
278    size_t                  curpos = 0;
279
280    assert(open_data != NULL);
281    assert(arc_handle != NULL);
282    assert(found != NULL);
283    *found = FALSE;
284    *arc_handle = NULL;
285    used_header_data = header_data != NULL ?
286        header_data :
287        ecalloc(1, sizeof *used_header_data);
288
289    *arc_handle = RAROpenArchiveEx(open_data);
290    if (*arc_handle == NULL) {
291        retval = open_data->OpenResult;
292        goto cleanup;
293    }
294    RARSetCallback(*arc_handle, _rar_unrar_callback, (LPARAM) cb_udata);
295
296    while ((result = RARReadHeaderEx(*arc_handle, used_header_data)) == 0) {
297        /* skip entries that were split before with incrementing current pos */
298        if ((used_header_data->Flags & 0x01U) || (curpos++ != position)) {
299            process_result = RARProcessFile(*arc_handle, RAR_SKIP, NULL, NULL);
300        } else {
301            *found = TRUE;
302            goto cleanup;
303        }
304        if (process_result != 0) {
305            retval = process_result;
306            goto cleanup;
307        }
308    }
309
310    if (result != 0 && result != 1) {
311        /* 0 indicates success, 1 indicates normal end of file */
312        retval = result;
313        goto cleanup;
314    }
315
316cleanup:
317    if (header_data == NULL)
318        efree(used_header_data);
319
320    return retval;
321}
322
323/* An unRAR callback.
324 * Processes requests for passwords and missing volumes
325 * If there is (userland) volume find callback specified, try to use that
326 * callback to retrieve the name of the missing volume. Otherwise, or if
327 * the volume find callback returns null, cancel the operation. */
328int CALLBACK _rar_unrar_callback(UINT msg, LPARAM UserData, LPARAM P1, LPARAM P2) /* {{{ */
329{
330    rar_cb_user_data *userdata = (rar_cb_user_data*)  UserData;
331    TSRMLS_FETCH();
332
333    if (msg == UCM_NEEDPASSWORD) {
334        /* user data is the password or null if none */
335        char *password = userdata->password;
336
337        if (password == NULL || password[0] == '\0') {
338            /*php_error_docref(NULL TSRMLS_CC, E_WARNING,
339                "Password needed, but it has not been specified");*/
340            return -1;
341        }
342        else {
343            strncpy((char *) P1, password, (size_t) P2);
344            assert((size_t) P2 > 0);
345            ((char *) P1)[(size_t) P2 - 1] = '\0';
346        }
347    }
348    else if (msg == UCM_CHANGEVOLUME) {
349        if (((int) P2) == RAR_VOL_ASK) {
350            int ret, called_cb = 0;
351            if (userdata->callable == NULL) {
352                /* if there's no callback, abort */
353                ret = -1;
354            }
355            else {
356                zend_fcall_info fci;
357                zend_fcall_info_cache cache;
358                /* make_userdata_fcall and volume_user_callback are chatty */
359                if (_rar_make_userdata_fcall(userdata->callable, &fci, &cache
360                        TSRMLS_CC) == SUCCESS) {
361                    ret = _rar_unrar_volume_user_callback(
362                        (char*) P1, &fci, &cache TSRMLS_CC);
363                    called_cb = 1;
364                }
365                else {
366                    ret = -1;
367                }
368
369            }
370
371            /* always a warning, never an exception here */
372            if (ret == -1 && !called_cb)
373                php_error_docref(NULL TSRMLS_CC, E_WARNING,
374                    "Volume %s was not found", (char*) P1);
375
376            return ret;
377        }
378    }
379
380    return 0;
381}
382/* }}} */
383
384PHP_FUNCTION(rar_bogus_ctor) /* {{{ */
385{
386    /* This exception should not be thrown. The point is to add this as
387     * a class constructor and make it private. This code would be able to
388     * run only if the constructor were made public */
389    zend_throw_exception(NULL,
390        "An object of this type cannot be created with the new operator.",
391        0 TSRMLS_CC);
392}
393/* }}} */
394
395PHP_FUNCTION(rar_wrapper_cache_stats) /* {{{ */
396{
397    char *result = NULL;
398    int len;
399
400    if (zend_parse_parameters_none() == FAILURE)
401        return;
402
403    len = spprintf(&result, 0, "%u/%u (hits/misses)",
404        RAR_G(contents_cache).hits, RAR_G(contents_cache).misses);
405
406    RETURN_STRINGL(result, len, 0);
407}
408/* }}} */
409/* }}} */
410
411/* {{{ Functions with internal linkage */
412/*
413 * Only relevant when sizeof(wchar_t) > 2 (so not windows).
414 * Removes the characters use value if > 0x10ffff; these are not
415 * valid UTF characters.
416 */
417
418static void _rar_fix_wide(wchar_t *str, size_t max_size) /* {{{ */
419{
420    wchar_t *write,
421            *read,
422            *max_fin;
423    max_fin = str + max_size;
424    for (write = str, read = str; *read != L'\0' && read != max_fin; read++) {
425        if ((unsigned) *read <= 0x10ffff)
426            *(write++) = *read;
427    }
428    *write = L'\0';
429}
430/* }}} */
431
432/* called from the RAR callback; calls a user callback in case a volume was
433 * not found
434 * This function sends messages instead of calling _rar_handle_ext_error
435 * because, in case we're using exceptions, we want to let an exception with
436 * error code ERAR_EOPEN to be thrown.
437 */
438static int _rar_unrar_volume_user_callback(char* dst_buffer,
439                                           zend_fcall_info *fci,
440                                           zend_fcall_info_cache *cache
441                                           TSRMLS_DC) /* {{{ */
442{
443    zval *failed_vol,
444         *retval_ptr = NULL,
445         **params;
446    int  ret = -1;
447
448    MAKE_STD_ZVAL(failed_vol);
449    ZVAL_STRING(failed_vol, dst_buffer, 1);
450    params = &failed_vol;
451    fci->retval_ptr_ptr = &retval_ptr;
452    fci->params = &params;
453    fci->param_count = 1;
454
455    if (zend_call_function(fci, cache TSRMLS_CC) != SUCCESS ||
456            fci->retval_ptr_ptr == NULL ||
457            *fci->retval_ptr_ptr == NULL) {
458        php_error_docref(NULL TSRMLS_CC, E_WARNING,
459            "Failure to call volume find callback");
460        goto cleanup;
461    }
462
463    assert(*fci->retval_ptr_ptr == retval_ptr);
464    if (Z_TYPE_P(retval_ptr) == IS_NULL) {
465        /* let return -1 */
466    }
467    else if (Z_TYPE_P(retval_ptr) == IS_STRING) {
468        char *filename = Z_STRVAL_P(retval_ptr);
469        char resolved_path[MAXPATHLEN];
470        size_t resolved_len;
471
472        if (OPENBASEDIR_CHECKPATH(filename)) {
473            goto cleanup;
474        }
475        if (!expand_filepath(filename, resolved_path TSRMLS_CC)) {
476            php_error_docref(NULL TSRMLS_CC, E_WARNING,
477                "Cound not expand filename %s", filename);
478            goto cleanup;
479        }
480
481        resolved_len = _rar_strnlen(resolved_path, MAXPATHLEN);
482        /* dst_buffer size is NM; first condition won't happen short of a bug
483         * in expand_filepath */
484        if (resolved_len == MAXPATHLEN || resolved_len > NM - 1) {
485            php_error_docref(NULL TSRMLS_CC, E_WARNING,
486                "Resolved path is too big for the unRAR library");
487            goto cleanup;
488        }
489
490        strncpy(dst_buffer, resolved_path, NM);
491        dst_buffer[NM - 1] = '\0';
492        ret = 1; /* try this new filename */
493    }
494    else {
495        php_error_docref(NULL TSRMLS_CC, E_WARNING,
496            "Wrong type returned by volume find callback, "
497            "expected string or NULL");
498        /* let return -1 */
499    }
500
501cleanup:
502    zval_ptr_dtor(&failed_vol);
503    if (retval_ptr != NULL)
504        zval_ptr_dtor(&retval_ptr);
505    return ret;
506}
507/* }}} */
508
509static int _rar_make_userdata_fcall(zval *callable,
510                             zend_fcall_info *fci,
511                             zend_fcall_info_cache *cache TSRMLS_DC) /* {{{ */
512{
513    char *error = NULL;
514    assert(callable != NULL);
515    assert(fci != NULL);
516    assert(cache != NULL);
517
518    *cache = empty_fcall_info_cache;
519
520#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION == 2
521    if (zend_fcall_info_init(callable, fci, cache TSRMLS_CC) != SUCCESS) {
522        php_error_docref(NULL TSRMLS_CC, E_WARNING,
523            "The RAR file was not opened in rar_open/RarArchive::open with a "
524            "valid callback.", error);
525        return FAILURE;
526    }
527    else {
528        return SUCCESS;
529    }
530#else
531    if (zend_fcall_info_init(callable, IS_CALLABLE_STRICT, fci, cache, NULL,
532            &error TSRMLS_CC) == SUCCESS) {
533        if (error) {
534            php_error_docref(NULL TSRMLS_CC, E_STRICT,
535                "The RAR file was not opened with a strictly valid callback (%s)",
536                error);
537            efree(error);
538        }
539        return SUCCESS;
540    }
541    else {
542        if (error) {
543            php_error_docref(NULL TSRMLS_CC, E_STRICT,
544                "The RAR file was not opened with a valid callback (%s)",
545                error);
546            efree(error);
547        }
548        return FAILURE;
549    }
550#endif
551
552}
553/* }}} */
554
555/* }}} */
556
557#ifdef COMPILE_DL_RAR
558ZEND_GET_MODULE(rar)
559#endif
560
561/* {{{ arginfo */
562ZEND_BEGIN_ARG_INFO_EX(arginfo_rar_open, 0, 0, 1)
563    ZEND_ARG_INFO(0, filename)
564    ZEND_ARG_INFO(0, password)
565    ZEND_ARG_INFO(0, volume_callback)
566ZEND_END_ARG_INFO()
567
568ZEND_BEGIN_ARG_INFO_EX(arginfo_rar_void_archmeth, 0, 0, 1)
569#if 0 /* don't turn on type hinting yet */
570    ZEND_ARG_OBJ_INFO(0, rarfile, RarArchive, 0)
571#else
572    ZEND_ARG_INFO(0, rarfile)
573#endif
574ZEND_END_ARG_INFO()
575
576ZEND_BEGIN_ARG_INFO_EX(arginfo_rar_entry_get, 0, 0, 2)
577#if 0 /* don't turn on type hinting yet */
578    ZEND_ARG_OBJ_INFO(0, rarfile, RarArchive, 0)
579#else
580    ZEND_ARG_INFO(0, rarfile)
581#endif
582    ZEND_ARG_INFO(0, filename)
583ZEND_END_ARG_INFO()
584
585ZEND_BEGIN_ARG_INFO_EX(arginfo_rar_allow_broken_set, 0, 0, 2)
586#if 0 /* don't turn on type hinting yet */
587    ZEND_ARG_OBJ_INFO(0, rarfile, RarArchive, 0)
588#else
589    ZEND_ARG_INFO(0, rarfile)
590#endif
591    ZEND_ARG_INFO(0, allow_broken)
592ZEND_END_ARG_INFO()
593
594ZEND_BEGIN_ARG_INFO(arginfo_rar_wrapper_cache_stats, 0)
595ZEND_END_ARG_INFO()
596/* }}} */
597
598/* {{{ rar_functions[]
599 *
600 */
601static zend_function_entry rar_functions[] = {
602    PHP_FE(rar_open,                arginfo_rar_open)
603    PHP_FE(rar_list,                arginfo_rar_void_archmeth)
604    PHP_FE(rar_entry_get,           arginfo_rar_entry_get)
605    PHP_FE(rar_solid_is,            arginfo_rar_void_archmeth)
606    PHP_FE(rar_comment_get,         arginfo_rar_void_archmeth)
607    PHP_FE(rar_broken_is,           arginfo_rar_void_archmeth)
608    PHP_FE(rar_allow_broken_set,    arginfo_rar_allow_broken_set)
609    PHP_FE(rar_close,               arginfo_rar_void_archmeth)
610    PHP_FE(rar_wrapper_cache_stats, arginfo_rar_wrapper_cache_stats)
611    {NULL, NULL, NULL}
612};
613/* }}} */
614
615/* {{{ Globals' related activities */
616ZEND_DECLARE_MODULE_GLOBALS(rar);
617
618static int _rar_array_apply_remove_first(void *pDest TSRMLS_DC)
619{
620    return (ZEND_HASH_APPLY_STOP | ZEND_HASH_APPLY_REMOVE);
621}
622
623/* caller should increment zval refcount before calling this */
624static void _rar_contents_cache_put(const char *key,
625                                    uint key_len,
626                                    zval *zv TSRMLS_DC)
627{
628    rar_contents_cache *cc = &RAR_G(contents_cache);
629    int cur_size;
630
631    cur_size = zend_hash_num_elements(cc->data);
632    if (cur_size == cc->max_size) {
633        zend_hash_apply(cc->data, _rar_array_apply_remove_first TSRMLS_CC);
634        assert(zend_hash_num_elements(cc->data) == cur_size - 1);
635    }
636    zval_add_ref(&zv);
637    zend_hash_update(cc->data, key, key_len, &zv, sizeof(zv), NULL);
638}
639
640static zval *_rar_contents_cache_get(const char *key,
641                                     uint key_len TSRMLS_DC)
642{
643    rar_contents_cache *cc = &RAR_G(contents_cache);
644    zval **element = NULL;
645    zend_hash_find(cc->data, key, key_len, (void **) &element);
646
647    if (element != NULL) {
648        cc->hits++;
649        zval_add_ref(element);
650        return *element;
651    }
652    else {
653        cc->misses++;
654        return NULL;
655    }
656}
657
658/* ZEND_MODULE_GLOBALS_CTOR_D declares it receiving zend_rar_globals*,
659 * which is incompatible; once cast into ts_allocate_ctor by the macro,
660 * ZEND_INIT_MODULE_GLOBALS, it cannot (per the spec) be used. */
661static void ZEND_MODULE_GLOBALS_CTOR_N(rar)(void *arg TSRMLS_DC) /* {{{ */
662{
663    zend_rar_globals *rar_globals = arg;
664    rar_globals->contents_cache.max_size = 5; /* TODO make configurable */
665    rar_globals->contents_cache.hits = 0;
666    rar_globals->contents_cache.misses = 0;
667    rar_globals->contents_cache.put = _rar_contents_cache_put;
668    rar_globals->contents_cache.get = _rar_contents_cache_get;
669    rar_globals->contents_cache.data =
670        pemalloc(sizeof *rar_globals->contents_cache.data, 1);
671    zend_hash_init(rar_globals->contents_cache.data,
672        rar_globals->contents_cache.max_size, NULL,
673        ZVAL_PTR_DTOR, 1);
674}
675/* }}} */
676
677static void ZEND_MODULE_GLOBALS_DTOR_N(rar)(void *arg TSRMLS_DC) /* {{{ */
678{
679    zend_rar_globals *rar_globals = arg;
680    zend_hash_destroy(rar_globals->contents_cache.data);
681    pefree(rar_globals->contents_cache.data, 1);
682}
683/* }}} */
684/* }}} */
685
686/* {{{ ZEND_MODULE_STARTUP */
687ZEND_MODULE_STARTUP_D(rar)
688{
689    minit_rararch(TSRMLS_C);
690    minit_rarentry(TSRMLS_C);
691    minit_rarerror(TSRMLS_C);
692
693    /* This doesn't work, it tries to call the destructor after the
694     * module has been unloaded. This information is in the zend_module_entry
695     * instead; that information is correctly used before the module is
696     * unloaded */
697    /* ZEND_INIT_MODULE_GLOBALS(rar, ZEND_MODULE_GLOBALS_CTOR_N(rar),
698        ZEND_MODULE_GLOBALS_DTOR_N(rar)); */
699
700    php_register_url_stream_wrapper("rar", &php_stream_rar_wrapper TSRMLS_CC);
701
702    REGISTER_LONG_CONSTANT("RAR_HOST_MSDOS",    HOST_MSDOS, CONST_CS | CONST_PERSISTENT);
703    REGISTER_LONG_CONSTANT("RAR_HOST_OS2",      HOST_OS2,   CONST_CS | CONST_PERSISTENT);
704    REGISTER_LONG_CONSTANT("RAR_HOST_WIN32",    HOST_WIN32, CONST_CS | CONST_PERSISTENT);
705    REGISTER_LONG_CONSTANT("RAR_HOST_UNIX",     HOST_UNIX,  CONST_CS | CONST_PERSISTENT);
706    REGISTER_LONG_CONSTANT("RAR_HOST_MACOS",    HOST_MACOS, CONST_CS | CONST_PERSISTENT);
707    REGISTER_LONG_CONSTANT("RAR_HOST_BEOS",     HOST_BEOS,  CONST_CS | CONST_PERSISTENT);
708    /* PHP < 5.3 doesn't have the PHP_MAXPATHLEN constant */
709#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 3
710    REGISTER_LONG_CONSTANT("RAR_MAXPATHLEN",    MAXPATHLEN, CONST_CS | CONST_PERSISTENT);
711#endif
712    return SUCCESS;
713}
714/* }}} */
715
716/* {{{ ZEND_MODULE_DEACTIVATE */
717ZEND_MODULE_DEACTIVATE_D(rar)
718{
719    /* clean cache on request shutdown */
720    zend_hash_clean(RAR_G(contents_cache).data);
721
722    return SUCCESS;
723}
724/* }}} */
725
726/* {{{ ZEND_MODULE_INFO */
727ZEND_MODULE_INFO_D(rar)
728{
729    char version[256];
730    char api_version[256];
731
732    php_info_print_table_start();
733    php_info_print_table_header(2, "RAR support", "enabled");
734    php_info_print_table_row(2, "RAR EXT version", PHP_RAR_VERSION);
735
736#if RARVER_BETA != 0
737    sprintf(version,"%d.%02d beta%d patch%d %d-%02d-%02d", RARVER_MAJOR,
738        RARVER_MINOR, RARVER_BETA, RARVER_PATCH, RARVER_YEAR, RARVER_MONTH,
739        RARVER_DAY);
740#else
741    sprintf(version,"%d.%02d patch%d %d-%02d-%02d", RARVER_MAJOR, RARVER_MINOR,
742        RARVER_PATCH, RARVER_YEAR, RARVER_MONTH, RARVER_DAY);
743#endif
744
745    sprintf(api_version,"%d extension %d", RAR_DLL_VERSION,
746        RAR_DLL_EXT_VERSION);
747
748    php_info_print_table_row(2, "UnRAR version", version);
749    php_info_print_table_row(2, "UnRAR API version", api_version);
750    php_info_print_table_end();
751}
752/* }}} */
753
754/* {{{ rar_module_entry
755 */
756zend_module_entry rar_module_entry = {
757    STANDARD_MODULE_HEADER,
758    "rar",
759    rar_functions,
760    ZEND_MODULE_STARTUP_N(rar),
761    /* ZEND_MODULE_SHUTDOWN_N(rar), */
762    NULL,
763    /* ZEND_MODULE_ACTIVATE_N(rar), */
764    NULL,
765    ZEND_MODULE_DEACTIVATE_N(rar),
766    ZEND_MODULE_INFO_N(rar),
767    PHP_RAR_VERSION,
768    ZEND_MODULE_GLOBALS(rar),
769    ZEND_MODULE_GLOBALS_CTOR_N(rar),
770    ZEND_MODULE_GLOBALS_DTOR_N(rar),
771    NULL, /* post_deactivate_func */
772    STANDARD_MODULE_PROPERTIES_EX,
773};
774/* }}} */
775
776#endif /* HAVE_RAR */
777
778#ifdef __cplusplus
779}
780#endif
781
782/*
783 * Local variables:
784 * tab-width: 4
785 * c-basic-offset: 4
786 * End:
787 * vim600: noet sw=4 ts=4 fdm=marker
788 * vim<600: noet sw=4 ts=4
789 */
790