Question About Quotation Marks

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
Queue
Posts: 31
Joined: 16 Feb 2013 14:31

Re: Question About Quotation Marks

#16 Post by Queue » 05 Mar 2013 22:18

The whole point of the code is to check how the batch was started. Only if it was started ''normally'' i.e. via ShellExecute, double-click in Explorer, or via a command that has to be specifically made to look like those, should the " env var be defined. In other cases, like when run via a command prompt, or from within another batch, the " env var should be undefined.

For fun, this iterative process has been to reduce cases where it all explodes, and explore some of the crazier aspects of cmd.exe's parsing.

Anyhow, I added an incremental poison character replacement to deal with both % and ?.

test35&%%0&;&this.bat

Code: Select all

@setlocal
@echo off
:: @prompt;::$S$T$_

setlocal enabledelayedexpansion
path; & dpath; & set pathext=;
set ""=^" & set "{=!CMDCMDLINE!"
if not "!{:~10,1!"==":" goto:chk
if not "!{:~-2,1!"==" " goto:chk
(::
:;:!CMDCMDLINE:*:=*!!CMDCMDLINE:~0,1!)
2>nul call call(%%%%CMDCMDLINE:**=*%%0%%%%
set "}=!CMDCMDLINE:^^^^=^^!"
if "!}:~3,1!"==":" set ""=:^"
(::
:;:!CMDCMDLINE:~0,1!)
set "}=!{:/=/0!"
set "}=!}:?=/1!"
2>nul call(:^&%%CMDCMDLINE:**=!}:%%=/2!%%
(::
:;:!CMDCMDLINE:^^^^=^^!!CMDCMDLINE:/2=%%!!CMDCMDLINE:/1=?!!CMDCMDLINE:/0=/!)
if !CMDCMDLINE!==!{! echo CMDCMDLINE restored
:chk
endlocal & if not "%"%"=="" ( set ""="" ) else set ""=^"

echo [%"%]

if not defined ^" goto:jmp
"&"^&\..\test35^&%%%%0^&^;^&this.bat test
:jmp

pause
cls
This can be started with:

Code: Select all

cmd /c ^""%~dp0test35&%%%%0&;&this.bat" "/?" ^"
from a second batch file and not choke.

Edit - For my specific use of call, I found a little trick:

Code: Select all

2>nul call call:(%%%%CMDCMDLINE:**=*%%0%%%%
The colon changes the file search to check for call:, which is an invalid file name so it aborts without checking all locations in the search paths (without having to null out the search paths and pathext).

Edit 2 - I went back and retested some earlier breaking filenames, and I rediscovered why I'd used :^&rem; which is to say it does serve a purpose. I'll elaborate when I post my next iteration. I'm also trying to figure out if I can jam it into the double call; an escaped & is no good because of caret doubling.

Queue

Queue
Posts: 31
Joined: 16 Feb 2013 14:31

Re: Question About Quotation Marks

#17 Post by Queue » 06 Mar 2013 18:13

Ok, here's an overly verbose version to play with.

test36.bat

Code: Select all

@setlocal
@echo off
:: echo on & prompt;::$S$T$_

setlocal enabledelayedexpansion
:: path; & dpath; & set pathext=;
set ""=^" & set "{=!CMDCMDLINE!"

echo [!{:*%~dp0=!]

if not "!{:~10,1!"==":" goto chk
if not "!{:~-2,1!"==" " goto chk
(::
!;:"!CMDCMDLINE:*:=*!"   ^
:;:"!CMDCMDLINE:~0,1!"   ^
::)

:: v v v v v v v v ::
::                 ::
:: set "#=^&"
set "#=^&rem;"
::                 ::
:: ^ ^ ^ ^ ^ ^ ^ ^ ::

call call:(%%#%%%%%%CMDCMDLINE:**=*%%0%%%%
(::
!;:"!CMDCMDLINE:^^=^!"   ^
::)
set "}=!CMDCMDLINE!"
if "!}:~3,1!"==":" set ""=:^"
(::
!;:"!CMDCMDLINE:~0,1!"   ^
::)
set "}=!{!"

:: v v v v v v v v ::
::                 ::
set "}=!}:/=/0!"
:: set "}=!}:?=/1!"
:: set "}=!}:&=/2!"
set "}=!}:%%=/3!"
::                 ::
:: ^ ^ ^ ^ ^ ^ ^ ^ ::

call(:%#%%%CMDCMDLINE:**=!}!%%
(::
!;:"!CMDCMDLINE:^^=^!"   ^
:;:"!CMDCMDLINE:/3=%%!"   ^
:;:"!CMDCMDLINE:/2=&!"   ^
:;:"!CMDCMDLINE:/1=?!"   ^
:;:"!CMDCMDLINE:/0=/!"   ^
::)
if not !CMDCMDLINE!==!{! echo [CMDCMDLINE FAILURE]
:chk
endlocal & if not "%"%"=="" ( set ""="" ) else set ""=^"

echo [%"%]

echo - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

if defined _ goto :eof
set _=_

cmd /c ^""%~dp0x&if;exists;2   \..\%~nx0" ^"
cmd /c ^""%~dp0x&%%%%0&;&this   \..\%~nx0" ^"
cmd /c ^""%~dp0x^=&%%%%~.bat   \..\%~nx0" ^"
cmd /c ^""%~dp0x&%%!&echo.bat   \..\%~nx0" "/?" /? ^"

pause
cls
Just give it a trivial file name, like test36.bat. At the end of the batch it calls itself with crazy characters making it easier to test how the code reacts to various different poison characters and character sequences. To simplify the output, I put in a lazy echo [!{:*%~dp0=!]; make sure the folders where test36.bat is located don't contain any poison characters. Remove that line if you insist on running the batch from such a location.

No errors are redirected.

Within the code are two areas marked with

Code: Select all

:: v v v v v v v v ::
:: ^ ^ ^ ^ ^ ^ ^ ^ ::
that denote code that can be commented or uncommented to see how it affects errors. Of note is set "#=^&rem;" which can be toggled to show how a rem is parsed among the contents of a call. The other marked section contains placeholder characters.

The output of the above is:

Code: Select all

[test36.bat" "]
["]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[x&if;exists;2  \..\test36.bat" "]
["]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[x&%%0&;&this   \..\test36.bat" "]
["]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[x^=&%%~.bat    \..\test36.bat" "]
The following usage of the path operator in batch-parameter
substitution is invalid: %~.bat \..\test36.bat"%

For valid formats type CALL /? or FOR /?
["]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[x&%!&echo.bat  \..\test36.bat" "/?" /? "]
["]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press any key to continue . . .
I've inverted the CMDCMDLINE restoration check to only echo if it fails. Comment out set "}=!}:%%=/3!" if you want to see it fail.

Edit - See the exclamation point on the line after (::? While it's well known that a :: on the following line gives an invalid drive letter error, a single : label is actually checking for a file (and failing because a valid file can't start with a colon). With delayed expansion on, starting the line after an intra-parenthesis :: comment with a ! and then at least 1 delimiter (like a semi-colon) SEEMS to be safe. It doesn't trigger error messages, or file access, though it does have some impact on how cmd.exe parses the line as can be seen if you have echo on.

Edit 2 - Apparently the !;: lines should be !;:: otherwise goto ^"^^^!CMDCMDLINE will be able to jump to them. I didn't know the parser was quite THAT aggressive at searching for labels.

Edit 3 - !=:: appears to be an even better option than !;:: since ; can be the name of an env var, while = cannot (correct?). For the use above, ; being defined isn't fatal, but it can be in other situations.

Edit 4 - In my efforts to avoid file i/o I think I actually stumbled upon something useful regarding the call command. If a call has the following form:

Code: Select all

call internalcommand/(whatever
:: example:
call set/(x=hello
it appears to avoid hitting the file system. The downside for set is that your env var has to start with /( and cannot use the set syntax where you wrap it all in quotation marks. I think some characters besides ( may work as well, but I haven't fully explored that yet. The other drawback is the internal command has to be flexible enough to cope with /(. So set works, as does echo, but then every line echo'd will start with (.

It'd be nice to get input from someone else testing to reproduce my results. A simple test scenario is:

Code: Select all

echo::
call echo/(test
echo::
where each echo:: should hit the file system twice (searching for echo: and echo::) and with no file system hits between the echo:: hits.

Queue

Post Reply