Index: Makefile.in ================================================================== --- Makefile.in +++ Makefile.in @@ -37,15 +37,15 @@ # care about testing the end result, this can be blank. # TCLSH = tclsh LIB = @LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@ -TCC += @EXTRA_CFLAGS@ @CPPFLAGS@ @CFLAGS@ -DHAVE_AUTOCONFIG_H -Dstrcmp=fossil_strcmp +TCC += @EXTRA_CFLAGS@ @CPPFLAGS@ @CFLAGS@ -DHAVE_AUTOCONFIG_H INSTALLDIR = $(DESTDIR)@prefix@/bin USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@ FOSSIL_ENABLE_TCL = @FOSSIL_ENABLE_TCL@ FOSSIL_ENABLE_TCL_STUBS = @FOSSIL_ENABLE_TCL_STUBS@ include $(SRCDIR)/main.mk distclean: clean rm -f autoconfig.h config.log Makefile Index: auto.def ================================================================== --- auto.def +++ auto.def @@ -233,8 +233,9 @@ # Check for getpassphrase() for Solaris 10 where getpass() truncates to 10 chars if {![cc-check-functions getpassphrase]} { # Haiku needs this cc-check-function-in-lib getpass bsd } +cc-check-function-in-lib dlopen dl make-template Makefile.in make-config-header autoconfig.h -auto {USE_* FOSSIL_*} Index: compat/zlib/configure ================================================================== --- compat/zlib/configure +++ compat/zlib/configure @@ -814,5 +814,6 @@ /^mandir *=/s#=.*#=$mandir# /^LDFLAGS *=/s#=.*#=$LDFLAGS# " | sed -e " s/\@VERSION\@/$VER/g; " > zlib.pc +# Index: src/blob.c ================================================================== --- src/blob.c +++ src/blob.c @@ -1104,26 +1104,21 @@ blob_zero(&temp); blob_append(&temp, zUtf8, -1); blob_swap(pBlob, &temp); blob_reset(&temp); #ifdef _WIN32 - }else if( starts_with_utf16le_bom(pBlob, &bomSize) ){ - /* Make sure the blob contains two terminating 0-bytes */ - blob_append(pBlob, "", 1); - zUtf8 = blob_str(pBlob) + bomSize; - zUtf8 = fossil_unicode_to_utf8(zUtf8); - blob_zero(pBlob); - blob_append(pBlob, zUtf8, -1); - fossil_unicode_free(zUtf8); - }else if( starts_with_utf16be_bom(pBlob, &bomSize) ){ - unsigned int i = blob_size(pBlob); + }else if( starts_with_utf16_bom(pBlob, &bomSize) ){ zUtf8 = blob_buffer(pBlob); - while( i > 0 ){ - /* swap bytes of unicode representation */ - char zTemp = zUtf8[--i]; - zUtf8[i] = zUtf8[i-1]; - zUtf8[--i] = zTemp; + if (*((unsigned short *)zUtf8) == 0xfffe) { + /* Found BOM, but with reversed bytes */ + unsigned int i = blob_size(pBlob); + while( i > 0 ){ + /* swap bytes of unicode representation */ + char zTemp = zUtf8[--i]; + zUtf8[i] = zUtf8[i-1]; + zUtf8[--i] = zTemp; + } } /* Make sure the blob contains two terminating 0-bytes */ blob_append(pBlob, "", 1); zUtf8 = blob_str(pBlob) + bomSize; zUtf8 = fossil_unicode_to_utf8(zUtf8); Index: src/checkin.c ================================================================== --- src/checkin.c +++ src/checkin.c @@ -616,30 +616,36 @@ ** of the array. ** ** If there were no arguments passed to [commit], aCommitFile is not ** allocated and remains NULL. Other parts of the code interpret this ** to mean "all files". +** +** Returns 1 if there was a warning, 0 otherwise. */ -void select_commit_files(void){ +int select_commit_files(void){ + int result = 0; if( g.argc>2 ){ - int ii; + int ii, jj=0; Blob b; blob_zero(&b); g.aCommitFile = fossil_malloc(sizeof(int)*(g.argc-1)); for(ii=2; ii<g.argc; ii++){ int iId; file_tree_name(g.argv[ii], &b, 1); iId = db_int(-1, "SELECT id FROM vfile WHERE pathname=%Q", blob_str(&b)); if( iId<0 ){ - fossil_fatal("fossil knows nothing about: %s", g.argv[ii]); + fossil_warning("fossil knows nothing about: %s", g.argv[ii]); + result = 1; + } else { + g.aCommitFile[jj++] = iId; } - g.aCommitFile[ii-2] = iId; blob_reset(&b); } - g.aCommitFile[ii-2] = 0; + g.aCommitFile[jj] = 0; } + return result; } /* ** Make sure the current check-in with timestamp zDate is younger than its ** ancestor identified rid and zUuid. Throw a fatal error if not. @@ -1199,11 +1205,16 @@ ** After the following function call has returned, the Global.aCommitFile[] ** array is allocated to contain the "id" field from the vfile table ** for each file to be committed. Or, if aCommitFile is NULL, all files ** should be committed. */ - select_commit_files(); + if ( select_commit_files() ){ + blob_zero(&ans); + prompt_user("continue (y/N)? ", &ans); + cReply = blob_str(&ans)[0]; + if( cReply!='y' && cReply!='Y' ) fossil_exit(1);; + } isAMerge = db_exists("SELECT 1 FROM vmerge WHERE id=0"); if( g.aCommitFile && isAMerge ){ fossil_fatal("cannot do a partial commit of a merge"); } Index: src/cson_amalgamation.c ================================================================== --- src/cson_amalgamation.c +++ src/cson_amalgamation.c @@ -4349,11 +4349,11 @@ { return 0; } else { - unsigned char * x = (unsigned char *)realloc( buf->mem, n ); + unsigned char * x = (unsigned char *)cson_realloc( buf->mem, n, "cson_buffer::mem" ); if( ! x ) return cson_rc.AllocError; memset( x + buf->used, 0, n - buf->used ); buf->mem = x; buf->capacity = n; ++buf->timesExpanded; Index: src/diff.c ================================================================== --- src/diff.c +++ src/diff.c @@ -359,53 +359,19 @@ ** This function returns non-zero if the blob starts with a UTF-16le or ** UTF-16be byte-order-mark (BOM). */ int starts_with_utf16_bom(const Blob *pContent, int *pnByte){ const char *z = blob_buffer(pContent); - int c1, c2; + int c1; if( pnByte ) *pnByte = 2; - if( blob_size(pContent)<2 ) return 0; - c1 = z[0]; c2 = z[1]; - if( (c1==(char)0xff) && (c2==(char)0xfe) ){ - return 1; - }else if( (c1==(char)0xfe) && (c2==(char)0xff) ){ - return 1; - } - return 0; -} - -/* -** This function returns non-zero if the blob starts with a UTF-16le -** byte-order-mark (BOM). -*/ -int starts_with_utf16le_bom(const Blob *pContent, int *pnByte){ - const char *z = blob_buffer(pContent); - int c1, c2; - - if( pnByte ) *pnByte = 2; - if( blob_size(pContent)<2 ) return 0; - c1 = z[0]; c2 = z[1]; - if( (c1==(char)0xff) && (c2==(char)0xfe) ){ - return 1; - } - return 0; -} - -/* -** This function returns non-zero if the blob starts with a UTF-16be -** byte-order-mark (BOM). -*/ -int starts_with_utf16be_bom(const Blob *pContent, int *pnByte){ - const char *z = blob_buffer(pContent); - int c1, c2; - - if( pnByte ) *pnByte = 2; - if( blob_size(pContent)<2 ) return 0; - c1 = z[0]; c2 = z[1]; - if( (c1==(char)0xfe) && (c2==(char)0xff) ){ - return 1; + if( (blob_size(pContent)<2) || (blob_size(pContent)&1)) return 0; + c1 = ((unsigned short *)z)[0]; + if( (c1==0xfeff) || (c1==0xfffe) ){ + if( blob_size(pContent) < 4 ) return 1; + c1 = ((unsigned short *)z)[1]; + if( c1 != 0 ) return 1; } return 0; } /* Index: src/http.c ================================================================== --- src/http.c +++ src/http.c @@ -130,11 +130,11 @@ ** ** The server address is contain in the "g" global structure. The ** url_parse() routine should have been called prior to this routine ** in order to fill this structure appropriately. */ -int http_exchange(Blob *pSend, Blob *pReply, int useLogin){ +int http_exchange(Blob *pSend, Blob *pReply, int useLogin, int maxRedirect){ Blob login; /* The login card */ Blob payload; /* The complete payload including login card */ Blob hdr; /* The HTTP request header */ int closeConnection; /* True to close the connection when done */ int iLength; /* Length of the reply payload */ @@ -231,10 +231,14 @@ }else if( c=='k' || c=='K' ){ closeConnection = 0; } }else if( rc==302 && fossil_strnicmp(zLine, "location:", 9)==0 ){ int i, j; + + if ( --maxRedirect == 0){ + fossil_fatal("redirect limit exceeded"); + } for(i=9; zLine[i] && zLine[i]==' '; i++){} if( zLine[i]==0 ) fossil_fatal("malformed redirect: %s", zLine); j = strlen(zLine) - 1; while( j>4 && fossil_strcmp(&zLine[j-4],"/xfer")==0 ){ j -= 4; @@ -241,11 +245,11 @@ zLine[j] = 0; } fossil_print("redirect to %s\n", &zLine[i]); url_parse(&zLine[i]); transport_close(); - return http_exchange(pSend, pReply, useLogin); + return http_exchange(pSend, pReply, useLogin, maxRedirect); }else if( fossil_strnicmp(zLine, "content-type: ", 14)==0 ){ if( fossil_strnicmp(&zLine[14], "application/x-fossil-debug", -1)==0 ){ isCompressed = 0; }else if( fossil_strnicmp(&zLine[14], "application/x-fossil-uncompressed", -1)==0 ){ Index: src/http_transport.c ================================================================== --- src/http_transport.c +++ src/http_transport.c @@ -183,10 +183,11 @@ ** and run an SSH command 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 */ zSsh = db_get("ssh-command", zDefaultSshCmd); blob_init(&zCmd, zSsh, -1); if( g.urlPort!=g.urlDfltPort ){ #ifdef __MINGW32__ @@ -219,22 +220,63 @@ } #endif }else{ zHost = mprintf("%s", g.urlName); } + n = blob_size(&zCmd); blob_append(&zCmd, " ", 1); shell_escape(&zCmd, zHost); - fossil_print(" %s\n", zHost); /* Show the conclusion of the SSH command */ + if( g.urlShell ){ + blob_appendf(&zCmd, " %s", g.urlShell); + }else{ +#if defined(FOSSIL_ENABLE_SSH_FAR_SIDE) + /* The following works. But only if the fossil on the remote side + ** is recent enough to support the test-ssh-far-side command. That + ** command was added on 2013-02-06. We will leave this turned off + ** until most fossil servers have upgraded to that version or a later + ** version. The sync will still work as long as the shell on the far + ** side is bash and not tcsh. And if the default far side shell is + ** tcsh, then the shell=/bin/bash query parameter can be used as a + ** work-around. Enable this code after about a year... + */ + blob_appendf(&zCmd, " exec %s test-ssh-far-side", g.urlFossil); +#endif + } + fossil_print("%s\n", blob_str(&zCmd)+n); /* Show tail of SSH command */ free(zHost); popen2(blob_str(&zCmd), &sshIn, &sshOut, &sshPid); if( sshPid==0 ){ fossil_fatal("cannot start ssh tunnel using [%b]", &zCmd); } blob_reset(&zCmd); transport_ssh_startup(); } } + +/* +** COMMAND: test-ssh-far-side +** +** Read lines of input text, one by one, and evaluate each line using +** system(). The ssh: sync protocol uses this on the far side of the +** SSH link. +*/ +void test_ssh_far_side_cmd(void){ + int i = 0; + int got; + char zLine[5000]; + while( i<sizeof(zLine) ){ + got = read(0, zLine+i, 1); + if( got==0 ) return; + if( zLine[i]=='\n' ){ + zLine[i] = 0; + system(zLine); + i = 0; + }else{ + i++; + } + } +} /* ** Open a connection to the server. The server is defined by the following ** global variables: ** @@ -251,13 +293,13 @@ Blob cmd; blob_zero(&cmd); shell_escape(&cmd, g.urlFossil); blob_append(&cmd, " test-http ", -1); shell_escape(&cmd, g.urlPath); - /* printf("%s\n", blob_str(&cmd)); fflush(stdout); */ - fprintf(sshOut, "%s\n", blob_str(&cmd)); + fprintf(sshOut, "%s || true\n", blob_str(&cmd)); fflush(sshOut); + if( g.fSshTrace ) printf("Sent: [%s]\n", blob_str(&cmd)); blob_reset(&cmd); }else if( g.urlIsHttps ){ #ifdef FOSSIL_ENABLE_SSL rc = ssl_open(); if( rc==0 ) transport.isOpen = 1; @@ -403,11 +445,10 @@ int got; if( sshIn ){ int x; int wanted = N; got = 0; - /* printf("want %d bytes...\n", wanted); fflush(stdout); */ while( wanted>0 ){ x = read(sshIn, &zBuf[got], wanted); if( x<=0 ) break; got += x; wanted -= x; @@ -438,11 +479,14 @@ int transport_receive(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; - /* printf("request %d with %d on hand\n", N, onHand); fflush(stdout); */ + if( g.fSshTrace){ + printf("Reading %d bytes with %d on hand... ", N, onHand); + fflush(stdout); + } if( onHand>0 ){ int toMove = onHand; if( toMove>N ) toMove = N; /* printf("bytes on hand: %d of %d\n", toMove, N); fflush(stdout); */ memcpy(zBuf, &transport.pBuf[transport.iCursor], toMove); @@ -460,10 +504,11 @@ if( got>0 ){ nByte += got; transport.nRcvd += got; } } + if( g.fSshTrace ) printf("Got %d bytes\n", nByte); return nByte; } /* ** Load up to N new bytes of content into the transport.pBuf buffer. @@ -532,11 +577,11 @@ } break; } i++; } - /* printf("Got line: [%s]\n", &transport.pBuf[iStart]); */ + if( g.fSshTrace ) printf("Got line: [%s]\n", &transport.pBuf[iStart]); return &transport.pBuf[iStart]; } void transport_global_shutdown(void){ if( g.urlIsSsh && sshPid ){ Index: src/json_detail.h ================================================================== --- src/json_detail.h +++ src/json_detail.h @@ -199,11 +199,11 @@ char const * requestId; char const * resultCode; char const * resultText; char const * timestamp; } FossilJsonKeys_; -const FossilJsonKeys_ FossilJsonKeys; +extern const FossilJsonKeys_ FossilJsonKeys; /* ** A page/command dispatch helper for fossil_json_f() implementations. ** pages must be an array of JsonPageDef commands which we can ** dispatch. The final item in the array MUST have a NULL name Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -135,10 +135,11 @@ int fSshTrace; /* Trace the SSH setup traffic */ int fNoSync; /* Do not do an autosync ever. --nosync */ char *zPath; /* Name of webpage being served */ char *zExtra; /* Extra path information past the webpage name */ char *zBaseURL; /* Full text of the URL being served */ + const char *zAltBase; /* Alternative URL root. --baseurl */ char *zTop; /* Parent directory of zPath */ const char *zContentType; /* The content type of the input HTTP request */ int iErrPriority; /* Priority of current error message */ char *zErrMsg; /* Text of an error message */ int sslNotAvailable; /* SSL is not available. Do not redirect to https: */ @@ -170,11 +171,12 @@ char *urlPath; /* Pathname for http: */ char *urlUser; /* User id for http: */ char *urlPasswd; /* Password for http: */ char *urlCanonical; /* Canonical representation of the URL */ char *urlProxyAuth; /* Proxy-Authorizer: string */ - char *urlFossil; /* The path of the ?fossil=path suffix on ssh: */ + char *urlFossil; /* The fossil query parameter on ssh: */ + char *urlShell; /* The shell query parameter on ssh: */ int dontKeepUrl; /* Do not persist the URL */ const char *zLogin; /* Login name. "" if not logged in. */ const char *zSSLIdentity; /* Value of --ssl-identity option, filename of SSL client identity */ int useLocalauth; /* No login required if from 127.0.0.1 */ @@ -1116,24 +1118,30 @@ ** Set the g.zBaseURL value to the full URL for the toplevel of ** the fossil tree. Set g.zTop to g.zBaseURL without the ** leading "http://" and the host and port. ** ** The g.zBaseURL is normally set based on HTTP_HOST and SCRIPT_NAME -** environment variables. However, if zAltBase is not NULL then it +** environment variables. However, if g.zAltBase is not NULL then it ** is the argument to the --baseurl option command-line option and ** g.zBaseURL and g.zTop is set from that instead. +** +** If g.zAltBase is set, then zRepoName may be a repository name (with a +** leading slash ** but without the .fossil) with is concatenated to the +** root. */ -static void set_base_url(const char *zAltBase){ +static void set_base_url(const char* zRepoName){ int i; const char *zHost; const char *zMode; const char *zCur; - if( g.zBaseURL!=0 ) return; - if( zAltBase ){ + fossil_free(g.zBaseURL); + if( g.zAltBase ){ int i, n, c; - g.zTop = g.zBaseURL = mprintf("%s", zAltBase); + if (!zRepoName) zRepoName = ""; + if (zRepoName[0] == '/') zRepoName++; + g.zTop = g.zBaseURL = mprintf("%s%s", g.zAltBase, zRepoName); if( memcmp(g.zTop, "http://", 7)!=0 && memcmp(g.zTop,"https://",8)!=0 ){ fossil_fatal("argument to --baseurl should be 'http://host/path'" " or 'https://host/path'"); } for(i=n=0; (c = g.zTop[i])!=0; i++){ @@ -1273,10 +1281,11 @@ zPathInfo = PD("PATH_INFO",""); if( !g.repositoryOpen ){ char *zRepo, *zToFree; const char *zOldScript = PD("SCRIPT_NAME", ""); char *zNewScript; + char *zRepoName; int j, k; i64 szFile; i = zPathInfo[0]!=0; while( 1 ){ @@ -1349,10 +1358,14 @@ } return; } break; } + /* Rebuild the base URL to add the repository name to the end. */ + zRepoName = mprintf("%.*s", i, zPathInfo); + set_base_url(zRepoName); + fossil_free(zRepoName); zNewScript = mprintf("%s%.*s", zOldScript, i, zPathInfo); cgi_replace_parameter("PATH_INFO", &zPathInfo[i+1]); zPathInfo += i; cgi_replace_parameter("SCRIPT_NAME", zNewScript); db_open_repository(zRepo); @@ -1362,18 +1375,19 @@ "# new PATH_INFO = [%s]\n" "# new SCRIPT_NAME = [%s]\n", zRepo, zPathInfo, zNewScript); } } + else + set_base_url(0); /* Find the page that the user has requested, construct and deliver that ** page. */ if( g.zContentType && memcmp(g.zContentType, "application/x-fossil", 20)==0 ){ zPathInfo = "/xfer"; } - set_base_url(0); if( zPathInfo==0 || zPathInfo[0]==0 || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){ #ifdef FOSSIL_ENABLE_JSON if(g.json.isJsonMode){ json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1); @@ -1740,11 +1754,10 @@ */ void cmd_http(void){ const char *zIpAddr; const char *zNotFound; const char *zHost; - const char *zAltBase; const char *zFileGlob; /* The winhttp module passes the --files option as --files-urlenc with ** the argument being URL encoded, to avoid wildcard expansion in the ** shell. This option is for internal use and is undocumented. @@ -1758,27 +1771,27 @@ zFileGlob = find_option("files",0,1); } zNotFound = find_option("notfound", 0, 1); g.useLocalauth = find_option("localauth", 0, 0)!=0; g.sslNotAvailable = find_option("nossl", 0, 0)!=0; - zAltBase = find_option("baseurl", 0, 1); - if( zAltBase ) set_base_url(zAltBase); + g.zAltBase = find_option("baseurl", 0, 1); + set_base_url(0); if( find_option("https",0,0)!=0 ) cgi_replace_parameter("HTTPS","on"); zHost = find_option("host", 0, 1); if( zHost ) cgi_replace_parameter("HTTP_HOST",zHost); g.cgiOutput = 1; + g.httpIn = stdin; + g.httpOut = stdout; if( g.argc!=2 && g.argc!=3 && g.argc!=6 ){ fossil_fatal("no repository specified"); } g.fullHttpReply = 1; if( g.argc==6 ){ g.httpIn = fossil_fopen(g.argv[3], "rb"); g.httpOut = fossil_fopen(g.argv[4], "wb"); zIpAddr = g.argv[5]; }else{ - g.httpIn = stdin; - g.httpOut = stdout; zIpAddr = 0; } find_server_repository(0); g.zRepositoryName = enter_chroot_jail(g.zRepositoryName); cgi_handle_http_request(zIpAddr); @@ -1888,11 +1901,10 @@ const char *zBrowser; /* Name of web browser program */ char *zBrowserCmd = 0; /* Command to launch the web browser */ int isUiCmd; /* True if command is "ui", not "server' */ const char *zNotFound; /* The --notfound option or NULL */ int flags = 0; /* Server flags */ - const char *zAltBase; /* Argument to the --baseurl option */ const char *zFileGlob; /* Static content must match this */ #if defined(_WIN32) const char *zStopperFile; /* Name of file used to terminate server */ zStopperFile = find_option("stopper", 0, 1); @@ -1904,14 +1916,12 @@ if( g.thTrace ){ blob_zero(&g.thLog); } zPort = find_option("port", "P", 1); zNotFound = find_option("notfound", 0, 1); - zAltBase = find_option("baseurl", 0, 1); - if( zAltBase ){ - set_base_url(zAltBase); - } + g.zAltBase = find_option("baseurl", 0, 1); + set_base_url(0); if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?"); isUiCmd = g.argv[1][0]=='u'; if( isUiCmd ){ flags |= HTTP_SERVER_LOCALHOST; g.useLocalauth = 1; Index: src/rss.c ================================================================== --- src/rss.c +++ src/rss.c @@ -22,18 +22,37 @@ #include "rss.h" #include <assert.h> /* ** WEBPAGE: timeline.rss +** URL: /timeline.rss/y=TYPE&n=LIMIT&tkt=UUID&tag=TAG&wiki=NAME&name=FILENAME +** +** Produce an RSS feed of the timeline. +** +** TYPE may be: all, ci (show checkins only), t (show tickets only), +** w (show wiki only). LIMIT is the number of items to show. +** +** tkt=UUID filters for only those events for the specified ticket. tag=TAG +** filters for a tag, and wiki=NAME for a wiki page. Only one may be used. +** +** In addition, name=FILENAME filters for a specific file. This may be +** combined with one of the other filters (useful for looking at a specific +** branch). */ + void page_timeline_rss(void){ Stmt q; int nLine=0; char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0; Blob bSQL; const char *zType = PD("y","all"); /* Type of events. All if NULL */ + const char *zTicketUuid = PD("tkt",NULL); + const char *zTag = PD("tag",NULL); + const char *zFilename = PD("name",NULL); + const char *zWiki = PD("wiki",NULL); int nLimit = atoi(PD("n","20")); + int nTagId; const char zSQL1[] = @ SELECT @ blob.rid, @ uuid, @ event.mtime, @@ -62,10 +81,11 @@ if( !g.perm.Read ){ if( g.perm.RdTkt && g.perm.RdWiki ){ blob_append(&bSQL, " AND event.type!='ci'", -1); }else if( g.perm.RdTkt ){ blob_append(&bSQL, " AND event.type=='t'", -1); + }else{ blob_append(&bSQL, " AND event.type=='w'", -1); } }else if( !g.perm.RdWiki ){ if( g.perm.RdTkt ){ @@ -76,10 +96,46 @@ }else if( !g.perm.RdTkt ){ assert( !g.perm.RdTkt &&& g.perm.Read && g.perm.RdWiki ); blob_append(&bSQL, " AND event.type!='t'", -1); } } + + if( zTicketUuid ){ + nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'", + zTicketUuid); + if ( nTagId==0 ){ + nTagId = -1; + } + }else if( zTag ){ + nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'sym-%q*'", + zTag); + if ( nTagId==0 ){ + nTagId = -1; + } + }else if( zWiki ){ + nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'wiki-%q*'", + zWiki); + if ( nTagId==0 ){ + nTagId = -1; + } + }else{ + nTagId = 0; + } + + if( nTagId==-1 ){ + blob_appendf(&bSQL, " AND 0"); + }else if( nTagId!=0 ){ + blob_appendf(&bSQL, " AND (EXISTS(SELECT 1 FROM tagxref" + " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid))", nTagId); + } + + if( zFilename ){ + blob_appendf(&bSQL, + " AND (SELECT mlink.fnid FROM mlink WHERE event.objid=mlink.mid) IN (SELECT fnid FROM filename WHERE name=%Q %s)", + zFilename, filename_collation() + ); + } blob_append( &bSQL, " ORDER BY event.mtime DESC", -1 ); cgi_set_content_type("application/rss+xml"); Index: src/url.c ================================================================== --- src/url.c +++ src/url.c @@ -63,10 +63,11 @@ || 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"; @@ -75,10 +76,11 @@ }else if( zUrl[0]=='s' ){ g.urlIsSsh = 1; g.urlProtocol = "ssh"; g.urlDfltPort = 22; g.urlFossil = "fossil"; + g.urlShell = 0; iStart = 6; }else{ g.urlIsHttps = 0; g.urlProtocol = "http"; g.urlDfltPort = 80; @@ -144,11 +146,18 @@ i++; } if( fossil_strcmp(zName,"fossil")==0 ){ g.urlFossil = zValue; dehttpize(g.urlFossil); - zExe = mprintf("?fossil=%T", g.urlFossil); + zExe = mprintf("%cfossil=%T", cQuerySep, g.urlFossil); + cQuerySep = '&'; + } + if( fossil_strcmp(zName,"shell")==0 ){ + g.urlShell = zValue; + dehttpize(g.urlShell); + zExe = mprintf("%cshell=%T", cQuerySep, g.urlFossil); + cQuerySep = '&'; } } dehttpize(g.urlPath); if( g.urlDfltPort==g.urlPort ){ Index: src/wikiformat.c ================================================================== --- src/wikiformat.c +++ src/wikiformat.c @@ -824,11 +824,12 @@ blob_appendf(pOut, "<%s", aMarkup[p->iCode].zName); for(i=0; i<p->nAttr; i++){ blob_appendf(pOut, " %s", aAttribute[p->aAttr[i].iACode].zName); if( p->aAttr[i].zValue ){ const char *zVal = p->aAttr[i].zValue; - if( p->aAttr[i].iACode==ATTR_SRC && zVal[0]=='/' ){ + if( p->aAttr[i].iACode==ATTR_SRC && zVal[0]=='/' + && (zVal[0]==0 || zVal[1]!='/') ){ blob_appendf(pOut, "=\"%s%s\"", g.zTop, zVal); }else{ blob_appendf(pOut, "=\"%s\"", zVal); } } @@ -1171,10 +1172,11 @@ assert( nClose>=20 ); if( strncmp(zTarget, "http:", 5)==0 || strncmp(zTarget, "https:", 6)==0 || strncmp(zTarget, "ftp:", 4)==0 || strncmp(zTarget, "mailto:", 7)==0 + || strncmp(zTarget, "//", 2)==0 ){ blob_appendf(p->pOut, "<a href=\"%s\">", zTarget); }else if( zTarget[0]=='/' ){ blob_appendf(p->pOut, "<a href=\"%s%h\">", g.zTop, zTarget); }else if( zTarget[0]=='.' Index: src/xfer.c ================================================================== --- src/xfer.c +++ src/xfer.c @@ -20,10 +20,16 @@ #include "config.h" #include "xfer.h" #include <time.h> +/* +** Maximum number of HTTP redirects that any http_exchange() call will +** follow before throwing a fatal error. Most browsers use a limit of 20. +*/ +#define MAX_REDIRECTS 20 + /* ** This structure holds information about the current state of either ** a client or a server that is participating in xfer. */ typedef struct Xfer Xfer; @@ -1480,11 +1486,12 @@ xfer.nIGotSent = 0; if( syncFlags & SYNC_VERBOSE ){ fossil_print("waiting for server..."); } fflush(stdout); - if( http_exchange(&send, &recv, (syncFlags & SYNC_CLONE)==0 || nCycle>0) ){ + if( http_exchange(&send, &recv, (syncFlags & SYNC_CLONE)==0 || nCycle>0, + MAX_REDIRECTS) ){ nErr++; break; } lastPctDone = -1; blob_reset(&send); Index: www/build.wiki ================================================================== --- www/build.wiki +++ www/build.wiki @@ -54,11 +54,11 @@ </ol> <h2>2.0 Compiling</h2> <ol> -<li value="6"> +<li value="5"> <p>Unpack the ZIP or tarball you downloaded then <b>cd</b> into the directory created.</p></li> <li><i>(Optional, unix only)</i> Run <b>./configure</b> to construct a makefile. @@ -101,11 +101,11 @@ </ol> <h2>3.0 Installing</h2> <ol> -<li value="9"> +<li value="8"> <p>The finished binary is named "fossil" (or "fossil.exe" on windows). Put this binary in a directory that is somewhere on your PATH environment variable. It does not matter where.</p>