/* +----------------------------------------------------------------------+ | PHP Version 4 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2002 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.02 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available at through the world-wide-web at | | http://www.php.net/license/2_02.txt. | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Sara Golemon | +----------------------------------------------------------------------+ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #if WITH_CVSCLIENT #include "php_cvsclient.h" #include "php_globals.h" #include "php_network.h" #include "ext/standard/url.h" #include "ext/standard/info.h" #include "ext/standard/php_string.h" #include "ext/standard/php_smart_str.h" #ifndef PHP_WIN32 #include #endif #include /* ********************* */ /* CVS pserver interface */ /* ********************* */ /* {{{ php_cvsclient_do_connect */ static php_stream * php_cvsclient_do_connect(char *path, int options, php_stream_context *context, php_url **presource TSRMLS_DC) { php_stream *stream = NULL; php_url *resource = NULL; resource = php_url_parse((char *) path); if (!resource || !resource->scheme || !resource->host) { goto connect_errexit; } if (strcasecmp("cvs", resource->scheme) && strcasecmp("cvs.diff", resource->scheme)) { goto connect_errexit; } /* use port 2401 if one wasn't specified */ if (resource->port == 0) { resource->port = 2401; } stream = php_stream_sock_open_host(resource->host, resource->port, SOCK_STREAM, NULL, 0); if (stream == NULL) { goto connect_errexit; } php_stream_context_set(stream, context); php_stream_notify_info(context, PHP_STREAM_NOTIFY_CONNECT, NULL, 0); if (presource) { *presource = resource; } else { php_url_free(resource); } return stream; connect_errexit: if (resource) { php_url_free(resource); } if (stream) { php_stream_close(stream); } return NULL; } /* }}} */ /* {{{ php_cvsclient_encode */ static char * php_cvsclient_encode(const char *orig) { unsigned char *enc; int i; enc = estrdup(orig); for(i=0; i= 32 && enc[i] <= 127) { enc[i] = cvs_encode[enc[i] - 32]; } } return enc; } /* }}} */ /* {{{ php_cvsclient_authenticate */ static int php_cvsclient_authenticate(php_stream *stream, const char *cvsroot, const char *user, const char *pass TSRMLS_DC) { char *encpw; char response[128]; encpw = php_cvsclient_encode(pass); php_stream_printf(stream TSRMLS_CC, "BEGIN AUTH REQUEST\n" "%s\n" "%s\n" "A%s\n" "END AUTH REQUEST\n", cvsroot, user, encpw); efree(encpw); /* Who do you love? */ if (php_stream_gets(stream, response, sizeof(response)-1) == NULL) { return FAILURE; } if (strncmp(response, "I LOVE YOU", strlen("I LOVE YOU"))) { return FAILURE; } return SUCCESS; } /* }}} */ /* {{{ php_cvsclient_negotiate */ static int php_cvsclient_negotiate(php_stream *stream, const char *cvsroot TSRMLS_DC) { php_cvsclient_requests *verbs = cvsclient_requests; char response[4096]; int requests = 0, i; php_stream_printf(stream TSRMLS_CC, "Root %s\n" "Valid-responses %s\n" "valid-requests\n", cvsroot, "Valid-requests New-entry Updated Created Update-existing Merged Rcs-diff Patched Mode Mod-time Checksum " "Copy-file Removed Remove-entry Set-static-directory Clear-static-directory Set-sticky Clear-sticky Template Set-checkin-prog " "Set-update-prog Notified Module-expansion Wrapper-rcsOption ok error Checked-in M E F MT"); if (php_stream_gets(stream, response, sizeof(response)-1) == NULL) { return 0; } for(i=0; irequest && verbs->label) { if (strstr(response, verbs->label)) { requests |= verbs->request; } verbs++; } return requests; } /* }}} */ /* ********************** */ /* Wrapper Implementation */ /* ********************** */ static char *php_cvsclient_get_url_param(const char *query, const char *param TSRMLS_DC) { int query_len, param_len; const char *p, *e; char *param_dup, *val; if (!query || !param) { return NULL; } query_len = strlen(query); param_len = strlen(param); if (!query_len || !param_len) { return NULL; } param_dup = emalloc(param_len + 3); memcpy(param_dup + 1, param, param_len); param_dup[0] = '&'; param_dup[param_len+1] = '='; param_dup[param_len+2] = '\0'; if (strncasecmp(query, param_dup + 1, param_len + 1) == 0) { /* param found at start of string */ p = query + param_len + 1; } else { p = strstr(query, param_dup); if (p) { /* param found further in */ p += param_len + 2; } } if (!p) { /* parameter not found */ efree(param_dup); return NULL; } e = strchr(p, '&'); if (!e) { /* last parameter */ e = p + strlen(p); } val = estrndup(p, e - p); efree(param_dup); return val; } /* {{{ php_stream_url_wrap_cvsclient */ static php_stream * php_stream_url_wrap_cvsclient(php_stream_wrapper *wrapper, char *path, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC) { php_stream *stream = NULL, *memstream = NULL; php_url *resource = NULL; char *module = NULL; char *cvsroot = NULL; char *filepath = NULL; char *filename, *p; char response[4096]; int valid_requests, i; long filesize; zval *wrapperdata = NULL; zval **tmpzval; int req_options = 0; if (strpbrk(mode, "awx+")) { php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "CVS wrapper does not support writeable connections (yet)."); return NULL; } /* Connect */ stream = php_cvsclient_do_connect(path, options, context, &resource TSRMLS_CC); if (!stream || !resource->path) { goto wrap_errexit; } p = strchr(resource->path + 1, '/'); if (p) { cvsroot = estrndup(resource->path, p - resource->path); filename = p; p = strchr(filename + 1, '/'); if (p) { module = estrndup(filename, p - filename); filename = p; p = strrchr(filename + 1, '/'); if (p) { filepath = estrndup(filename, p - filename); filename = p + 1; } else { filename++; } } else { goto wrap_errexit; } } else { goto wrap_errexit; } /* Authenticate */ if (resource && resource->user && resource->pass && php_cvsclient_authenticate(stream, cvsroot, resource->user, resource->pass TSRMLS_CC) == FAILURE) { goto wrap_errexit; } /* Negotiate */ if ((valid_requests = php_cvsclient_negotiate(stream, cvsroot TSRMLS_CC)) == 0) { goto wrap_errexit; } /* Request */ if (resource->query && strlen(resource->query)) { char *rev; rev = php_cvsclient_get_url_param(resource->query, "r" TSRMLS_CC); if (rev) { /* Revision tag provided in URL (overrides context option) */ php_stream_printf(stream TSRMLS_CC, "Argument -r\nArgument %s\n", rev); efree(rev); req_options |= CVSCLIENT_HAVE_REVISION; } } if (((req_options & CVSCLIENT_HAVE_REVISION) == 0) && context && php_stream_context_get_option(context, "cvs", "revision", &tmpzval) == SUCCESS) { SEPARATE_ZVAL(tmpzval); convert_to_string_ex(tmpzval); php_stream_printf(stream TSRMLS_CC, "Argument -r\nArgument %s\n", Z_STRVAL_PP(tmpzval)); zval_ptr_dtor(tmpzval); req_options |= CVSCLIENT_HAVE_REVISION; } php_stream_printf(stream TSRMLS_CC, "Argument %s\n" "Directory .\n" "%s%s%s\n" "update\n", filename, cvsroot, module, filepath ? filepath : ""); efree(cvsroot); cvsroot = NULL; efree(module); module = NULL; if (filepath) { efree(filepath); filepath = NULL; } /* Parse response */ ALLOC_INIT_ZVAL(wrapperdata); array_init(wrapperdata); while (php_stream_gets(stream, response, sizeof(response)-1) != NULL) { if (strncasecmp(response, "error", 5) == 0) { goto wrap_errexit; } if (strncasecmp(response, "mod-time ", 9) == 0) { add_assoc_string(wrapperdata, "mod-time", response + 9, 1); } if ((strlen(response) > strlen(filename) + 4) && response[0] == '/' && strncmp(response + 1, filename, strlen(filename)) == 0 && response[strlen(filename)+1] == '/') { p = response + strlen(filename) + 2; p = strchr(p, '/'); if (p) { *p = '\0'; p = response + strlen(filename) + 2; add_assoc_string(wrapperdata, "revision", p, 1); } } /* TODO: Parse other elements into wrapperdata */ for(i=0; i (sizeof(response) - 1)) ? sizeof(response)-1 : filesize); php_stream_write(memstream, response, read); filesize -= read; if (read <= 0) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error reading remote file."); goto wrap_errexit; } } php_stream_rewind(memstream); php_stream_close(stream); memstream->wrapperdata = wrapperdata; php_url_free(resource); resource = NULL; return memstream; wrap_errexit: if (filepath) { efree(filepath); } if (module) { efree(module); } if (cvsroot) { efree(cvsroot); } if (wrapperdata) { zval_ptr_dtor(&wrapperdata); } if (resource) { php_url_free(resource); } if (stream) { php_stream_close(stream); } if (memstream) { php_stream_close(memstream); } return NULL; } /* }}} */ /* {{{ php_stream_cvsclient_stat */ static int php_stream_cvsclient_stat(php_stream_wrapper *wrapper, php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC) { /* For now, we return with a failure code to prevent the underlying * file's details from being used instead. */ return -1; } /* }}} */ static php_stream_wrapper_ops cvsclient_stream_wops = { php_stream_url_wrap_cvsclient, NULL, /* stream_close */ php_stream_cvsclient_stat, NULL, /* stat_url */ NULL, /* opendir */ "CVS" #if PHP_MAJOR_VERSION >= 5 ,NULL /* unlink */ #endif }; php_stream_wrapper php_stream_cvsclient_wrapper = { &cvsclient_stream_wops, NULL, 1 /* is_url */ }; /* {{{ php_stream_url_wrap_cvsclient_diff */ static php_stream * php_stream_url_wrap_cvsclient_diff(php_stream_wrapper *wrapper, char *path, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC) { php_stream *stream = NULL, *memstream = NULL; php_url *resource = NULL; char *module = NULL; char *cvsroot = NULL; char *filepath = NULL; char *filename, *p; char response[4096]; int valid_requests, unified = 0; zval *wrapperdata = NULL, *revision_list = NULL; zval **tmpzval; char *rev1 = NULL, *rev2 = NULL, *type = NULL; if (strpbrk(mode, "awx+")) { php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Writing does not make sense for a CVS diff."); return NULL; } /* Connect */ stream = php_cvsclient_do_connect(path, options, context, &resource TSRMLS_CC); if (!stream || !resource->path) { goto wrap_errexit; } p = strchr(resource->path + 1, '/'); if (p) { cvsroot = estrndup(resource->path, p - resource->path); filename = p; p = strchr(filename + 1, '/'); if (p) { module = estrndup(filename, p - filename); filename = p; p = strrchr(filename + 1, '/'); if (p) { filepath = estrndup(filename, p - filename); filename = p + 1; } else { filename++; } } else { goto wrap_errexit; } } else { goto wrap_errexit; } /* Authenticate */ if (resource && resource->user && resource->pass && php_cvsclient_authenticate(stream, cvsroot, resource->user, resource->pass TSRMLS_CC) == FAILURE) { goto wrap_errexit; } /* Negotiate */ if ((valid_requests = php_cvsclient_negotiate(stream, cvsroot TSRMLS_CC)) == 0) { goto wrap_errexit; } if (resource->query && strlen(resource->query)) { rev1 = php_cvsclient_get_url_param(resource->query, "r1" TSRMLS_CC); rev2 = php_cvsclient_get_url_param(resource->query, "r2" TSRMLS_CC); type = php_cvsclient_get_url_param(resource->query, "type" TSRMLS_CC); } if (!rev1 && context && (php_stream_context_get_option(context, "cvs", "revision1", &tmpzval) == SUCCESS || php_stream_context_get_option(context, "cvs", "revision", &tmpzval) == SUCCESS)) { SEPARATE_ZVAL(tmpzval); convert_to_string_ex(tmpzval); rev1 = estrndup(Z_STRVAL_PP(tmpzval), Z_STRLEN_PP(tmpzval)); zval_ptr_dtor(tmpzval); } if (!rev2 && context && php_stream_context_get_option(context, "cvs", "revision2", &tmpzval) == SUCCESS) { SEPARATE_ZVAL(tmpzval); convert_to_string_ex(tmpzval); rev2 = estrndup(Z_STRVAL_PP(tmpzval), Z_STRLEN_PP(tmpzval)); zval_ptr_dtor(tmpzval); } if (!type && context && php_stream_context_get_option(context, "cvs", "diff_type", &tmpzval) == SUCCESS) { SEPARATE_ZVAL(tmpzval); convert_to_string_ex(tmpzval); type = estrndup(Z_STRVAL_PP(tmpzval), Z_STRLEN_PP(tmpzval)); zval_ptr_dtor(tmpzval); } if (!rev1) { /* Revision1 is required */ goto wrap_errexit; } if (type && ((strcasecmp(type, "u") == 0) || (strcasecmp(type, "unified") == 0))) { unified = 1; php_stream_write_string(stream, "Argument -u\n"); } php_stream_printf(stream TSRMLS_CC, "Argument -r\n" "Argument %s\n" "Argument -r\n" "Argument %s\n", rev1, rev2 ? rev2 : "HEAD"); /* Request */ php_stream_printf(stream TSRMLS_CC, "Directory .\n" "%s%s%s\n" "Entry /%s/%s///\n" "Unchanged %s\n" "Argument %s\n" "diff\n", cvsroot, module, filepath ? filepath : "", filename, rev2 ? rev2 : "HEAD", filename, filename); efree(rev1); rev1 = NULL; if (rev2) { efree(rev2); rev2 = NULL; } if (type) { efree(type); type = NULL; } efree(cvsroot); cvsroot = NULL; efree(module); module = NULL; if (filepath) { efree(filepath); filepath = NULL; } /* Parse response */ ALLOC_INIT_ZVAL(wrapperdata); array_init(wrapperdata); while (php_stream_gets(stream, response, sizeof(response)-1) != NULL) { if (strncasecmp(response, "error", 5) == 0) { goto wrap_errexit; } if (strncasecmp(response, "M retrieving revision ", strlen("M retrieving revision ")) == 0) { zval *retval; if (!revision_list) { ALLOC_INIT_ZVAL(revision_list); array_init(revision_list); add_assoc_zval(wrapperdata, "revisions", revision_list); } ALLOC_INIT_ZVAL(retval); php_trim(response + strlen("M retrieving revision "), strlen(response) - strlen("M retrieving revision "), NULL, 0, retval, 2 TSRMLS_CC); add_next_index_zval(revision_list, retval); } /* TODO: Parse other elements into wrapperdata */ if (strlen(response) < 4 || strncasecmp(response, "M diff", strlen("M diff"))) { goto header_loop; } break; header_loop: ; } memstream = php_stream_fopen_tmpfile(); if (!memstream) { goto wrap_errexit; } while (php_stream_gets(stream, response, sizeof(response)-1) != NULL && strlen(response) >= strlen("M ") && strncmp(response, "M ", strlen("M ")) == 0) { if (unified && strlen(response) > strlen("M --- ") && strncmp(response, "M --- ", strlen("M --- ")) == 0) { zval *retval; ALLOC_INIT_ZVAL(retval); php_trim(response + strlen("M "), strlen(response) - (sizeof("M ") - 1), NULL, 0, retval, 2 TSRMLS_CC); add_assoc_zval(wrapperdata, "revision1", retval); } else if (unified && strlen(response) > strlen("M +++ ") && strncmp(response, "M +++ ", sizeof("M +++ ") - 1) == 0) { zval *retval; ALLOC_INIT_ZVAL(retval); php_trim(response + (sizeof("M ") - 1), strlen(response) - (sizeof("M ") - 1), NULL, 0, retval, 2 TSRMLS_CC); add_assoc_zval(wrapperdata, "revision2", retval); } else php_stream_write(memstream, response + (sizeof("M ") - 1), strlen(response) - (sizeof("M ") - 1)); } php_stream_rewind(memstream); php_stream_close(stream); memstream->wrapperdata = wrapperdata; php_url_free(resource); return memstream; wrap_errexit: if (filepath) { efree(filepath); } if (module) { efree(module); } if (cvsroot) { efree(cvsroot); } if (wrapperdata) { /* This will catch revision_list as well */ zval_ptr_dtor(&wrapperdata); } if (resource) { php_url_free(resource); } if (stream) { php_stream_close(stream); } if (memstream) { php_stream_close(memstream); } if (rev1) { efree(rev1); } if (rev2) { efree(rev2); } if (type) { efree(type); } return NULL; } /* }}} */ static php_stream_wrapper_ops cvsclient_stream_diff_wops = { php_stream_url_wrap_cvsclient_diff, NULL, /* stream_close */ NULL, NULL, /* stat_url */ NULL, /* opendir */ "CVSDIFF" #if PHP_MAJOR_VERSION >= 5 ,NULL /* unlink */ #endif }; php_stream_wrapper php_stream_cvsclient_diff_wrapper = { &cvsclient_stream_diff_wops, NULL, 1 /* is_url */ }; /* ****************** */ /* Userland Functions */ /* ****************** */ /* {{{ proto resource cvsclient_connect(string server, string cvsroot[, int port]) Connect to CVS pserver */ PHP_FUNCTION(cvsclient_connect) { php_cvsclient_resource *cvsclient; php_stream *stream; char *server, *cvsroot; long server_len, cvsroot_len, port = PHP_CVSCLIENT_DEFAULT_PORT; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|l", &server, &server_len, &cvsroot, &cvsroot_len, &port) == FAILURE) { RETURN_FALSE; } stream = php_stream_sock_open_host(server, port, SOCK_STREAM, NULL, 0); if (!stream) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to CVS pserver cvs://%s:%ld", server, port); RETURN_FALSE; } cvsclient = emalloc(sizeof(php_cvsclient_resource)); cvsclient->server = stream; cvsclient->cvsroot = estrndup(cvsroot, cvsroot_len); ZEND_REGISTER_RESOURCE(return_value, cvsclient, le_cvsclient); } /* }}} */ /* {{{ proto bool cvsclient_login(resource cvsclient, string username, string password) Authenticate to the CVS pserver */ PHP_FUNCTION(cvsclient_login) { zval *zcvsclient; php_cvsclient_resource *cvsclient; char *username, *password; long username_len, password_len; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rss", &zcvsclient, &username, &username_len, &password, &password_len) == FAILURE) { RETURN_FALSE; } ZEND_FETCH_RESOURCE(cvsclient, php_cvsclient_resource*, &zcvsclient, -1, CVSCLIENT_RES_NAME, le_cvsclient); /* Authenticate */ if (php_cvsclient_authenticate(cvsclient->server, cvsclient->cvsroot, username, password TSRMLS_CC) == FAILURE) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "CVS pserver authentication failure."); RETURN_FALSE; } /* Not really part of the authentication process, but the protocol expects it next. */ cvsclient->requests = php_cvsclient_negotiate(cvsclient->server, cvsclient->cvsroot TSRMLS_CC); RETURN_TRUE; } /* }}} */ /* {{{ proto mixed cvsclient_retrieve(resource cvsclient, string module, string path[, string saveto[, string revision]]) Retrieve specified (can also be tag or branch) of filename referred to by in */ PHP_FUNCTION(cvsclient_retrieve) { zval *zcvsclient; php_cvsclient_resource *cvsclient; char *p, *module, *path, *saveto = NULL, *revision = NULL, *file, response[4096]; long module_len, path_len, saveto_len = 0, revision_len = 0, filesize = 0, count, i; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rss|ss", &zcvsclient, &module, &module_len, &path, &path_len, &saveto, &saveto_len, &revision, &revision_len) == FAILURE) { RETURN_FALSE; } ZEND_FETCH_RESOURCE(cvsclient, php_cvsclient_resource*, &zcvsclient, -1, CVSCLIENT_RES_NAME, le_cvsclient); if (path[0] == '/') { path++; } p = strrchr(path, '/'); if (revision) { php_stream_printf(cvsclient->server TSRMLS_CC, "Argument -r\nArgument %s\n", revision); } if (p) { /* path is non-root */ char save; save = path[path_len - (p - path)]; path[path_len - (p - path)] = '\0'; php_stream_printf(cvsclient->server TSRMLS_CC, "Argument %s\n" "Directory .\n" "%s/%s/%s\n", p + 1, cvsclient->cvsroot, module, path); path[path_len - (p - path)] = save; } else { /* file is at root of cvs module */ php_stream_printf(cvsclient->server TSRMLS_CC, "Argument %s\n" "Directory .\n" "%s/%s\n", path, cvsclient->cvsroot, module); } php_stream_write_string(cvsclient->server, "update\n"); while (filesize == 0 && php_stream_gets(cvsclient->server, response, sizeof(response)-1) != NULL) { /* Parse response and look for data */ if (strncasecmp(response, "error", 5) == 0) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unrecoverable error occured (%s)", response); zend_list_delete(Z_LVAL_P(zcvsclient)); RETURN_FALSE; } filesize = 1; for(i=0; filesize && i 1 || (saveto_len == 1 && saveto[0] == '-'))) { /* Save to a "local" file */ php_stream *outfile; if (!(outfile = php_stream_open_wrapper(saveto, "wb", ENFORCE_SAFE_MODE | REPORT_ERRORS, NULL))) { RETURN_FALSE; } while (filesize > 0) { count = php_stream_read(cvsclient->server, response, filesize > (sizeof(response) - 1) ? sizeof(response) - 1 : filesize); php_stream_write(outfile, response, count); filesize -= count; if (count <= 0) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error reading remote file."); RETURN_FALSE; } } RETURN_TRUE; } else { /* Return as a string */ p = file = emalloc(filesize); while (filesize > 0) { count = php_stream_read(cvsclient->server, p, filesize); filesize -= count; p += count; if (count <= 0) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error reading remote file."); efree(file); RETURN_FALSE; } } RETURN_STRINGL(file, p - file, 0); } } /* }}} */ static char *php_cvsclient_parse_log(const char *log, const char *param TSRMLS_DC) { int log_len, param_len; const char *p, *e; char *param_dup, *val; if (!log || !param) { return NULL; } log_len = strlen(log); param_len = strlen(param); if (!log_len || !param_len) { return NULL; } param_dup = emalloc(param_len + 3); memcpy(param_dup, param, param_len); param_dup[param_len] = ':'; param_dup[param_len+1] = ' '; param_dup[param_len+2] = '\0'; if (strncasecmp(log, param_dup + 1, param_len + 1) == 0) { /* param found at start of string */ p = log + param_len + 1; } else { p = strstr(log, param_dup); if (p) { /* param found further in */ p += param_len + 2; } } if (!p) { /* parameter not found */ efree(param_dup); return NULL; } e = strchr(p, ';'); if (!e) { /* last parameter */ e = p + strlen(p); } val = estrndup(p, e - p); efree(param_dup); return val; } /* {{{ proto string cvsclient_log(resource cvsclient, string module, string filepath[, string revision]) Retrieve log message for a particular revision */ PHP_FUNCTION(cvsclient_log) { zval *zcvsclient, *currentlog = NULL; php_cvsclient_resource *cvsclient; char *p, *module, *path, *revision = NULL, response[4096]; long module_len, path_len, revision_len = 0, filesize = 0, search_mode; smart_str logentry = {0}; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rss|s", &zcvsclient, &module, &module_len, &path, &path_len, &revision, &revision_len) == FAILURE) { RETURN_FALSE; } ZEND_FETCH_RESOURCE(cvsclient, php_cvsclient_resource*, &zcvsclient, -1, CVSCLIENT_RES_NAME, le_cvsclient); if (path[0] == '/') { path++; } p = strrchr(path, '/'); if (revision) { php_stream_printf(cvsclient->server TSRMLS_CC, "Argument -r\nArgument %s\n", revision); } if (p) { /* path is non-root */ char save; save = path[path_len - (p - path)]; path[path_len - (p - path)] = '\0'; php_stream_printf(cvsclient->server TSRMLS_CC, "Argument %s\n" "Directory .\n" "%s/%s/%s\n", p + 1, cvsclient->cvsroot, module, path); path[path_len - (p - path)] = save; } else { /* file is at root of cvs module */ php_stream_printf(cvsclient->server TSRMLS_CC, "Argument %s\n" "Directory .\n" "%s/%s\n", path, cvsclient->cvsroot, module); } php_stream_write_string(cvsclient->server, "log\n"); array_init(return_value); revision = NULL; search_mode = CVSCLIENT_DIVIDER; while (filesize == 0 && php_stream_gets(cvsclient->server, response, sizeof(response)-1) != NULL) { /* Parse response and look for data */ if (strncasecmp(response, "error", 5) == 0) { break; } if ((search_mode == CVSCLIENT_DIVIDER || search_mode == CVSCLIENT_BODY)&& strlen(response) >= sizeof(PHP_CVSCLIENT_REV_DIVIDER) && strncmp(response, PHP_CVSCLIENT_REV_DIVIDER, sizeof(PHP_CVSCLIENT_REV_DIVIDER) - 1) == 0 && response[sizeof(PHP_CVSCLIENT_REV_DIVIDER)-1] != '-') { /* New log entry */ if (currentlog) { if (logentry.len) { smart_str_0(&logentry); add_assoc_stringl(currentlog, "log", logentry.c, logentry.len, 0); logentry.c = NULL; logentry.len = 0; } if (revision) { add_assoc_zval(return_value, revision, currentlog); revision = NULL; } else { add_next_index_zval(return_value, currentlog); } currentlog = NULL; } search_mode = CVSCLIENT_REVISION; } if (search_mode == CVSCLIENT_BODY && strncmp(response, PHP_CVSCLIENT_REV_DIVIDER2, sizeof(PHP_CVSCLIENT_REV_DIVIDER2) - 1) == 0) { break; } if (search_mode == CVSCLIENT_BODY) { smart_str_appendl(&logentry, response + sizeof("M ") - 1, strlen(response) - sizeof("M ") +1); } if (search_mode == CVSCLIENT_SIGNATURE && strlen(response) > sizeof("M date: ") && strncasecmp(response, "M date: ", sizeof("M date: ") - 1) == 0) { char *date, *author, *state, *lines; p = response + 2; if (!currentlog) { /* Probably not needed */ ALLOC_INIT_ZVAL(currentlog); array_init(currentlog); } date = php_cvsclient_parse_log(p, "date" TSRMLS_CC); author = php_cvsclient_parse_log(p, "author" TSRMLS_CC); state = php_cvsclient_parse_log(p, "state" TSRMLS_CC); lines = php_cvsclient_parse_log(p, "lines" TSRMLS_CC); if (date) { zval *z; ALLOC_INIT_ZVAL(z); php_trim(date, strlen(date), NULL, 0, z, 2 TSRMLS_CC); add_assoc_zval(currentlog, "date", z); efree(date); } if (author) { zval *z; ALLOC_INIT_ZVAL(z); php_trim(author, strlen(author), NULL, 0, z, 2 TSRMLS_CC); add_assoc_zval(currentlog, "author", z); efree(author); } if (state) { zval *z; ALLOC_INIT_ZVAL(z); php_trim(state, strlen(state), NULL, 0, z, 2 TSRMLS_CC); add_assoc_zval(currentlog, "state", z); efree(state); } if (lines) { zval *z; ALLOC_INIT_ZVAL(z); php_trim(lines, strlen(lines), NULL, 0, z, 2 TSRMLS_CC); add_assoc_zval(currentlog, "lines", z); efree(lines); } search_mode = CVSCLIENT_BODY; } if (search_mode == CVSCLIENT_REVISION && strlen(response) > sizeof("M revision ") && strncmp(response, "M revision ", sizeof("M revision ") - 1) == 0) { zval *revval; ALLOC_INIT_ZVAL(revval); php_trim(response + sizeof("M revision ") - 1, strlen(response) - sizeof("M revision ") + 1, NULL, 0, revval, 2 TSRMLS_CC); revision = Z_STRVAL_P(revval); /* Used for hash key */ if (!currentlog) { ALLOC_INIT_ZVAL(currentlog); array_init(currentlog); } add_assoc_zval(currentlog, "revision", revval); search_mode = CVSCLIENT_SIGNATURE; } } if (currentlog) { if (logentry.c) { smart_str_0(&logentry); add_assoc_stringl(currentlog, "log", logentry.c, logentry.len, 0); } if (revision) { add_assoc_zval(return_value, revision, currentlog); } else { add_next_index_zval(return_value, currentlog); } } else { if (logentry.c) { efree(logentry.c); } } } /* }}} */ /* ******************* */ /* Module Housekeeping */ /* ******************* */ function_entry cvsclient_functions[] = { PHP_FE(cvsclient_connect, NULL) PHP_FE(cvsclient_login, NULL) PHP_FE(cvsclient_retrieve, NULL) PHP_FE(cvsclient_log, NULL) /* TODO: Resource based single-session access and committment */ {NULL, NULL, NULL} }; zend_module_entry cvsclient_module_entry = { STANDARD_MODULE_HEADER, "cvsclient", cvsclient_functions, PHP_MINIT(cvsclient), PHP_MSHUTDOWN(cvsclient), NULL, /* RINIT */ NULL, /* RSHUTDOWN */ PHP_MINFO(cvsclient), NO_VERSION_YET, STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_CVSCLIENT ZEND_GET_MODULE(cvsclient) #endif static void cvsclient_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) { php_cvsclient_resource *cvsclient = (php_cvsclient_resource *)rsrc->ptr; if (cvsclient) { if (cvsclient->server) { php_stream_close(cvsclient->server); cvsclient->server = NULL; } if (cvsclient->cvsroot) { efree(cvsclient->cvsroot); cvsclient->cvsroot = NULL; } efree(cvsclient); cvsclient = NULL; rsrc->ptr = NULL; } } PHP_MINIT_FUNCTION(cvsclient) { /* TODO: Readonly support for cvs.log, cvs.history, etc... */ le_cvsclient = zend_register_list_destructors_ex(cvsclient_dtor, NULL, CVSCLIENT_RES_NAME, module_number); if (php_register_url_stream_wrapper("cvs", &php_stream_cvsclient_wrapper TSRMLS_CC) == FAILURE) { return FAILURE; } if (php_register_url_stream_wrapper("cvs.diff", &php_stream_cvsclient_diff_wrapper TSRMLS_CC) == FAILURE) { return FAILURE; } return SUCCESS; } PHP_MSHUTDOWN_FUNCTION(cvsclient) { if (php_unregister_url_stream_wrapper("cvs" TSRMLS_CC) == FAILURE) { return FAILURE; } if (php_unregister_url_stream_wrapper("cvs.diff" TSRMLS_CC) == FAILURE) { return FAILURE; } return SUCCESS; } PHP_MINFO_FUNCTION(cvsclient) { php_info_print_table_start(); php_info_print_table_row(2, "CVS Client", "enabled"); php_info_print_table_row(2, "Wrapper", "cvs://, cvs.diff://"); php_info_print_table_row(2, "Resource", CVSCLIENT_RES_NAME); php_info_print_table_end(); } #endif /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim600: sw=4 ts=4 fdm=marker * vim<600: sw=4 ts=4 */