Index: src/attach.c
==================================================================
--- src/attach.c
+++ src/attach.c
@@ -214,11 +214,11 @@
}else{
rid = content_put(pAttach);
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d);", rid);
db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", rid);
}
- manifest_crosslink(rid, pAttach);
+ manifest_crosslink(rid, pAttach, MC_NONE);
}
/*
** WEBPAGE: attachadd
@@ -431,11 +431,11 @@
blob_appendf(&manifest, "D %s\n", zDate);
blob_appendf(&manifest, "U %F\n", g.zLogin ? g.zLogin : "nobody");
md5sum_blob(&manifest, &cksum);
blob_appendf(&manifest, "Z %b\n", &cksum);
rid = content_put(&manifest);
- manifest_crosslink(rid, &manifest);
+ manifest_crosslink(rid, &manifest, MC_NONE);
db_end_transaction(0);
@
The attachment below has been deleted.
}
if( P("del")
Index: src/branch.c
==================================================================
--- src/branch.c
+++ src/branch.c
@@ -153,12 +153,12 @@
brid = content_put_ex(&branch, 0, 0, 0, isPrivate);
if( brid==0 ){
fossil_fatal("trouble committing manifest: %s", g.zErrMsg);
}
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid);
- if( manifest_crosslink(brid, &branch)==0 ){
- fossil_fatal("unable to install new manifest");
+ if( manifest_crosslink(brid, &branch, MC_PERMIT_HOOKS)==0 ){
+ fossil_fatal("%s\n", g.zErrMsg);
}
assert( blob_is_reset(&branch) );
content_deltify(rootid, brid, 0);
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", brid);
fossil_print("New branch: %s\n", zUuid);
Index: src/checkin.c
==================================================================
--- src/checkin.c
+++ src/checkin.c
@@ -1816,11 +1816,14 @@
nvid = content_put(&manifest);
if( nvid==0 ){
fossil_fatal("trouble committing manifest: %s", g.zErrMsg);
}
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nvid);
- manifest_crosslink(nvid, &manifest);
+ if( manifest_crosslink(nvid, &manifest,
+ dryRunFlag ? MC_NONE : MC_PERMIT_HOOKS)==0 ){
+ fossil_fatal("%s\n", g.zErrMsg);
+ }
assert( blob_is_reset(&manifest) );
content_deltify(vid, nvid, 0);
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid);
db_prepare(&q, "SELECT uuid,merge FROM vmerge JOIN blob ON merge=rid"
Index: src/configure.c
==================================================================
--- src/configure.c
+++ src/configure.c
@@ -98,10 +98,11 @@
{ "timeline-plaintext", CONFIGSET_SKIN },
{ "adunit", CONFIGSET_SKIN },
{ "adunit-omit-if-admin", CONFIGSET_SKIN },
{ "adunit-omit-if-user", CONFIGSET_SKIN },
{ "th1-setup", CONFIGSET_TH1 },
+ { "th1-uri-regexp", CONFIGSET_TH1 },
#ifdef FOSSIL_ENABLE_TCL
{ "tcl", CONFIGSET_TH1 },
{ "tcl-setup", CONFIGSET_TH1 },
#endif
@@ -138,10 +139,12 @@
{ "@shun", CONFIGSET_SHUN },
{ "xfer-common-script", CONFIGSET_XFER },
{ "xfer-push-script", CONFIGSET_XFER },
+ { "xfer-commit-script", CONFIGSET_XFER },
+ { "xfer-ticket-script", CONFIGSET_XFER },
};
static int iConfig = 0;
/*
Index: src/content.c
==================================================================
--- src/content.c
+++ src/content.c
@@ -388,11 +388,11 @@
int i;
/* Parse the object rid itself */
if( linkFlag ){
content_get(rid, &content);
- manifest_crosslink(rid, &content);
+ manifest_crosslink(rid, &content, MC_NONE);
assert( blob_is_reset(&content) );
}
/* Parse all delta-manifests that depend on baseline-manifest rid */
db_prepare(&q, "SELECT rid FROM orphan WHERE baseline=%d", rid);
@@ -405,11 +405,11 @@
aChild[nChildUsed++] = child;
}
db_finalize(&q);
for(i=0; iport);
+ *(int*)&addr.sin_addr = inet_addr(pUrlData->name);
if( -1 == *(int*)&addr.sin_addr ){
#ifndef FOSSIL_STATIC_LINK
struct hostent *pHost;
- pHost = gethostbyname(g.urlName);
+ pHost = gethostbyname(pUrlData->name);
if( pHost!=0 ){
memcpy(&addr.sin_addr,pHost->h_addr_list[0],pHost->h_length);
}else
#endif
{
- socket_set_errmsg("can't resolve host name: %s", g.urlName);
+ socket_set_errmsg("can't resolve host name: %s", pUrlData->name);
return 1;
}
}
addrIsInit = 1;
@@ -167,11 +167,12 @@
if( iSocket<0 ){
socket_set_errmsg("cannot create a socket");
return 1;
}
if( connect(iSocket,(struct sockaddr*)&addr,sizeof(addr))<0 ){
- socket_set_errmsg("cannot connect to host %s:%d", g.urlName, g.urlPort);
+ socket_set_errmsg("cannot connect to host %s:%d", pUrlData->name,
+ pUrlData->port);
socket_close();
return 1;
}
#if !defined(_WIN32)
signal(SIGPIPE, SIG_IGN);
@@ -215,16 +216,16 @@
/*
** Attempt to resolve g.urlName to IP and setup g.zIpAddr so rcvfrom gets
** populated. For hostnames with more than one IP (or if overridden in
** ~/.ssh/config) the rcvfrom may not match the host to which we connect.
*/
-void socket_ssh_resolve_addr(void){
+void socket_ssh_resolve_addr(UrlData *pUrlData){
struct hostent *pHost; /* Used to make best effort for rcvfrom */
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
- pHost = gethostbyname(g.urlName);
+ pHost = gethostbyname(pUrlData->name);
if( pHost!=0 ){
memcpy(&addr.sin_addr,pHost->h_addr_list[0],pHost->h_length);
g.zIpAddr = mprintf("%s", inet_ntoa(addr.sin_addr));
}
}
Index: src/http_ssl.c
==================================================================
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -183,11 +183,11 @@
** g.urlName Name of the server. Ex: www.fossil-scm.org
** g.urlPort TCP/IP port to use. Ex: 80
**
** Return the number of errors.
*/
-int ssl_open(void){
+int ssl_open(UrlData *pUrlData){
X509 *cert;
int hasSavedCertificate = 0;
int trusted = 0;
unsigned long e;
@@ -194,11 +194,11 @@
ssl_global_init();
/* Get certificate for current server from global config and
* (if we have it in config) add it to certificate store.
*/
- cert = ssl_get_certificate(&trusted);
+ cert = ssl_get_certificate(pUrlData, &trusted);
if ( cert!=NULL ){
X509_STORE_add_cert(SSL_CTX_get_cert_store(sslCtx), cert);
X509_free(cert);
hasSavedCertificate = 1;
}
@@ -205,11 +205,11 @@
iBio = BIO_new_ssl_connect(sslCtx);
BIO_get_ssl(iBio, &ssl);
#if (SSLEAY_VERSION_NUMBER >= 0x00908070) && !defined(OPENSSL_NO_TLSEXT)
- if( !SSL_set_tlsext_host_name(ssl, g.urlName) ){
+ if( !SSL_set_tlsext_host_name(ssl, pUrlData->name) ){
fossil_warning("WARNING: failed to set server name indication (SNI), "
"continuing without it.\n");
}
#endif
@@ -218,23 +218,25 @@
ssl_set_errmsg("SSL: cannot open SSL (%s)",
ERR_reason_error_string(ERR_get_error()));
return 1;
}
- BIO_set_conn_hostname(iBio, g.urlName);
- BIO_set_conn_int_port(iBio, &g.urlPort);
+ BIO_set_conn_hostname(iBio, pUrlData->name);
+ BIO_set_conn_int_port(iBio, &pUrlData->port);
if( BIO_do_connect(iBio)<=0 ){
ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)",
- g.urlName, g.urlPort, ERR_reason_error_string(ERR_get_error()));
+ pUrlData->name, pUrlData->port,
+ ERR_reason_error_string(ERR_get_error()));
ssl_close();
return 1;
}
if( BIO_do_handshake(iBio)<=0 ) {
ssl_set_errmsg("Error establishing SSL connection %s:%d (%s)",
- g.urlName, g.urlPort, ERR_reason_error_string(ERR_get_error()));
+ pUrlData->name, pUrlData->port,
+ ERR_reason_error_string(ERR_get_error()));
ssl_close();
return 1;
}
/* Check if certificate is valid */
cert = SSL_get_peer_certificate(ssl);
@@ -281,11 +283,11 @@
" certificates list\n\n"
"If you are not expecting this message, answer no and "
"contact your server\nadministrator.\n\n"
"Accept certificate for host %s (a=always/y/N)? ",
X509_verify_cert_error_string(e), desc, warning,
- g.urlName);
+ pUrlData->name);
BIO_free(mem);
prompt_user(prompt, &ans);
free(prompt);
cReply = blob_str(&ans)[0];
@@ -302,11 +304,11 @@
&ans);
cReply = blob_str(&ans)[0];
trusted = ( cReply=='a' || cReply=='A' );
blob_reset(&ans);
}
- ssl_save_certificate(cert, trusted);
+ ssl_save_certificate(pUrlData, cert, trusted);
}
}
/* Set the Global.zIpAddr variable to the server we are talking to.
** This is used to populate the ipaddr column of the rcvfrom table,
@@ -323,44 +325,44 @@
}
/*
** Save certificate to global config.
*/
-void ssl_save_certificate(X509 *cert, int trusted){
+void ssl_save_certificate(UrlData *pUrlData, X509 *cert, int trusted){
BIO *mem;
char *zCert, *zHost;
mem = BIO_new(BIO_s_mem());
PEM_write_bio_X509(mem, cert);
BIO_write(mem, "", 1); /* nul-terminate mem buffer */
BIO_get_mem_data(mem, &zCert);
- zHost = mprintf("cert:%s", g.urlName);
+ zHost = mprintf("cert:%s", pUrlData->name);
db_set(zHost, zCert, 1);
free(zHost);
- zHost = mprintf("trusted:%s", g.urlName);
+ zHost = mprintf("trusted:%s", pUrlData->name);
db_set_int(zHost, trusted, 1);
free(zHost);
BIO_free(mem);
}
/*
** Get certificate for g.urlName from global config.
** Return NULL if no certificate found.
*/
-X509 *ssl_get_certificate(int *pTrusted){
+X509 *ssl_get_certificate(UrlData *pUrlData, int *pTrusted){
char *zHost, *zCert;
BIO *mem;
X509 *cert;
- zHost = mprintf("cert:%s", g.urlName);
+ zHost = mprintf("cert:%s", pUrlData->name);
zCert = db_get(zHost, NULL);
free(zHost);
if ( zCert==NULL )
return NULL;
if ( pTrusted!=0 ){
- zHost = mprintf("trusted:%s", g.urlName);
+ zHost = mprintf("trusted:%s", pUrlData->name);
*pTrusted = db_get_int(zHost, 0);
free(zHost);
}
mem = BIO_new(BIO_s_mem());
Index: src/http_transport.c
==================================================================
--- src/http_transport.c
+++ src/http_transport.c
@@ -52,13 +52,13 @@
/*
** Return the current transport error message.
*/
-const char *transport_errmsg(void){
+const char *transport_errmsg(UrlData *pUrlData){
#ifdef FOSSIL_ENABLE_SSL
- if( g.urlIsHttps ){
+ if( pUrlData->isHttps ){
return ssl_errmsg();
}
#endif
return socket_errmsg();
}
@@ -86,47 +86,47 @@
#endif
/*
** SSH initialization of the transport layer
*/
-int transport_ssh_open(void){
+int transport_ssh_open(UrlData *pUrlData){
/* For SSH we need to create and run SSH fossil http
** to talk to the remote machine.
*/
const char *zSsh; /* The base SSH command */
Blob zCmd; /* The SSH command */
char *zHost; /* The host name to contact */
int n; /* Size of prefix string */
- socket_ssh_resolve_addr();
+ socket_ssh_resolve_addr(pUrlData);
zSsh = db_get("ssh-command", zDefaultSshCmd);
blob_init(&zCmd, zSsh, -1);
- if( g.urlPort!=g.urlDfltPort && g.urlPort ){
+ if( pUrlData->port!=pUrlData->dfltPort && pUrlData->port ){
#ifdef __MINGW32__
- blob_appendf(&zCmd, " -P %d", g.urlPort);
+ blob_appendf(&zCmd, " -P %d", pUrlData->port);
#else
- blob_appendf(&zCmd, " -p %d", g.urlPort);
+ blob_appendf(&zCmd, " -p %d", pUrlData->port);
#endif
}
if( g.fSshTrace ){
fossil_force_newline();
fossil_print("%s", blob_str(&zCmd)); /* Show the base of the SSH command */
}
- if( g.urlUser && g.urlUser[0] ){
- zHost = mprintf("%s@%s", g.urlUser, g.urlName);
+ if( pUrlData->user && pUrlData->user[0] ){
+ zHost = mprintf("%s@%s", pUrlData->user, pUrlData->name);
}else{
- zHost = mprintf("%s", g.urlName);
+ zHost = mprintf("%s", pUrlData->name);
}
n = blob_size(&zCmd);
blob_append(&zCmd, " ", 1);
shell_escape(&zCmd, zHost);
blob_append(&zCmd, " ", 1);
- shell_escape(&zCmd, mprintf("%s", g.urlFossil));
+ shell_escape(&zCmd, mprintf("%s", pUrlData->fossil));
blob_append(&zCmd, " test-http", 10);
- if( g.urlPath && g.urlPath[0] ){
+ if( pUrlData->path && pUrlData->path[0] ){
blob_append(&zCmd, " ", 1);
- shell_escape(&zCmd, mprintf("%s", g.urlPath));
+ shell_escape(&zCmd, mprintf("%s", pUrlData->path));
}
if( g.fSshTrace ){
fossil_print("%s\n", blob_str(&zCmd)+n); /* Show tail of SSH command */
}
free(zHost);
@@ -146,25 +146,25 @@
** g.urlPort TCP/IP port. Ex: 80
** g.urlIsHttps Use TLS for the connection
**
** Return the number of errors.
*/
-int transport_open(void){
+int transport_open(UrlData *pUrlData){
int rc = 0;
if( transport.isOpen==0 ){
- if( g.urlIsSsh ){
- rc = transport_ssh_open();
+ if( pUrlData->isSsh ){
+ rc = transport_ssh_open(pUrlData);
if( rc==0 ) transport.isOpen = 1;
- }else if( g.urlIsHttps ){
+ }else if( pUrlData->isHttps ){
#ifdef FOSSIL_ENABLE_SSL
- rc = ssl_open();
+ rc = ssl_open(pUrlData);
if( rc==0 ) transport.isOpen = 1;
#else
socket_set_errmsg("HTTPS: Fossil has been compiled without SSL support");
rc = 1;
#endif
- }else if( g.urlIsFile ){
+ }else if( pUrlData->isFile ){
sqlite3_uint64 iRandId;
sqlite3_randomness(sizeof(iRandId), &iRandId);
transport.zOutFile = mprintf("%s-%llu-out.http",
g.zRepositoryName, iRandId);
transport.zInFile = mprintf("%s-%llu-in.http",
@@ -173,21 +173,21 @@
if( transport.pFile==0 ){
fossil_fatal("cannot output temporary file: %s", transport.zOutFile);
}
transport.isOpen = 1;
}else{
- rc = socket_open();
+ rc = socket_open(pUrlData);
if( rc==0 ) transport.isOpen = 1;
}
}
return rc;
}
/*
** Close the current connection
*/
-void transport_close(void){
+void transport_close(UrlData *pUrlData){
if( transport.isOpen ){
free(transport.pBuf);
transport.pBuf = 0;
transport.nAlloc = 0;
transport.nUsed = 0;
@@ -194,17 +194,17 @@
transport.iCursor = 0;
if( transport.pLog ){
fclose(transport.pLog);
transport.pLog = 0;
}
- if( g.urlIsSsh ){
+ if( pUrlData->isSsh ){
transport_ssh_close();
- }else if( g.urlIsHttps ){
+ }else if( pUrlData->isHttps ){
#ifdef FOSSIL_ENABLE_SSL
ssl_close();
#endif
- }else if( g.urlIsFile ){
+ }else if( pUrlData->isFile ){
if( transport.pFile ){
fclose(transport.pFile);
transport.pFile = 0;
}
file_delete(transport.zInFile);
@@ -219,28 +219,28 @@
}
/*
** Send content over the wire.
*/
-void transport_send(Blob *toSend){
+void transport_send(UrlData *pUrlData, Blob *toSend){
char *z = blob_buffer(toSend);
int n = blob_size(toSend);
transport.nSent += n;
- if( g.urlIsSsh ){
+ if( pUrlData->isSsh ){
fwrite(z, 1, n, sshOut);
fflush(sshOut);
- }else if( g.urlIsHttps ){
+ }else if( pUrlData->isHttps ){
#ifdef FOSSIL_ENABLE_SSL
int sent;
while( n>0 ){
sent = ssl_send(0, z, n);
/* printf("Sent %d of %d bytes\n", sent, n); fflush(stdout); */
if( sent<=0 ) break;
n -= sent;
}
#endif
- }else if( g.urlIsFile ){
+ }else if( pUrlData->isFile ){
fwrite(z, 1, n, transport.pFile);
}else{
int sent;
while( n>0 ){
sent = socket_send(0, z, n);
@@ -253,16 +253,16 @@
/*
** This routine is called when the outbound message is complete and
** it is time to being receiving a reply.
*/
-void transport_flip(void){
- if( g.urlIsFile ){
+void transport_flip(UrlData *pUrlData){
+ if( pUrlData->isFile ){
char *zCmd;
fclose(transport.pFile);
zCmd = mprintf("\"%s\" http \"%s\" \"%s\" \"%s\" 127.0.0.1 --localauth",
- g.nameOfExe, g.urlName, transport.zOutFile, transport.zInFile
+ g.nameOfExe, pUrlData->name, transport.zOutFile, transport.zInFile
);
fossil_system(zCmd);
free(zCmd);
transport.pFile = fossil_fopen(transport.zInFile, "rb");
}
@@ -282,21 +282,21 @@
/*
** This routine is called when the inbound message has been received
** and it is time to start sending again.
*/
-void transport_rewind(void){
- if( g.urlIsFile ){
- transport_close();
+void transport_rewind(UrlData *pUrlData){
+ if( pUrlData->isFile ){
+ transport_close(pUrlData);
}
}
/*
** Read N bytes of content directly from the wire and write into
** the buffer.
*/
-static int transport_fetch(char *zBuf, int N){
+static int transport_fetch(UrlData *pUrlData, char *zBuf, int N){
int got;
if( sshIn ){
int x;
int wanted = N;
got = 0;
@@ -304,17 +304,17 @@
x = read(sshIn, &zBuf[got], wanted);
if( x<=0 ) break;
got += x;
wanted -= x;
}
- }else if( g.urlIsHttps ){
+ }else if( pUrlData->isHttps ){
#ifdef FOSSIL_ENABLE_SSL
got = ssl_receive(0, zBuf, N);
#else
got = 0;
#endif
- }else if( g.urlIsFile ){
+ }else if( pUrlData->isFile ){
got = fread(zBuf, 1, N, transport.pFile);
}else{
got = socket_receive(0, zBuf, N);
}
/* printf("received %d of %d bytes\n", got, N); fflush(stdout); */
@@ -327,11 +327,11 @@
/*
** Read N bytes of content from the wire and store in the supplied buffer.
** Return the number of bytes actually received.
*/
-int transport_receive(char *zBuf, int N){
+int transport_receive(UrlData *pUrlData, char *zBuf, int N){
int onHand; /* Bytes current held in the transport buffer */
int nByte = 0; /* Bytes of content received */
onHand = transport.nUsed - transport.iCursor;
if( g.fSshTrace){
@@ -351,11 +351,11 @@
N -= toMove;
zBuf += toMove;
nByte += toMove;
}
if( N>0 ){
- int got = transport_fetch(zBuf, N);
+ int got = transport_fetch(pUrlData, zBuf, N);
if( got>0 ){
nByte += got;
transport.nRcvd += got;
}
}
@@ -366,11 +366,11 @@
/*
** Load up to N new bytes of content into the transport.pBuf buffer.
** The buffer itself might be moved. And the transport.iCursor value
** might be reset to 0.
*/
-static void transport_load_buffer(int N){
+static void transport_load_buffer(UrlData *pUrlData, int N){
int i, j;
if( transport.nAlloc==0 ){
transport.nAlloc = N;
transport.pBuf = fossil_malloc( N );
transport.iCursor = 0;
@@ -388,11 +388,11 @@
transport.nAlloc = transport.nUsed + N;
pNew = fossil_realloc(transport.pBuf, transport.nAlloc);
transport.pBuf = pNew;
}
if( N>0 ){
- i = transport_fetch(&transport.pBuf[transport.nUsed], N);
+ i = transport_fetch(pUrlData, &transport.pBuf[transport.nUsed], N);
if( i>0 ){
transport.nRcvd += i;
transport.nUsed += i;
}
}
@@ -404,18 +404,18 @@
** from the received line and zero-terminate the result. Return a pointer
** to the line.
**
** Each call to this routine potentially overwrites the returned buffer.
*/
-char *transport_receive_line(void){
+char *transport_receive_line(UrlData *pUrlData){
int i;
int iStart;
i = iStart = transport.iCursor;
while(1){
if( i >= transport.nUsed ){
- transport_load_buffer(g.urlIsSsh ? 2 : 1000);
+ transport_load_buffer(pUrlData, pUrlData->isSsh ? 2 : 1000);
i -= iStart;
iStart = 0;
if( i >= transport.nUsed ){
transport.pBuf[i] = 0;
transport.iCursor = i;
@@ -437,15 +437,15 @@
}
/*
** Global transport shutdown
*/
-void transport_global_shutdown(void){
- if( g.urlIsSsh ){
+void transport_global_shutdown(UrlData *pUrlData){
+ if( pUrlData->isSsh ){
transport_ssh_close();
}
- if( g.urlIsHttps ){
+ if( pUrlData->isHttps ){
#ifdef FOSSIL_ENABLE_SSL
ssl_global_shutdown();
#endif
}else{
socket_global_shutdown();
Index: src/info.c
==================================================================
--- src/info.c
+++ src/info.c
@@ -2194,11 +2194,11 @@
md5sum_blob(&ctrl, &cksum);
blob_appendf(&ctrl, "Z %b\n", &cksum);
db_begin_transaction();
g.markPrivate = content_is_private(rid);
nrid = content_put(&ctrl);
- manifest_crosslink(nrid, &ctrl);
+ manifest_crosslink(nrid, &ctrl, MC_PERMIT_HOOKS);
assert( blob_is_reset(&ctrl) );
db_end_transaction(0);
}
cgi_redirectf("ci?name=%s", zUuid);
}
Index: src/json_branch.c
==================================================================
--- src/json_branch.c
+++ src/json_branch.c
@@ -291,12 +291,12 @@
brid = content_put(&branch);
if( brid==0 ){
fossil_fatal("Problem committing manifest: %s", g.zErrMsg);
}
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid);
- if( manifest_crosslink(brid, &branch)==0 ){
- fossil_fatal("unable to install new manifest");
+ if( manifest_crosslink(brid, &branch, MC_PERMIT_HOOKS)==0 ){
+ fossil_fatal("%s\n", g.zErrMsg);
}
assert( blob_is_reset(&branch) );
content_deltify(rootid, brid, 0);
if( zNewRid ){
*zNewRid = brid;
Index: src/main.c
==================================================================
--- src/main.c
+++ src/main.c
@@ -114,10 +114,12 @@
#endif
/*
** All global variables are in this structure.
*/
+#define GLOBAL_URL() ((UrlData *)(&g.urlIsFile))
+
struct Global {
int argc; char **argv; /* Command-line arguments to the program */
char *nameOfExe; /* Full path of executable. */
const char *zErrlog; /* Log errors to this file, if not NULL */
int isConst; /* True if the output is unchanging */
@@ -168,10 +170,14 @@
int wikiFlags; /* Wiki conversion flags applied to %w and %W */
char isHTTP; /* True if server/CGI modes, else assume CLI. */
char javascriptHyperlink; /* If true, set href= using script, not HTML */
Blob httpHeader; /* Complete text of the HTTP request header */
+ /*
+ ** NOTE: These members MUST be kept in sync with those in the "UrlData"
+ ** structure defined in "url.c".
+ */
int urlIsFile; /* True if a "file:" url */
int urlIsHttps; /* True if a "https:" url */
int urlIsSsh; /* True if an "ssh:" url */
char *urlName; /* Hostname for http: or filename for file: */
char *urlHostname; /* The HOST: parameter on http headers */
@@ -831,10 +837,20 @@
const char *get_version(){
static const char version[] = RELEASE_VERSION " " MANIFEST_VERSION " "
MANIFEST_DATE " UTC";
return version;
}
+
+/*
+** This function returns the user-agent string for Fossil, for
+** use in HTTP(S) requests.
+*/
+const char *get_user_agent(){
+ static const char version[] = "Fossil/" RELEASE_VERSION " (" MANIFEST_DATE
+ " " MANIFEST_VERSION ")";
+ return version;
+}
/*
** COMMAND: version
**
** Usage: %fossil version ?-verbose|-v?
Index: src/manifest.c
==================================================================
--- src/manifest.c
+++ src/manifest.c
@@ -42,10 +42,16 @@
*/
#define PERM_REG 0 /* regular file */
#define PERM_EXE 1 /* executable */
#define PERM_LNK 2 /* symlink */
+/*
+** Flags for use with manifest_crosslink().
+*/
+#define MC_NONE 0 /* default handling */
+#define MC_PERMIT_HOOKS 1 /* permit hooks to execute */
+
/*
** A single F-card within a manifest
*/
struct ManifestFile {
char *zName; /* Name of a file */
@@ -1650,34 +1656,41 @@
** Historical note: This routine original processed manifests only.
** Processing for other control artifacts was added later. The name
** of the routine, "manifest_crosslink", and the name of this source
** file, is a legacy of its original use.
*/
-int manifest_crosslink(int rid, Blob *pContent){
- int i;
+int manifest_crosslink(int rid, Blob *pContent, int flags){
+ int i, result = TH_OK;
Manifest *p;
Stmt q;
int parentid = 0;
+ const char *zScript = 0;
+ const char *zUuid = 0;
if( (p = manifest_cache_find(rid))!=0 ){
blob_reset(pContent);
}else if( (p = manifest_parse(pContent, rid, 0))==0 ){
assert( blob_is_reset(pContent) || pContent==0 );
+ fossil_error(1, "syntax error in manifest");
return 0;
}
if( g.xlinkClusterOnly && p->type!=CFTYPE_CLUSTER ){
manifest_destroy(p);
assert( blob_is_reset(pContent) );
+ fossil_error(1, "no manifest");
return 0;
}
if( p->type==CFTYPE_MANIFEST && fetch_baseline(p, 0) ){
manifest_destroy(p);
assert( blob_is_reset(pContent) );
+ fossil_error(1, "cannot fetch baseline manifest");
return 0;
}
db_begin_transaction();
if( p->type==CFTYPE_MANIFEST ){
+ zScript = xfer_commit_code();
+ zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){
char *zCom;
for(i=0; inParent; i++){
int pid = uuid_to_rid(p->azParent[i], 1);
db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)"
@@ -1768,11 +1781,11 @@
switch( p->aTag[i].zName[0] ){
case '-': type = 0; break; /* Cancel prior occurrences */
case '+': type = 1; break; /* Apply to target only */
case '*': type = 2; break; /* Propagate to descendants */
default:
- fossil_fatal("unknown tag type in manifest: %s", p->aTag);
+ fossil_error(1, "unknown tag type in manifest: %s", p->aTag);
return 0;
}
tag_insert(&p->aTag[i].zName[1], type, p->aTag[i].zValue,
rid, p->rDate, tid);
}
@@ -1870,10 +1883,12 @@
}
}
if( p->type==CFTYPE_TICKET ){
char *zTag;
+ zScript = xfer_ticket_code();
+ zUuid = p->zTicketUuid;
assert( manifest_crosslink_busy==1 );
zTag = mprintf("tkt-%s", p->zTicketUuid);
tag_insert(zTag, 1, 0, rid, p->rDate, rid);
free(zTag);
db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)",
@@ -1934,33 +1949,39 @@
if( p->type==CFTYPE_CONTROL ){
Blob comment;
int i;
const char *zName;
const char *zValue;
- const char *zUuid;
+ const char *zTagUuid;
int branchMove = 0;
blob_zero(&comment);
if( p->zComment ){
blob_appendf(&comment, " %s.", p->zComment);
}
/* Next loop expects tags to be sorted on UUID, so sort it. */
qsort(p->aTag, p->nTag, sizeof(p->aTag[0]), tag_compare);
for(i=0; inTag; i++){
- zUuid = p->aTag[i].zUuid;
- if( !zUuid ) continue;
- if( i==0 || fossil_strcmp(zUuid, p->aTag[i-1].zUuid)!=0 ){
+ zTagUuid = p->aTag[i].zUuid;
+ if( !zTagUuid ) continue;
+ if( i==0 || fossil_strcmp(zTagUuid, p->aTag[i-1].zUuid)!=0 ){
blob_appendf(&comment,
" Edit [%S]:",
- zUuid);
+ zTagUuid);
branchMove = 0;
+ if( db_exists("SELECT 1 FROM event, blob"
+ " WHERE event.type='ci' AND event.objid=blob.rid"
+ " AND blob.uuid='%s'", zTagUuid) ){
+ zScript = xfer_commit_code();
+ zUuid = zTagUuid;
+ }
}
zName = p->aTag[i].zName;
zValue = p->aTag[i].zValue;
if( strcmp(zName, "*branch")==0 ){
blob_appendf(&comment,
" Move to branch [/timeline?r=%h&nd&dp=%S | %h].",
- zValue, zUuid, zValue);
+ zValue, zTagUuid, zValue);
branchMove = 1;
continue;
}else if( strcmp(zName, "*bgcolor")==0 ){
blob_appendf(&comment,
" Change branch background color to \"%h\".", zValue);
@@ -2021,17 +2042,23 @@
p->rDate, rid, p->zUser, blob_str(&comment)+1
);
blob_reset(&comment);
}
db_end_transaction(0);
+ if( zScript && (flags & MC_PERMIT_HOOKS) ){
+ result = xfer_run_common_script();
+ if( result==TH_OK ){
+ result = xfer_run_script(zScript, zUuid);
+ }
+ }
if( p->type==CFTYPE_MANIFEST ){
manifest_cache_insert(p);
}else{
manifest_destroy(p);
}
assert( blob_is_reset(pContent) );
- return 1;
+ return ( result!=TH_ERROR );
}
/*
** COMMAND: test-crosslink
**
@@ -2045,7 +2072,7 @@
Blob content;
db_find_and_open_repository(0, 0);
if( g.argc!=3 ) usage("RECORDID");
rid = name_to_rid(g.argv[2]);
content_get(rid, &content);
- manifest_crosslink(rid, &content);
+ manifest_crosslink(rid, &content, MC_NONE);
}
Index: src/rebuild.c
==================================================================
--- src/rebuild.c
+++ src/rebuild.c
@@ -250,11 +250,11 @@
blob_copy(©, pBase);
pUse = ©
}
if( zFNameFormat==0 ){
/* We are doing "fossil rebuild" */
- manifest_crosslink(rid, pUse);
+ manifest_crosslink(rid, pUse, MC_NONE);
}else{
/* We are doing "fossil deconstruct" */
char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
char *zFile = mprintf(zFNameFormat, zUuid, zUuid+prefixLength);
blob_write_to_file(pUse,zFile);
Index: src/setup.c
==================================================================
--- src/setup.c
+++ src/setup.c
@@ -812,11 +812,11 @@
if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
login_verify_csrf_secret();
db_set(zVar, zQ, 0);
zVal = zQ;
}
- @ %s(zLabel)
}
Index: src/tag.c
==================================================================
--- src/tag.c
+++ src/tag.c
@@ -326,11 +326,11 @@
}
blob_appendf(&ctrl, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin);
md5sum_blob(&ctrl, &cksum);
blob_appendf(&ctrl, "Z %b\n", &cksum);
nrid = content_put(&ctrl);
- manifest_crosslink(nrid, &ctrl);
+ manifest_crosslink(nrid, &ctrl, MC_PERMIT_HOOKS);
assert( blob_is_reset(&ctrl) );
}
/*
** COMMAND: tag
Index: src/th_main.c
==================================================================
--- src/th_main.c
+++ src/th_main.c
@@ -827,10 +827,124 @@
rc = TH_ERROR;
}
re_free(pRe);
return rc;
}
+
+/*
+** TH command: http ?-asynchronous? ?--? url ?payload?
+**
+** Perform an HTTP or HTTPS request for the specified URL. If a
+** payload is present, it will be interpreted as text/plain and
+** the POST method will be used; otherwise, the GET method will
+** be used. Upon success, if the -asynchronous option is used, an
+** empty string is returned as the result; otherwise, the response
+** from the server is returned as the result. Synchronous requests
+** are not currently implemented.
+*/
+#define HTTP_WRONGNUMARGS "http ?-asynchronous? ?--? url ?payload?"
+static int httpCmd(
+ Th_Interp *interp,
+ void *p,
+ int argc,
+ const char **argv,
+ int *argl
+){
+ int nArg = 1;
+ int fAsynchronous = 0;
+ const char *zType, *zRegexp;
+ Blob payload;
+ ReCompiled *pRe = 0;
+ UrlData urlData;
+
+ if( argc<2 || argc>5 ){
+ return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
+ }
+ if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){
+ fAsynchronous = 1; nArg++;
+ }
+ if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
+ if( nArg+1!=argc && nArg+2!=argc ){
+ return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
+ }
+ memset(&urlData, '\0', sizeof(urlData));
+ url_parse_local(argv[nArg], 0, &urlData);
+ if( urlData.isSsh || urlData.isFile ){
+ Th_ErrorMessage(interp, "url must be http:// or https://", 0, 0);
+ return TH_ERROR;
+ }
+ zRegexp = db_get("th1-uri-regexp", 0);
+ if( zRegexp && zRegexp[0] ){
+ const char *zErr = re_compile(&pRe, zRegexp, 0);
+ if( zErr ){
+ Th_SetResult(interp, zErr, -1);
+ return TH_ERROR;
+ }
+ }
+ if( !pRe || !re_match(pRe, (const unsigned char *)urlData.canonical, -1) ){
+ Th_SetResult(interp, "url not allowed", -1);
+ re_free(pRe);
+ return TH_ERROR;
+ }
+ re_free(pRe);
+ blob_zero(&payload);
+ if( nArg+2==argc ){
+ blob_append(&payload, argv[nArg+1], argl[nArg+1]);
+ zType = "POST";
+ }else{
+ zType = "GET";
+ }
+ if( fAsynchronous ){
+ const char *zSep, *zParams;
+ Blob hdr;
+ zParams = strrchr(argv[nArg], '?');
+ if( strlen(urlData.path)>0 && zParams!=argv[nArg] ){
+ zSep = "";
+ }else{
+ zSep = "/";
+ }
+ blob_zero(&hdr);
+ blob_appendf(&hdr, "%s %s%s%s HTTP/1.0\r\n",
+ zType, zSep, urlData.path, zParams ? zParams : "");
+ if( urlData.proxyAuth ){
+ blob_appendf(&hdr, "Proxy-Authorization: %s\r\n", urlData.proxyAuth);
+ }
+ if( urlData.passwd && urlData.user && urlData.passwd[0]=='#' ){
+ char *zCredentials = mprintf("%s:%s", urlData.user, &urlData.passwd[1]);
+ char *zEncoded = encode64(zCredentials, -1);
+ blob_appendf(&hdr, "Authorization: Basic %s\r\n", zEncoded);
+ fossil_free(zEncoded);
+ fossil_free(zCredentials);
+ }
+ blob_appendf(&hdr, "Host: %s\r\n"
+ "User-Agent: %s\r\n", urlData.hostname, get_user_agent());
+ if( zType[0]=='P' ){
+ blob_appendf(&hdr, "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Content-Length: %d\r\n\r\n", blob_size(&payload));
+ }else{
+ blob_appendf(&hdr, "\r\n");
+ }
+ if( transport_open(&urlData) ){
+ Th_ErrorMessage(interp, transport_errmsg(&urlData), 0, 0);
+ blob_reset(&hdr);
+ blob_reset(&payload);
+ return TH_ERROR;
+ }
+ transport_send(&urlData, &hdr);
+ transport_send(&urlData, &payload);
+ blob_reset(&hdr);
+ blob_reset(&payload);
+ transport_close(&urlData);
+ Th_SetResult(interp, 0, 0); /* NOTE: Asynchronous, no results. */
+ return TH_OK;
+ }else{
+ Th_ErrorMessage(interp,
+ "synchronous requests are not yet implemented", 0, 0);
+ blob_reset(&payload);
+ return TH_ERROR;
+ }
+}
/*
** Make sure the interpreter has been initialized. Initialize it if
** it has not been already.
**
@@ -855,10 +969,11 @@
{"enable_output", enableOutputCmd, 0},
{"hascap", hascapCmd, 0},
{"hasfeature", hasfeatureCmd, 0},
{"html", putsCmd, (void*)&aFlags[0]},
{"htmlize", htmlizeCmd, 0},
+ {"http", httpCmd, 0},
{"linecount", linecntCmd, 0},
{"puts", putsCmd, (void*)&aFlags[1]},
{"query", queryCmd, 0},
{"randhex", randhexCmd, 0},
{"regexp", regexpCmd, 0},
Index: src/tkt.c
==================================================================
--- src/tkt.c
+++ src/tkt.c
@@ -513,15 +513,16 @@
}
/*
** Write a ticket into the repository.
*/
-static void ticket_put(
+static int ticket_put(
Blob *pTicket, /* The text of the ticket change record */
const char *zTktId, /* The ticket to which this change is applied */
int needMod /* True if moderation is needed */
){
+ int result;
int rid = content_put_ex(pTicket, 0, 0, 0, needMod);
if( rid==0 ){
fossil_fatal("trouble committing ticket: %s", g.zErrMsg);
}
if( needMod ){
@@ -533,13 +534,14 @@
}else{
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d);", rid);
db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", rid);
}
manifest_crosslink_begin();
- manifest_crosslink(rid, pTicket);
+ result = (manifest_crosslink(rid, pTicket, MC_PERMIT_HOOKS)==0);
assert( blob_is_reset(pTicket) );
manifest_crosslink_end();
+ return result;
}
/*
** Subscript command: submit_ticket
**
@@ -1344,11 +1346,14 @@
}
blob_appendf(&tktchng, "K %s\n", zTktUuid);
blob_appendf(&tktchng, "U %F\n", zUser);
md5sum_blob(&tktchng, &cksum);
blob_appendf(&tktchng, "Z %b\n", &cksum);
- ticket_put(&tktchng, zTktUuid, 0);
- printf("ticket %s succeeded for %s\n",
+ if( ticket_put(&tktchng, zTktUuid, 0) ){
+ fossil_fatal("%s\n", g.zErrMsg);
+ }else{
+ fossil_print("ticket %s succeeded for %s\n",
(eCmd==set?"set":"add"),zTktUuid);
+ }
}
}
}
Index: src/url.c
==================================================================
--- src/url.c
+++ src/url.c
@@ -39,10 +39,34 @@
#define URL_REMEMBER 0x002 /* Remember the url for later reuse */
#define URL_ASK_REMEMBER_PW 0x004 /* Ask whether to remember prompted pw */
#define URL_REMEMBER_PW 0x008 /* Should remember pw */
#define URL_PROMPTED 0x010 /* Prompted for PW already */
+/*
+** The URL related data used with this subsystem.
+*/
+struct UrlData {
+ /*
+ ** NOTE: These members MUST be kept in sync with the related ones in the
+ ** "Global" structure defined in "main.c".
+ */
+ int isFile; /* True if a "file:" url */
+ int isHttps; /* True if a "https:" url */
+ int isSsh; /* True if an "ssh:" url */
+ char *name; /* Hostname for http: or filename for file: */
+ char *hostname; /* The HOST: parameter on http headers */
+ char *protocol; /* "http" or "https" */
+ int port; /* TCP port number for http: or https: */
+ int dfltPort; /* The default port for the given protocol */
+ char *path; /* Pathname for http: */
+ char *user; /* User id for http: */
+ char *passwd; /* Password for http: */
+ char *canonical; /* Canonical representation of the URL */
+ char *proxyAuth; /* Proxy-Authorizer: string */
+ char *fossil; /* The fossil query parameter on ssh: */
+ unsigned flags; /* Boolean flags controlling URL processing */
+};
#endif /* INTERFACE */
/*
** Convert a string to lower-case.
@@ -51,10 +75,201 @@
while( *z ){
*z = fossil_tolower(*z);
z++;
}
}
+
+/*
+** Parse the given URL. Populate members of the provided UrlData structure
+** as follows:
+**
+** isFile True if FILE:
+** isHttps True if HTTPS:
+** isSsh True if SSH:
+** protocol "http" or "https" or "file"
+** name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE:
+** port TCP port number for HTTP or HTTPS.
+** dfltPort Default TCP port number (80 or 443).
+** path Path name for HTTP or HTTPS.
+** user Userid.
+** passwd Password.
+** hostname HOST:PORT or just HOST if port is the default.
+** canonical The URL in canonical form, omitting the password
+**
+*/
+void url_parse_local(
+ const char *zUrl,
+ unsigned int urlFlags,
+ UrlData *pUrlData
+){
+ int i, j, c;
+ char *zFile = 0;
+
+ if( zUrl==0 ){
+ zUrl = db_get("last-sync-url", 0);
+ if( zUrl==0 ) return;
+ if( pUrlData->passwd==0 ){
+ pUrlData->passwd = unobscure(db_get("last-sync-pw", 0));
+ }
+ }
+
+ if( strncmp(zUrl, "http://", 7)==0
+ || strncmp(zUrl, "https://", 8)==0
+ || strncmp(zUrl, "ssh://", 6)==0
+ ){
+ int iStart;
+ char *zLogin;
+ char *zExe;
+ char cQuerySep = '?';
+
+ pUrlData->isFile = 0;
+ if( zUrl[4]=='s' ){
+ pUrlData->isHttps = 1;
+ pUrlData->protocol = "https";
+ pUrlData->dfltPort = 443;
+ iStart = 8;
+ }else if( zUrl[0]=='s' ){
+ pUrlData->isSsh = 1;
+ pUrlData->protocol = "ssh";
+ pUrlData->dfltPort = 22;
+ pUrlData->fossil = "fossil";
+ iStart = 6;
+ }else{
+ pUrlData->isHttps = 0;
+ pUrlData->protocol = "http";
+ pUrlData->dfltPort = 80;
+ iStart = 7;
+ }
+ for(i=iStart; (c=zUrl[i])!=0 && c!='/' && c!='@'; i++){}
+ if( c=='@' ){
+ /* Parse up the user-id and password */
+ for(j=iStart; juser = mprintf("%.*s", j-iStart, &zUrl[iStart]);
+ dehttpize(pUrlData->user);
+ if( jisSsh==0 ){
+ urlFlags |= URL_ASK_REMEMBER_PW;
+ }
+ pUrlData->passwd = mprintf("%.*s", i-j-1, &zUrl[j+1]);
+ dehttpize(pUrlData->passwd);
+ }
+ if( pUrlData->isSsh ){
+ urlFlags &= ~URL_ASK_REMEMBER_PW;
+ }
+ zLogin = mprintf("%t@", pUrlData->user);
+ for(j=i+1; (c=zUrl[j])!=0 && c!='/' && c!=':'; j++){}
+ pUrlData->name = mprintf("%.*s", j-i-1, &zUrl[i+1]);
+ i = j;
+ }else{
+ for(i=iStart; (c=zUrl[i])!=0 && c!='/' && c!=':'; i++){}
+ pUrlData->name = mprintf("%.*s", i-iStart, &zUrl[iStart]);
+ zLogin = mprintf("");
+ }
+ url_tolower(pUrlData->name);
+ if( c==':' ){
+ pUrlData->port = 0;
+ i++;
+ while( (c = zUrl[i])!=0 && fossil_isdigit(c) ){
+ pUrlData->port = pUrlData->port*10 + c - '0';
+ i++;
+ }
+ pUrlData->hostname = mprintf("%s:%d", pUrlData->name, pUrlData->port);
+ }else{
+ pUrlData->port = pUrlData->dfltPort;
+ pUrlData->hostname = pUrlData->name;
+ }
+ dehttpize(pUrlData->name);
+ pUrlData->path = mprintf("%s", &zUrl[i]);
+ for(i=0; pUrlData->path[i] && pUrlData->path[i]!='?'; i++){}
+ if( pUrlData->path[i] ){
+ pUrlData->path[i] = 0;
+ i++;
+ }
+ zExe = mprintf("");
+ while( pUrlData->path[i]!=0 ){
+ char *zName, *zValue;
+ zName = &pUrlData->path[i];
+ zValue = zName;
+ while( pUrlData->path[i] && pUrlData->path[i]!='=' ){ i++; }
+ if( pUrlData->path[i]=='=' ){
+ pUrlData->path[i] = 0;
+ i++;
+ zValue = &pUrlData->path[i];
+ while( pUrlData->path[i] && pUrlData->path[i]!='&' ){ i++; }
+ }
+ if( pUrlData->path[i] ){
+ pUrlData->path[i] = 0;
+ i++;
+ }
+ if( fossil_strcmp(zName,"fossil")==0 ){
+ pUrlData->fossil = zValue;
+ dehttpize(pUrlData->fossil);
+ zExe = mprintf("%cfossil=%T", cQuerySep, pUrlData->fossil);
+ cQuerySep = '&';
+ }
+ }
+
+ dehttpize(pUrlData->path);
+ if( pUrlData->dfltPort==pUrlData->port ){
+ pUrlData->canonical = mprintf(
+ "%s://%s%T%T%s",
+ pUrlData->protocol, zLogin, pUrlData->name, pUrlData->path, zExe
+ );
+ }else{
+ pUrlData->canonical = mprintf(
+ "%s://%s%T:%d%T%s",
+ pUrlData->protocol, zLogin, pUrlData->name, pUrlData->port,
+ pUrlData->path, zExe
+ );
+ }
+ if( pUrlData->isSsh && pUrlData->path[1] ) pUrlData->path++;
+ free(zLogin);
+ }else if( strncmp(zUrl, "file:", 5)==0 ){
+ pUrlData->isFile = 1;
+ if( zUrl[5]=='/' && zUrl[6]=='/' ){
+ i = 7;
+ }else{
+ i = 5;
+ }
+ zFile = mprintf("%s", &zUrl[i]);
+ }else if( file_isfile(zUrl) ){
+ pUrlData->isFile = 1;
+ zFile = mprintf("%s", zUrl);
+ }else if( file_isdir(zUrl)==1 ){
+ zFile = mprintf("%s/FOSSIL", zUrl);
+ if( file_isfile(zFile) ){
+ pUrlData->isFile = 1;
+ }else{
+ free(zFile);
+ fossil_fatal("unknown repository: %s", zUrl);
+ }
+ }else{
+ fossil_fatal("unknown repository: %s", zUrl);
+ }
+ if( urlFlags ) pUrlData->flags = urlFlags;
+ if( pUrlData->isFile ){
+ Blob cfile;
+ dehttpize(zFile);
+ file_canonical_name(zFile, &cfile, 0);
+ free(zFile);
+ pUrlData->protocol = "file";
+ pUrlData->path = "";
+ pUrlData->name = mprintf("%b", &cfile);
+ pUrlData->canonical = mprintf("file://%T", pUrlData->name);
+ blob_reset(&cfile);
+ }else if( pUrlData->user!=0 && pUrlData->passwd==0 && (urlFlags & URL_PROMPT_PW) ){
+ url_prompt_for_password_local(pUrlData);
+ }else if( pUrlData->user!=0 && ( urlFlags & URL_ASK_REMEMBER_PW ) ){
+ if( isatty(fileno(stdin)) ){
+ if( save_password_prompt(pUrlData->passwd) ){
+ pUrlData->flags = urlFlags |= URL_REMEMBER_PW;
+ }else{
+ pUrlData->flags = urlFlags &= ~URL_REMEMBER_PW;
+ }
+ }
+ }
+}
/*
** Parse the given URL, which describes a sync server. Populate variables
** in the global "g" structure as follows:
**
@@ -79,175 +294,11 @@
**
** ssh://userid@host:port/path?fossil=path/to/fossil.exe
**
*/
void url_parse(const char *zUrl, unsigned int urlFlags){
- int i, j, c;
- char *zFile = 0;
-
- if( zUrl==0 ){
- zUrl = db_get("last-sync-url", 0);
- if( zUrl==0 ) return;
- if( g.urlPasswd==0 ){
- g.urlPasswd = unobscure(db_get("last-sync-pw", 0));
- }
- }
-
- if( strncmp(zUrl, "http://", 7)==0
- || strncmp(zUrl, "https://", 8)==0
- || strncmp(zUrl, "ssh://", 6)==0
- ){
- int iStart;
- char *zLogin;
- char *zExe;
- char cQuerySep = '?';
-
- g.urlIsFile = 0;
- if( zUrl[4]=='s' ){
- g.urlIsHttps = 1;
- g.urlProtocol = "https";
- g.urlDfltPort = 443;
- iStart = 8;
- }else if( zUrl[0]=='s' ){
- g.urlIsSsh = 1;
- g.urlProtocol = "ssh";
- g.urlDfltPort = 22;
- g.urlFossil = "fossil";
- iStart = 6;
- }else{
- g.urlIsHttps = 0;
- g.urlProtocol = "http";
- g.urlDfltPort = 80;
- iStart = 7;
- }
- for(i=iStart; (c=zUrl[i])!=0 && c!='/' && c!='@'; i++){}
- if( c=='@' ){
- /* Parse up the user-id and password */
- for(j=iStart; jurl);
}
/*
-** Prompt the user for the password for g.urlUser. Store the result
-** in g.urlPasswd.
-*/
-void url_prompt_for_password(void){
- if( g.urlIsSsh || g.urlIsFile ) return;
- if( isatty(fileno(stdin))
- && (g.urlFlags & URL_PROMPT_PW)!=0
- && (g.urlFlags & URL_PROMPTED)==0
- ){
- g.urlFlags |= URL_PROMPTED;
- g.urlPasswd = prompt_for_user_password(g.urlUser);
- if( g.urlPasswd[0]
- && (g.urlFlags & (URL_REMEMBER|URL_ASK_REMEMBER_PW))!=0
- ){
- if( save_password_prompt() ){
- g.urlFlags |= URL_REMEMBER_PW;
- }else{
- g.urlFlags &= ~URL_REMEMBER_PW;
+** Prompt the user for the password that corresponds to the "user" member of
+** the provided UrlData structure. Store the result into the "passwd" member
+** of the provided UrlData structure.
+*/
+void url_prompt_for_password_local(UrlData *pUrlData){
+ if( pUrlData->isSsh || pUrlData->isFile ) return;
+ if( isatty(fileno(stdin))
+ && (pUrlData->flags & URL_PROMPT_PW)!=0
+ && (pUrlData->flags & URL_PROMPTED)==0
+ ){
+ pUrlData->flags |= URL_PROMPTED;
+ pUrlData->passwd = prompt_for_user_password(pUrlData->user);
+ if( pUrlData->passwd[0]
+ && (pUrlData->flags & (URL_REMEMBER|URL_ASK_REMEMBER_PW))!=0
+ ){
+ if( save_password_prompt(pUrlData->passwd) ){
+ pUrlData->flags |= URL_REMEMBER_PW;
+ }else{
+ pUrlData->flags &= ~URL_REMEMBER_PW;
}
}
}else{
fossil_fatal("missing or incorrect password for user \"%s\"",
- g.urlUser);
+ pUrlData->user);
}
}
+
+/*
+** Prompt the user for the password for g.urlUser. Store the result
+** in g.urlPasswd.
+*/
+void url_prompt_for_password(void){
+ url_prompt_for_password_local(GLOBAL_URL());
+}
/*
** Remember the URL and password if requested.
*/
void url_remember(void){
Index: src/user.c
==================================================================
--- src/user.c
+++ src/user.c
@@ -132,15 +132,15 @@
}
/*
** Prompt to save Fossil user password
*/
-int save_password_prompt(){
+int save_password_prompt(const char *passwd){
Blob x;
char c;
const char *old = db_get("last-sync-pw", 0);
- if( (old!=0) && fossil_strcmp(unobscure(old), g.urlPasswd)==0 ){
+ if( (old!=0) && fossil_strcmp(unobscure(old), passwd)==0 ){
return 0;
}
prompt_user("remember password (Y/n)? ", &x);
c = blob_str(&x)[0];
blob_reset(&x);
Index: src/wiki.c
==================================================================
--- src/wiki.c
+++ src/wiki.c
@@ -294,11 +294,11 @@
moderation_table_create();
db_multi_exec("INSERT INTO modreq(objid) VALUES(%d)", nrid);
}
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", nrid);
- manifest_crosslink(nrid, pWiki);
+ manifest_crosslink(nrid, pWiki, MC_NONE);
}
/*
** Formal names and common names for the various wiki styles.
*/
Index: src/xfer.c
==================================================================
--- src/xfer.c
+++ src/xfer.c
@@ -191,11 +191,11 @@
if( rid==0 ){
blob_appendf(&pXfer->err, "%s", g.zErrMsg);
blob_reset(&content);
}else{
if( !isPriv ) content_make_public(rid);
- manifest_crosslink(rid, &content);
+ manifest_crosslink(rid, &content, MC_NONE);
}
assert( blob_is_reset(&content) );
remote_has(rid);
}
@@ -820,33 +820,73 @@
static void server_private_xfer_not_authorized(void){
@ error not\sauthorized\sto\ssync\sprivate\scontent
}
/*
-** Run the specified TH1 script, if any, and returns the return code or TH_OK
-** when there is no script.
-*/
-static int run_script(const char *zScript){
- if( !zScript ){
- return TH_OK; /* No script, return success. */
- }
- Th_FossilInit(TH_INIT_DEFAULT); /* Make sure TH1 is ready. */
- return Th_Eval(g.interp, 0, zScript, -1);
-}
-
-/*
-** Run the pre-transfer TH1 script, if any, and returns the return code.
-*/
-static int run_common_script(void){
- return run_script(db_get("xfer-common-script", 0));
-}
-
-/*
-** Run the post-push TH1 script, if any, and returns the return code.
-*/
-static int run_push_script(void){
- return run_script(db_get("xfer-push-script", 0));
+** Return the common TH1 code to evaluate prior to evaluating any other
+** TH1 transfer notification scripts.
+*/
+const char *xfer_common_code(void){
+ return db_get("xfer-common-script", 0);
+}
+
+/*
+** Return the TH1 code to evaluate when a push is processed.
+*/
+const char *xfer_push_code(void){
+ return db_get("xfer-push-script", 0);
+}
+
+/*
+** Return the TH1 code to evaluate when a commit is processed.
+*/
+const char *xfer_commit_code(void){
+ return db_get("xfer-commit-script", 0);
+}
+
+/*
+** Return the TH1 code to evaluate when a ticket change is processed.
+*/
+const char *xfer_ticket_code(void){
+ return db_get("xfer-ticket-script", 0);
+}
+
+/*
+** Run the specified TH1 script, if any, and returns 1 on error.
+*/
+int xfer_run_script(const char *zScript, const char *zUuid){
+ int result;
+ if( !zScript ) return TH_OK;
+ Th_FossilInit(TH_INIT_DEFAULT);
+ if( zUuid ){
+ result = Th_SetVar(g.interp, "uuid", -1, zUuid, -1);
+ if( result!=TH_OK ){
+ fossil_error(1, "%s", Th_GetResult(g.interp, 0));
+ return result;
+ }
+ }
+ result = Th_Eval(g.interp, 0, zScript, -1);
+ if( result!=TH_OK ){
+ fossil_error(1, "%s", Th_GetResult(g.interp, 0));
+ }
+ return result;
+}
+
+/*
+** Runs the pre-transfer TH1 script, if any, and returns its return code.
+** This script may be run multiple times. If the script performs actions
+** that cannot be redone, it should use an internal [if] guard similar to
+** the following:
+**
+** if {![info exists common_done]} {
+** # ... code here
+** set common_done 1
+** }
+*/
+int xfer_run_common_script(void){
+ Th_FossilInit(TH_INIT_DEFAULT);
+ return xfer_run_script(xfer_common_code(), 0);
}
/*
** If this variable is set, disable login checks. Used for debugging
** only.
@@ -875,10 +915,11 @@
int isClone = 0;
int nGimme = 0;
int size;
int recvConfig = 0;
char *zNow;
+ int result;
if( fossil_strcmp(PD("REQUEST_METHOD","POST"),"POST") ){
fossil_redirect_home();
}
g.zLogin = "anonymous";
@@ -904,13 +945,14 @@
db_begin_transaction();
db_multi_exec(
"CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
);
manifest_crosslink_begin();
- if( run_common_script()==TH_ERROR ){
+ result = xfer_run_common_script();
+ if( result==TH_ERROR ){
cgi_reset_content();
- @ error common\sscript\sfailed:\s%F(Th_GetResult(g.interp, 0))
+ @ error common\sscript\sfailed:\s%F(g.zErrMsg)
nErr++;
}
while( blob_line(xfer.pIn, &xfer.line) ){
if( blob_buffer(&xfer.line)[0]=='#' ) continue;
if( blob_size(&xfer.line)==0 ) continue;
@@ -1231,14 +1273,17 @@
}
blobarray_reset(xfer.aToken, xfer.nToken);
blob_reset(&xfer.line);
}
if( isPush ){
- if( run_push_script()==TH_ERROR ){
- cgi_reset_content();
- @ error push\sscript\sfailed:\s%F(Th_GetResult(g.interp, 0))
- nErr++;
+ if( result==TH_OK ){
+ result = xfer_run_script(xfer_push_code(), 0);
+ if( result==TH_ERROR ){
+ cgi_reset_content();
+ @ error push\sscript\sfailed:\s%F(g.zErrMsg)
+ nErr++;
+ }
}
request_phantoms(&xfer, 500);
}
if( isClone && nGimme==0 ){
/* The initial "clone" message from client to server contains no
@@ -1881,13 +1926,13 @@
fossil_force_newline();
fossil_print(
"%s finished with %lld bytes sent, %lld bytes received\n",
zOpType, nSent, nRcvd);
- transport_close();
- transport_global_shutdown();
+ transport_close(GLOBAL_URL());
+ transport_global_shutdown(GLOBAL_URL());
db_multi_exec("DROP TABLE onremote");
manifest_crosslink_end();
content_enable_dephantomize(1);
db_end_transaction(0);
return nErr;
}
Index: src/xfersetup.c
==================================================================
--- src/xfersetup.c
+++ src/xfersetup.c
@@ -31,16 +31,63 @@
if( !g.perm.Setup ){
login_needed();
}
style_header("Transfer Setup");
+
@
setup_menu_entry("Common", "xfersetup_com",
"Common TH1 code run before all transfer request processing.");
setup_menu_entry("Push", "xfersetup_push",
"Specific TH1 code to run after \"push\" transfer requests.");
+ setup_menu_entry("Commit", "xfersetup_commit",
+ "Specific TH1 code to run after processing a commit.");
+ setup_menu_entry("Ticket", "xfersetup_ticket",
+ "Specific TH1 code to run after processing a ticket change.");
@
+
+ url_parse(0, 0);
+ if( g.urlProtocol ){
+ unsigned syncFlags;
+ const char *zButton;
+ char *zWarning;
+
+ if( db_get_boolean("dont-push", 0) ){
+ syncFlags = SYNC_PULL;
+ zButton = "Pull";
+ zWarning = 0;
+ }else{
+ syncFlags = SYNC_PUSH | SYNC_PULL;
+ zButton = "Synchronize";
+ zWarning = mprintf("WARNING: Pushing to \"%s\" is enabled.",
+ g.urlCanonical);
+ }
+ if( P("sync") ){
+ user_select();
+ url_enable_proxy(0);
+ client_sync(syncFlags, 0, 0);
+ }
+ @ Press the %h(zButton) button below to synchronize with the
+ @ "%h(g.urlCanonical)" repository now. This may be useful when
+ @ testing the various transfer scripts.
+ @ You can use the "http -async" command in your scripts, but
+ @ make sure the "th1-uri-regexp" setting is set first.
+ if( zWarning ){
+ @
+ @ %h(zWarning)
+ free(zWarning);
+ }
+ @
+ @
+ @
+ @
+ @
+ }
+
style_footer();
}
/*
** Common implementation for the transfer setup editor pages.
@@ -140,9 +187,49 @@
"Transfer Push Script",
"xfer-push-script",
zDefaultXferPush,
zDesc,
0,
+ 0,
+ 30
+ );
+}
+
+static const char *zDefaultXferCommit = 0;
+
+/*
+** WEBPAGE: xfersetup_commit
+*/
+void xfersetup_commit_page(void){
+ static const char zDesc[] =
+ @ Enter TH1 script that runs when a commit is processed.
+ ;
+ xfersetup_generic(
+ "Transfer Commit Script",
+ "xfer-commit-script",
+ zDefaultXferCommit,
+ zDesc,
+ 0,
+ 0,
+ 30
+ );
+}
+
+static const char *zDefaultXferTicket = 0;
+
+/*
+** WEBPAGE: xfersetup_ticket
+*/
+void xfersetup_ticket_page(void){
+ static const char zDesc[] =
+ @ Enter TH1 script that runs when a ticket change is processed.
+ ;
+ xfersetup_generic(
+ "Transfer Ticket Script",
+ "xfer-ticket-script",
+ zDefaultXferTicket,
+ zDesc,
+ 0,
0,
30
);
}