Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -330,29 +330,146 @@ if(g.db){ db_close(0); } } +#if defined(_WIN32) /* -** Convert all arguments from mbcs to UTF-8. Then +** Parse the command-line arguments passed to windows. We do this +** ourselves to work around bugs in the command-line parsing of MinGW. +** It is possible (in theory) to only use this routine when compiling +** with MinGW and to use built-in command-line parsing for MSVC and +** MinGW-64. However, the code is here, it is efficient, and works, and +** by using it in all cases we do a better job of testing it. If you suspect +** a bug in this code, test your theory by invoking "fossil test-echo". +** +** This routine is copied from TCL with some reformatting. +** The original comment text follows: +** +** Parse the Windows command line string into argc/argv. Done here +** because we don't trust the builtin argument parser in crt0. Windows +** applications are responsible for breaking their command line into +** arguments. +** +** 2N backslashes + quote -> N backslashes + begin quoted string +** 2N + 1 backslashes + quote -> literal +** N backslashes + non-quote -> literal +** quote + quote in a quoted string -> single quote +** quote + quote not in quoted string -> empty string +** quote -> begin quoted string +** +** Results: +** Fills argcPtr with the number of arguments and argvPtr with the array +** of arguments. +*/ +#include <tchar.h> +#define tchar_isspace(X) ((X)==TEXT(' ') || (X)==TEXT('\t')) +static void parse_windows_command_line( + int *argcPtr, /* Filled with number of argument strings. */ + void *argvPtr /* Filled with argument strings (malloc'd). */ +){ + TCHAR *cmdLine, *p, *arg, *argSpace; + TCHAR **argv; + int argc, size, inquote, copy, slashes; + + cmdLine = GetCommandLine(); + + /* + ** Precompute an overly pessimistic guess at the number of arguments in + ** the command line by counting non-space spans. + */ + size = 2; + for(p=cmdLine; *p!=TEXT('\0'); p++){ + if( tchar_isspace(*p) ){ + size++; + while( tchar_isspace(*p) ){ + p++; + } + if( *p==TEXT('\0') ){ + break; + } + } + } + + argSpace = fossil_malloc(size * sizeof(char*) + + (_tcslen(cmdLine) * sizeof(TCHAR)) + sizeof(TCHAR)); + argv = (TCHAR**)argSpace; + argSpace += size*(sizeof(char*)/sizeof(TCHAR)); + size--; + + p = cmdLine; + for(argc=0; argc<size; argc++){ + argv[argc] = arg = argSpace; + while( tchar_isspace(*p) ){ + p++; + } + if (*p == TEXT('\0')) { + break; + } + inquote = 0; + slashes = 0; + while(1){ + copy = 1; + while( *p==TEXT('\\') ){ + slashes++; + p++; + } + if( *p==TEXT('"') ){ + if( (slashes&1)==0 ){ + copy = 0; + if( inquote && p[1]==TEXT('"') ){ + p++; + copy = 1; + }else{ + inquote = !inquote; + } + } + slashes >>= 1; + } + while( slashes ){ + *arg = TEXT('\\'); + arg++; + slashes--; + } + if( *p==TEXT('\0') || (!inquote && tchar_isspace(*p)) ){ + break; + } + if( copy!=0 ){ + *arg = *p; + arg++; + } + p++; + } + *arg = '\0'; + argSpace = arg + 1; + } + argv[argc] = NULL; + *argcPtr = argc; + *((TCHAR ***)argvPtr) = argv; +} +#endif /* defined(_WIN32) */ + + +/* +** Convert all arguments from mbcs (or unicode) to UTF-8. Then ** search g.argv for arguments "--args FILENAME". If found, then ** (1) remove the two arguments from g.argv ** (2) Read the file FILENAME ** (3) Use the contents of FILE to replace the two removed arguments: ** (a) Ignore blank lines in the file ** (b) Each non-empty line of the file is an argument, except ** (c) If the line begins with "-" and contains a space, it is broken ** into two arguments at the space. */ -static void expand_args_option(int argc, char **argv){ +static void expand_args_option(int argc, void *argv){ Blob file = empty_blob; /* Content of the file */ Blob line = empty_blob; /* One line of the file */ unsigned int nLine; /* Number of lines in the file*/ unsigned int i, j, k; /* Loop counters */ int n; /* Number of bytes in one line */ - char *z; /* General use string pointer */ - char **newArgv; /* New expanded g.argv under construction */ + char *z; /* General use string pointer */ + char **newArgv; /* New expanded g.argv under construction */ char const * zFileName; /* input file name */ FILE * zInFile; /* input FILE */ int foundBom = -1; /* -1= not searched yet, 0 = no; 1=yes */ #ifdef _WIN32 wchar_t buf[MAX_PATH]; @@ -359,13 +476,18 @@ #endif g.argc = argc; g.argv = argv; #ifdef _WIN32 + parse_windows_command_line(&g.argc, &g.argv); GetModuleFileNameW(NULL, buf, MAX_PATH); g.argv[0] = fossil_unicode_to_utf8(buf); +#ifdef UNICODE + for(i=1; i<g.argc; i++) g.argv[i] = fossil_unicode_to_utf8(g.argv[i]); +#else for(i=1; i<g.argc; i++) g.argv[i] = fossil_mbcs_to_utf8(g.argv[i]); +#endif #endif for(i=1; i<g.argc-1; i++){ z = g.argv[i]; if( z[0]!='-' ) continue; z++; @@ -400,11 +522,11 @@ z[n-1] = 0; if (foundBom == -1) { static const char bom[] = { 0xEF, 0xBB, 0xBF }; foundBom = memcmp(z, bom, 3)==0; if( foundBom ) { - z += 3; n -= 3; + z += 3; n -= 3; } } if((n>1) && ('\r'==z[n-2])){ if(n==2) continue /*empty line*/; z[n-2] = 0; @@ -1742,11 +1864,11 @@ ** See also: cgi, http, winsrv */ void cmd_webserver(void){ int iPort, mxPort; /* Range of TCP ports allowed */ const char *zPort; /* Value of the --port option */ - char *zBrowser; /* Name of web browser program */ + 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 */ @@ -1779,11 +1901,11 @@ /* Unix implementation */ if( isUiCmd ){ #if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__) zBrowser = db_get("web-browser", 0); if( zBrowser==0 ){ - static char *azBrowserProg[] = { "xdg-open", "gnome-open", "firefox" }; + static const char *const azBrowserProg[] = { "xdg-open", "gnome-open", "firefox" }; int i; zBrowser = "echo"; for(i=0; i<sizeof(azBrowserProg)/sizeof(azBrowserProg[0]); i++){ if( binaryOnPath(azBrowserProg[i]) ){ zBrowser = azBrowserProg[i]; Index: src/makemake.tcl ================================================================== --- src/makemake.tcl +++ src/makemake.tcl @@ -466,10 +466,15 @@ # With JSON support ifdef FOSSIL_ENABLE_JSON TCC += -DFOSSIL_ENABLE_JSON=1 RCC += -DFOSSIL_ENABLE_JSON=1 endif + +# Fix buggy MinGW command line parsing +ifdef MINGW_BROKEN_MAINARGS +TCC += -DMINGW_BROKEN_MAINARGS +endif #### We add the -static option here so that we can build a static # executable that will run in a chroot jail. # LIB = -static ADDED test/cmdline.test Index: test/cmdline.test ================================================================== --- test/cmdline.test +++ test/cmdline.test @@ -0,0 +1,30 @@ +# +# Copyright (c) 2011 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/ +# +############################################################################ +# +# Test command line parsing +# + +proc cmd-line {testname args} { + set i 1 + foreach {cmdline result} $args { + fossil test-echo {*}$cmdline + test cmd-line-$testname.$i {[lrange [split $::RESULT \n] 2 end]=="\{argv\[2\] = \[$result\]\}"} + incr i + } +} +cmd-line 100 abc abc {"abc"} abc +cmd-line 101 * {*} *.* {*.*} Index: win/Makefile.mingw ================================================================== --- win/Makefile.mingw +++ win/Makefile.mingw @@ -145,10 +145,15 @@ # With JSON support ifdef FOSSIL_ENABLE_JSON TCC += -DFOSSIL_ENABLE_JSON=1 RCC += -DFOSSIL_ENABLE_JSON=1 endif + +# Fix buggy MinGW command line parsing +ifdef MINGW_BROKEN_MAINARGS +TCC += -DMINGW_BROKEN_MAINARGS +endif #### We add the -static option here so that we can build a static # executable that will run in a chroot jail. # LIB = -static