/*
+----------------------------------------------------------------------+
| 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 <pollita@php.net> |
+----------------------------------------------------------------------+
*/
#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 <sys/socket.h>
#endif
#include <ctype.h>
/* ********************* */
/* 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<strlen(enc); i++) {
if (enc[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; i<strlen(response); i++) {
response[i] = tolower(response[i]);
}
/* TODO: Improve this, we could (and probably do) get false positives */
while (verbs->request && 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<strlen(response); i++) {
if (!isdigit(response[i]) && !iscntrl(response[i])) {
goto header_loop;
}
}
break;
header_loop:
;
}
filesize = atoi(response);
add_assoc_long(wrapperdata, "filesize", filesize);
memstream = php_stream_fopen_tmpfile();
if (!memstream) {
goto wrap_errexit;
}
while (filesize) {
int read;
read = php_stream_read(stream, response, (filesize > (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 <revision> (can also be tag or branch) of filename referred to by <path> in <module> */
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<strlen(response); i++) {
if (!isdigit(response[i]) && !iscntrl(response[i])) {
filesize = 0;
}
}
}
if (!filesize) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to find document length.");
RETURN_FALSE;
}
filesize = atoi(response);
if (filesize <= 0) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid filesize (%ld)", filesize);
RETURN_FALSE;
}
if (saveto && (saveto_len > 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
*/
syntax highlighted by Code2HTML, v. 0.9.1