diff options
| author | cashmere <cashmere@autistici.org> | 2026-09-04 09:05:24 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-09-04 09:05:24 +0200 |
| commit | 4f49b16079b27d7ce633619e4cddfdf2bffbc952 (patch) | |
| tree | c296c1e7f3c3e2f6859c6d3a1b1b208779b89679 /src/url.c | |
| download | relic-4f49b16079b27d7ce633619e4cddfdf2bffbc952.tar.gz | |
Diffstat (limited to 'src/url.c')
| -rw-r--r-- | src/url.c | 861 |
1 files changed, 861 insertions, 0 deletions
diff --git a/src/url.c b/src/url.c new file mode 100644 index 0000000..95cff53 --- /dev/null +++ b/src/url.c @@ -0,0 +1,861 @@ +/* +** Copyright (c) 2007 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code for parsing URLs that appear on the command-line +*/ +#include "config.h" +#include "url.h" +#include <stdio.h> + +#ifdef _WIN32 +#include <io.h> +#endif + +#if INTERFACE +/* +** Flags for url_parse() +*/ +#define URL_PROMPT_PW 0x0001 /* Prompt for password if needed */ +#define URL_REMEMBER 0x0002 /* Remember the url for later reuse */ +#define URL_ASK_REMEMBER_PW 0x0004 /* Ask whether to remember prompted pw */ +#define URL_REMEMBER_PW 0x0008 /* Should remember pw */ +#define URL_PROMPTED 0x0010 /* Prompted for PW already */ +#define URL_OMIT_USER 0x0020 /* Omit the user name from URL */ +#define URL_USE_CONFIG 0x0040 /* Use remembered URLs from CONFIG table */ +#define URL_USE_PARENT 0x0080 /* Use the URL of the parent project */ +#define URL_SSH_PATH 0x0100 /* Include PATH= on SSH syncs */ +#define URL_SSH_RETRY 0x0200 /* This a retry of an SSH */ +#define URL_SSH_EXE 0x0400 /* ssh: URL contains fossil= query param*/ + +/* +** The URL related data used with this subsystem. +*/ +struct UrlData { + int isFile; /* True if a "file:" url */ + int isHttps; /* True if a "https:" url */ + int isSsh; /* True if an "ssh:" url */ + int isAlias; /* Input URL was an alias */ + char *name; /* Hostname for http: or filename for file: */ + char *hostname; /* The HOST: parameter on http headers */ + const char *protocol; /* "http" or "https" or "ssh" or "file" */ + 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: */ + char *subpath; /* Secondary HTTP request path for ssh: and file: */ + char *pwConfig; /* CONFIG table entry that gave us the password */ + unsigned flags; /* Boolean flags controlling URL processing */ + int useProxy; /* Used to remember that a proxy is in use */ + int proxyOrigPort; /* Tunneled port number for https through proxy */ + char *proxyUrlPath; /* Remember path when proxy is use */ + char *proxyUrlCanonical; /* Remember canonical path when proxy is use */ +}; +#endif /* INTERFACE */ + + +/* +** Parse the URL in the zUrl argument. Store results in the pUrlData object. +** Populate members of pUrlData as follows: +** +** isFile True if FILE: +** isHttps True if HTTPS: +** isSsh True if SSH: +** protocol "http" or "https" or "file" or "ssh" +** 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 +** +** If URL_USECONFIG is set and zUrl is NULL or "default", then parse the +** URL stored in last-sync-url and last-sync-pw of the CONFIG table. Or if +** URL_USE_PARENT is also set, then use parent-project-url and +** parent-project-pw from the CONFIG table instead of last-sync-url +** and last-sync-pw. +** +** If URL_USE_CONFIG is set and zUrl is a symbolic name, then look up +** the URL in sync-url:%Q and sync-pw:%Q elements of the CONFIG table where +** %Q is the symbolic name. +** +** This routine differs from url_parse() in that this routine stores the +** results in pUrlData and does not change the values of global variables. +** The url_parse() routine puts its result in g.url. +*/ +void url_parse_local( + const char *zUrl, + unsigned int urlFlags, + UrlData *pUrlData +){ + int i, j, c; + char *zFile = 0; + + memset(pUrlData, 0, sizeof(*pUrlData)); + if( urlFlags & URL_USE_CONFIG ){ + if( zUrl==0 || strcmp(zUrl,"default")==0 ){ + const char *zPwConfig = "last-sync-pw"; + if( urlFlags & URL_USE_PARENT ){ + zUrl = db_get("parent-project-url", 0); + if( zUrl==0 ){ + zUrl = db_get("last-sync-url",0); + }else{ + zPwConfig = "parent-project-pw"; + } + }else{ + zUrl = db_get("last-sync-url", 0); + } + if( zUrl==0 ) return; + if( pUrlData->passwd==0 ){ + pUrlData->passwd = unobscure(db_get(zPwConfig, 0)); + pUrlData->pwConfig = fossil_strdup(zPwConfig); + } + pUrlData->isAlias = 1; + }else{ + char *zKey = sqlite3_mprintf("sync-url:%q", zUrl); + char *zAlt = db_get(zKey, 0); + if( zAlt ){ + pUrlData->pwConfig = mprintf("sync-pw:%q", zUrl); + pUrlData->passwd = unobscure( + db_text(0, "SELECT value FROM config WHERE name='sync-pw:%q'",zUrl) + ); + zUrl = zAlt; + urlFlags |= URL_REMEMBER_PW; + pUrlData->isAlias = 1; + }else{ + pUrlData->isAlias = 0; + } + sqlite3_free(zKey); + } + }else{ + if( zUrl==0 ) return; + } + + if( strncmp(zUrl, "http://", 7)==0 + || strncmp(zUrl, "https://", 8)==0 + || strncmp(zUrl, "ssh://", 6)==0 + ){ + int iStart; + char *zLogin; + char *zExe; + char cQuerySep = '?'; + + 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_strdup("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; j<i && zUrl[j]!=':'; j++){} + pUrlData->user = mprintf("%.*s", j-iStart, &zUrl[iStart]); + dehttpize(pUrlData->user); + if( j<i ){ + if( ( urlFlags & URL_REMEMBER ) && pUrlData->isSsh==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; + } + if( urlFlags & URL_OMIT_USER ){ + zLogin = mprintf(""); + }else{ + 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{ + int inSquare = 0; + int n; + for(i=iStart; (c=zUrl[i])!=0 && c!='/' && (inSquare || c!=':'); i++){ + if( c=='[' ) inSquare = 1; + if( c==']' ) inSquare = 0; + } + pUrlData->name = mprintf("%.*s", i-iStart, &zUrl[iStart]); + n = strlen(pUrlData->name); + if( pUrlData->name[0]=='[' && n>2 && pUrlData->name[n-1]==']' ){ + pUrlData->name++; + pUrlData->name[n-2] = 0; + } + zLogin = mprintf(""); + } + fossil_strtolwr(pUrlData->name); + if( c==':' ){ + pUrlData->port = 0; + i++; + while( (c = zUrl[i])!=0 && fossil_isdigit(c) ){ + pUrlData->port = pUrlData->port*10 + c - '0'; + i++; + } + if( c!=0 && c!='/' ) fossil_fatal("url missing '/' after port number"); + pUrlData->hostname = mprintf("%s:%d", pUrlData->name, pUrlData->port); + }else{ + pUrlData->port = pUrlData->dfltPort; + pUrlData->hostname = pUrlData->name; + } + dehttpize(pUrlData->name); + pUrlData->path = fossil_strdup(&zUrl[i]); + for(i=0; pUrlData->path[i] && pUrlData->path[i]!='?'; i++){} + if( pUrlData->path[i] ){ + pUrlData->path[i] = 0; + i++; + } + zExe = fossil_strdup(""); + 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 ){ + fossil_free(pUrlData->fossil); + pUrlData->fossil = fossil_strdup(zValue); + dehttpize(pUrlData->fossil); + fossil_free(zExe); + zExe = mprintf("%cfossil=%T", cQuerySep, pUrlData->fossil); + cQuerySep = '&'; + urlFlags |= URL_SSH_EXE; + } + } + + dehttpize(pUrlData->path); + if( pUrlData->dfltPort==pUrlData->port ){ + pUrlData->canonical = mprintf( + "%s://%s%T%T%z", + pUrlData->protocol, zLogin, pUrlData->name, pUrlData->path, zExe + ); + }else{ + pUrlData->canonical = mprintf( + "%s://%s%T:%d%T%z", + pUrlData->protocol, zLogin, pUrlData->name, pUrlData->port, + pUrlData->path, zExe + ); + } + if( pUrlData->isSsh && pUrlData->path[1] ){ + char *zOld = pUrlData->path; + pUrlData->path = fossil_strdup(zOld+1); + fossil_free(zOld); + } + free(zLogin); + }else if( strncmp(zUrl, "file:", 5)==0 ){ + pUrlData->isFile = 1; + if( zUrl[5]=='/' && zUrl[6]=='/' ){ + i = 7; + }else{ + i = 5; + } + zFile = fossil_strdup(&zUrl[i]); + }else if( file_isfile(zUrl, ExtFILE) ){ + pUrlData->isFile = 1; + zFile = fossil_strdup(zUrl); + }else if( file_isdir(zUrl, ExtFILE)==1 ){ + zFile = mprintf("%s/FOSSIL", zUrl); + if( file_isfile(zFile, ExtFILE) ){ + pUrlData->isFile = 1; + }else{ + free(zFile); + zFile = 0; + 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); + zFile = 0; + pUrlData->protocol = "file"; + pUrlData->path = mprintf(""); + 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)!=0 ){ + url_prompt_for_password_local(pUrlData); + }else if( pUrlData->user!=0 && ( urlFlags & URL_ASK_REMEMBER_PW ) ){ + if( fossil_isatty(fossil_fileno(stdin)) + && ( urlFlags & URL_REMEMBER_PW )==0 ){ + if( save_password_prompt(pUrlData->passwd) ){ + pUrlData->flags = urlFlags |= URL_REMEMBER_PW; + }else{ + pUrlData->flags = urlFlags &= ~URL_REMEMBER_PW; + } + } + } +} + +/* +** Construct the complete URL for a UrlData object, including the +** login name and password, into memory obtained from fossil_malloc() +** and return a pointer to that URL text. +*/ +char *url_full(const UrlData *p){ + Blob x = BLOB_INITIALIZER; + if( p->isFile || p->user==0 || p->user[0]==0 ){ + return fossil_strdup(p->canonical); + } + blob_appendf(&x, "%s://", p->protocol); + if( p->user && p->user[0] ){ + blob_appendf(&x, "%t", p->user); + if( p->passwd && p->passwd[0] ){ + blob_appendf(&x, ":%t", p->passwd); + } + blob_appendf(&x, "@"); + } + blob_appendf(&x, "%T", p->name); + if( p->dfltPort!=p->port ){ + blob_appendf(&x, ":%d", p->port); + } + blob_appendf(&x, "%T", p->path); + (void)blob_str(&x); + return x.aData; +} + +/* +** Construct a URL for a UrlData object that omits the +** login name and password, into memory obtained from fossil_malloc() +** and return a pointer to that URL text. +*/ +char *url_nouser(const UrlData *p){ + Blob x = BLOB_INITIALIZER; + if( p->isFile || p->user==0 || p->user[0]==0 ){ + return fossil_strdup(p->canonical); + } + blob_appendf(&x, "%s://", p->protocol); + blob_appendf(&x, "%T", p->name); + if( p->dfltPort!=p->port ){ + blob_appendf(&x, ":%d", p->port); + } + blob_appendf(&x, "%T", p->path); + (void)blob_str(&x); + return x.aData; +} + +/* +** SQL function to remove the username/password from a URL +*/ +void url_nouser_func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zOrig = (const char*)sqlite3_value_text(argv[0]); + UrlData x; + if( zOrig==0 ) return; + memset(&x, 0, sizeof(x)); + url_parse_local(zOrig, URL_OMIT_USER, &x); + sqlite3_result_text(context, x.canonical, -1, SQLITE_TRANSIENT); + url_unparse(&x); +} + +/* +** Reclaim malloced memory from a UrlData object +*/ +void url_unparse(UrlData *p){ + if( p==0 ){ + p = &g.url; + } + fossil_free(p->canonical); + fossil_free(p->name); + fossil_free(p->path); + fossil_free(p->user); + fossil_free(p->passwd); + fossil_free(p->fossil); + fossil_free(p->subpath); + fossil_free(p->pwConfig); + memset(p, 0, sizeof(*p)); +} + +/* +** Move a URL parse from one UrlData object to another. +*/ +void url_move_parse(UrlData *pTo, UrlData *pFrom){ + url_unparse(pTo); + memcpy(pTo, pFrom, sizeof(*pTo)); + memset(pFrom, 0, sizeof(*pFrom)); +} + +/* +** Parse the given URL, which describes a sync server. Populate variables +** in the global "g.url" structure as shown below. If zUrl is NULL, then +** parse the URL given in the last-sync-url setting, taking the password +** form last-sync-pw. +** +** g.url.isFile True if FILE: +** g.url.isHttps True if HTTPS: +** g.url.isSsh True if SSH: +** g.url.protocol "http" or "https" or "file" or "ssh" +** g.url.name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE: +** g.url.port TCP port number for HTTP or HTTPS. +** g.url.dfltPort Default TCP port number (80 or 443). +** g.url.path Path name for HTTP or HTTPS. +** g.url.user Userid. +** g.url.passwd Password. +** g.url.hostname HOST:PORT or just HOST if port is the default. +** g.url.canonical The URL in canonical form, omitting the password +** g.url.pwConfig Name of CONFIG table entry containing the password +** +** HTTP url format as follows (HTTPS is the same with a different scheme): +** +** http://userid:password@host:port/path +** +** SSH url format is: +** +** ssh://userid@host:port/path?fossil=path/to/fossil.exe +** +** If URL_USE_CONFIG is set then the URL and password might be pulled from +** the CONFIG table rather than from the zUrl parameter. If zUrl is NULL +** or "default" then the URL is given by the "last-sync-url" setting and +** the password comes form the "last-sync-pw" setting. If zUrl is a symbolic +** name, then the URL comes from "sync-url:NAME" and the password from +** "sync-pw:NAME" where NAME is the input zUrl string. Whenever the +** password is taken from the CONFIG table, the g.url.pwConfig field is +** set to the CONFIG.NAME value from which that password is taken. Otherwise, +** g.url.pwConfig is NULL. +*/ +void url_parse(const char *zUrl, unsigned int urlFlags){ + url_parse_local(zUrl, urlFlags, &g.url); +} + +/* +** Print the content of g.url +*/ +void urlparse_print(int showPw){ + fossil_print("g.url.isFile = %d\n", g.url.isFile); + fossil_print("g.url.isHttps = %d\n", g.url.isHttps); + fossil_print("g.url.isSsh = %d\n", g.url.isSsh); + fossil_print("g.url.protocol = %s\n", g.url.protocol); + fossil_print("g.url.name = %s\n", g.url.name); + fossil_print("g.url.port = %d\n", g.url.port); + fossil_print("g.url.dfltPort = %d\n", g.url.dfltPort); + fossil_print("g.url.hostname = %s\n", g.url.hostname); + fossil_print("g.url.path = %s\n", g.url.path); + fossil_print("g.url.user = %s\n", g.url.user); + if( showPw || g.url.pwConfig==0 ){ + fossil_print("g.url.passwd = %s\n", g.url.passwd); + }else{ + fossil_print("g.url.passwd = ************\n"); + } + fossil_print("g.url.pwConfig = %s\n", g.url.pwConfig); + fossil_print("g.url.canonical = %s\n", g.url.canonical); + fossil_print("g.url.fossil = %s\n", g.url.fossil); + fossil_print("g.url.subpath = %s\n", g.url.subpath); + fossil_print("g.url.flags = 0x%04x\n", g.url.flags); + fossil_print("url_full(g.url) = %z\n", url_full(&g.url)); +} + +/* +** COMMAND: test-urlparser +** +** Usage: %fossil test-urlparser URL ?options? +** +** --prompt-pw Prompt for password if missing +** --remember Store results in last-sync-url +** --show-pw Show the CONFIG-derived password in the output +** --use-config Pull URL and password from the CONFIG table +** --use-parent Use the parent project URL +*/ +void cmd_test_urlparser(void){ + int i; + unsigned fg = 0; + int showPw = 0; + db_must_be_within_tree(); + url_proxy_options(); + if( find_option("remember",0,0) ) fg |= URL_REMEMBER; + if( find_option("prompt-pw",0,0) ) fg |= URL_PROMPT_PW; + if( find_option("use-parent",0,0) ) fg |= URL_USE_PARENT|URL_USE_CONFIG; + if( find_option("use-config",0,0) ) fg |= URL_USE_CONFIG; + if( find_option("show-pw",0,0) ) showPw = 1; + if( (fg & URL_USE_CONFIG)==0 ) showPw = 1; + if( g.argc!=3 && g.argc!=4 ){ + usage("URL"); + } + url_parse(g.argv[2], fg); + for(i=0; i<2; i++){ + urlparse_print(showPw); + if( g.url.isFile || g.url.isSsh ) break; + if( i==0 ){ + fossil_print("********\n"); + url_enable_proxy("Using proxy: "); + } + url_unparse(0); + } +} + +/* +** Proxy specified on the command-line using the --proxy option. +** If there is no --proxy option on the command-line then this +** variable holds a NULL pointer. +*/ +static const char *zProxyOpt = 0; + +/* +** Extract any proxy options from the command-line. +** +** --proxy URL|off +** +** The original purpose of this routine is the above. But this +** also happens to be a convenient place to look for other +** network-related options: +** +** --nosync Temporarily disable "autosync" +** +** --ipv4 Disallow IPv6. Use only IPv4. +** +** --ipv6 Disallow IPv4. Use only IPv6. +** +** --accept-any-cert Disable server SSL cert validation. Accept +** any SSL cert that the server provides. +** WARNING: this option opens you up to +** forged-DNS and man-in-the-middle attacks! +*/ +void url_proxy_options(void){ + zProxyOpt = find_option("proxy", 0, 1); + if( find_option("nosync",0,0) ) g.fNoSync = 1; + if( find_option("ipv4",0,0) ) g.eIPvers = 1; + if( find_option("ipv6",0,0) ) g.eIPvers = 2; +#ifdef FOSSIL_ENABLE_SSL + if( find_option("accept-any-cert",0,0) ){ + ssl_disable_cert_verification(); + } +#endif /* FOSSIL_ENABLE_SSL */ +} + +/* +** If the "proxy" setting is defined, then change the URL settings +** (initialized by a prior call to url_parse()) so that the HTTP +** header will be appropriate for the proxy and so that the TCP/IP +** connection will be opened to the proxy rather than to the server. +** +** If zMsg is not NULL and a proxy is used, then print zMsg followed +** by the canonical name of the proxy (with userid and password suppressed). +*/ +void url_enable_proxy(const char *zMsg){ + const char *zProxy; + zProxy = zProxyOpt; + if( zProxy==0 ){ + zProxy = db_get("proxy", "system"); + if( fossil_strcmp(zProxy, "system")==0 ){ + zProxy = fossil_getenv("http_proxy"); + } + } + if( zProxy && zProxy[0] && !is_false(zProxy) + && !g.url.isSsh && !g.url.isFile ){ + char *zOriginalUrl = g.url.canonical; + char *zOriginalHost = g.url.hostname; + int fOriginalIsHttps = g.url.isHttps; + char *zOriginalUser = g.url.user; + char *zOriginalPasswd = g.url.passwd; + char *zOriginalUrlPath = g.url.path; + int iOriginalPort = g.url.port; + unsigned uOriginalFlags = g.url.flags; + g.url.user = 0; + g.url.passwd = ""; + url_parse(zProxy, 0); + if( zMsg ) fossil_print("%s%s\n", zMsg, g.url.canonical); + g.url.path = zOriginalUrl; + g.url.hostname = zOriginalHost; + if( g.url.user ){ + char *zCredentials1 = mprintf("%s:%s", g.url.user, g.url.passwd); + char *zCredentials2 = encode64(zCredentials1, -1); + g.url.proxyAuth = mprintf("Basic %z", zCredentials2); + free(zCredentials1); + } + g.url.user = zOriginalUser; + g.url.passwd = zOriginalPasswd; + g.url.isHttps = fOriginalIsHttps; + g.url.useProxy = 1; + g.url.proxyUrlCanonical = zOriginalUrl;; + g.url.proxyUrlPath = zOriginalUrlPath; + g.url.proxyOrigPort = iOriginalPort; + g.url.flags = uOriginalFlags; + } +} + +#if INTERFACE +/* +** An instance of this object is used to build a URL with query parameters. +*/ +struct HQuery { + Blob url; /* The URL */ + const char *zBase; /* The base URL */ + int nParam; /* Number of parameters. */ + int nAlloc; /* Number of allocated slots */ + const char **azName; /* Parameter names */ + const char **azValue; /* Parameter values */ +}; +#endif + +/* +** Initialize the URL object. +*/ +void url_initialize(HQuery *p, const char *zBase){ + memset(p, 0, sizeof(*p)); + blob_zero(&p->url); + p->zBase = zBase; +} + +/* +** Resets the given URL object, deallocating any memory +** it uses. +*/ +void url_reset(HQuery *p){ + blob_reset(&p->url); + fossil_free((void *)p->azName); + fossil_free((void *)p->azValue); + url_initialize(p, p->zBase); +} + +/* +** Add a fixed parameter to an HQuery. Or remove the parameters if zValue==0. +*/ +void url_add_parameter(HQuery *p, const char *zName, const char *zValue){ + int i; + for(i=0; i<p->nParam; i++){ + if( fossil_strcmp(p->azName[i],zName)==0 ){ + if( zValue==0 ){ + p->nParam--; + p->azValue[i] = p->azValue[p->nParam]; + p->azName[i] = p->azName[p->nParam]; + }else{ + p->azValue[i] = zValue; + } + return; + } + } + assert( i==p->nParam ); + if( zValue==0 ) return; + if( i>=p->nAlloc ){ + p->nAlloc = p->nAlloc*2 + 10; + p->azName = fossil_realloc((void *)p->azName, + sizeof(p->azName[0])*p->nAlloc); + p->azValue = fossil_realloc((void *)p->azValue, + sizeof(p->azValue[0])*p->nAlloc); + } + p->azName[i] = zName; + p->azValue[i] = zValue; + p->nParam++; +} + +/* +** Render the URL with a parameter override. +** +** Returned memory is transient and is overwritten on the next call to this +** routine for the same HQuery, or until the HQuery object is destroyed. +*/ +char *url_render( + HQuery *p, /* Base URL */ + const char *zName1, /* First override */ + const char *zValue1, /* First override value */ + const char *zName2, /* Second override */ + const char *zValue2 /* Second override value */ +){ + const char *zSep = "?"; + int i; + + blob_reset(&p->url); + blob_appendf(&p->url, "%R/%s", p->zBase); + for(i=0; i<p->nParam; i++){ + const char *z = p->azValue[i]; + if( zName1 && fossil_strcmp(zName1,p->azName[i])==0 ){ + zName1 = 0; + z = zValue1; + if( z==0 ) continue; + } + if( zName2 && fossil_strcmp(zName2,p->azName[i])==0 ){ + zName2 = 0; + z = zValue2; + if( z==0 ) continue; + } + blob_appendf(&p->url, "%s%s", zSep, p->azName[i]); + if( z && z[0] ) blob_appendf(&p->url, "=%T", z); + zSep = "&"; + } + if( zName1 && zValue1 ){ + blob_appendf(&p->url, "%s%s", zSep, zName1); + if( zValue1[0] ) blob_appendf(&p->url, "=%T", zValue1); + zSep = "&"; + } + if( zName2 && zValue2 ){ + blob_appendf(&p->url, "%s%s", zSep, zName2); + if( zValue2[0] ) blob_appendf(&p->url, "=%T", zValue2); + } + return blob_str(&p->url); +} + +/* +** 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( fossil_isatty(fossil_fileno(stdin)) + && (pUrlData->flags & URL_PROMPT_PW)!=0 + && (pUrlData->flags & URL_PROMPTED)==0 + ){ + pUrlData->flags |= URL_PROMPTED; + pUrlData->passwd = prompt_for_user_password(pUrlData->canonical); + 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\"", + pUrlData->user); + } +} + +/* +** Prompt the user for the password for g.url.user. Store the result +** in g.url.passwd. +*/ +void url_prompt_for_password(void){ + url_prompt_for_password_local(&g.url); +} + +/* +** Remember the URL and password if requested. +*/ +void url_remember(void){ + if( g.url.flags & URL_REMEMBER ){ + const char *url; + if( g.url.useProxy ){ + url = g.url.proxyUrlCanonical; + }else{ + url = g.url.canonical; + } + if( g.url.flags & URL_USE_PARENT ){ + db_set("parent-project-url", url, 0); + }else{ + db_set("last-sync-url", url, 0); + } + if( g.url.user!=0 && g.url.passwd!=0 && ( g.url.flags & URL_REMEMBER_PW ) ){ + if( g.url.flags & URL_USE_PARENT ){ + db_set("parent-project-pw", obscure(g.url.passwd), 0); + }else{ + db_set("last-sync-pw", obscure(g.url.passwd), 0); + } + } + } +} + +/* Preemptively prompt for a password if a username is given in the +** URL but no password. +*/ +void url_get_password_if_needed(void){ + if( (g.url.user && g.url.user[0]) + && (g.url.passwd==0 || g.url.passwd[0]==0) + && fossil_isatty(fossil_fileno(stdin)) + ){ + url_prompt_for_password(); + } +} + +/* +** Given a URL for a remote repository clone point, try to come up with a +** reasonable basename of a local clone of that repository. +** +** * If the URL has a path, use the tail of the path, with any suffix +** elided. +** +** * If the URL is just a domain name, without a path, then use the +** first element of the domain name, except skip over "www." if +** present and if there is a ".com" or ".org" or similar suffix. +** +** The string returned is obtained from fossil_malloc(). NULL might be +** returned if there is an error. +*/ +char *url_to_repo_basename(const char *zUrl){ + const char *zTail = 0; + int i; + if( zUrl==0 ) return 0; + for(i=0; zUrl[i]; i++){ + if( zUrl[i]=='?' ) break; + if( (zUrl[i]=='/' || zUrl[i]=='@') && zUrl[i+1]!=0 ) zTail = &zUrl[i+1]; + } + if( zTail==0 ) return 0; + if( sqlite3_strnicmp(zTail, "www.", 4)==0 && strchr(zTail+4,'.')!=0 ){ + /* Remove the "www." prefix if there are more "." characters later. + ** But don't remove the "www." prefix if what follows is the suffix. + ** forum:/forumpost/74e111a2ee */ + zTail += 4; + } + if( zTail[0]==0 ) return 0; + for(i=0; zTail[i] && zTail[i]!='.' && zTail[i]!='?' && + zTail[i]!=':' && zTail[i]!='/'; i++){} + if( i==0 ) return 0; + return mprintf("%.*s", i, zTail); +} + +/* +** COMMAND: test-url-basename +** Usage: %fossil test-url-basenames URL ... +** +** This command is used for unit testing of the url_to_repo_basename() +** routine. The command-line arguments are URL, presumably for remote +** Fossil repositories. This command runs url_to_repo_basename() on each +** of those inputs and displays the result. +*/ +void cmd_test_url_basename(void){ + int i; + char *z; + for(i=2; i<g.argc; i++){ + z = url_to_repo_basename(g.argv[i]); + fossil_print("%s -> %s\n", g.argv[i], z); + fossil_free(z); + } +} |
