I've made a WPAD server Fiddler extension and in a fit of creativity I've named it: WPAD Server Fiddler Extension.
Of course you know about Fiddler, Eric's awesome HTTP debugger tool, the HTTP proxy that lets you inspect, visualize and modify the HTTP traffic that flows through it. And on the subject you've probably definitely heard of WPAD, the Web Proxy Auto Discovery protocol that allows web browsers like IE to use DHCP or DNS to automatically discover HTTP proxies on their network. While working on a particularly nasty WPAD bug towards the end of IE8 I really wished I had a way to see the WPAD requests and responses and modify PAC responses in Fiddler. Well the wishes of me of the past are now fulfilled by present day me as this Fiddler extension will respond to WPAD DHCP requests telling those clients (by default) that Fiddler is their proxy.
When I started working on this project I didn't really understand how DHCP worked especially with respect to WPAD. I won't bore you with my misconceptions: it works by having your one DHCP server on your network respond to regular DHCP requests as well as WPAD DHCP requests. And Windows I've found runs a DHCP client service (you can start/stop it via Start|Run|'services.msc', scroll to DHCP Client or via the command line with "net start/stop 'DHCP Client'") that caches DHCP server responses making it just slightly more difficult to test and debug my extension. If a Windows app uses the DHCP client APIs to ask for the WPAD option, this service will send out a DHCP request and take the first DHCP server response it gets. That means that if you're on a network with a DHCP server, my extension will be racing to respond to the client. If the DHCP server wins then the client ignores the WPAD response from my extension.
Various documents and tools I found useful while working on this:
I built timestamp.exe, a Windows command line tool to convert between computer and human readable date/time formats mostly for working on the first run wizard for IE8. We commonly write out our dates in binary form to the registry and in order to test and debug my work it became useful to be able to determine to what date the binary value of a FILETIME or SYSTEMTIME corresponded or to produce my own binary value of a FILETIME and insert it into the registry.
For instance, to convert to a binary value:
[PS C:\] timestamp -inString 2009/08/28:10:18 -outHexValue -convert filetime
2009/08/28:10:18 as FILETIME: 00 7c c8 d1 c8 27 ca 01
Converting in the other direction, if you don't know what format the bytes are in, just feed them in and timestamp will try all conversions and list only the valid ones:
[PS C:\] timestamp -inHexValue "40 52 1c 3b"
40 52 1c 3b as FILETIME: 1601-01-01:00:01:39.171
40 52 1c 3b as Unix Time: 2001-06-05:03:30:08.000
40 52 1c 3b as DOS Time: 2009-08-28:10:18:00.000
(it also supports OLE Dates, and SYSTEMTIME which aren't listed there because the hex value isn't valid for those types). Or use the guess
option to get timestamp's best guess:
[PS C:\] timestamp -inHexValue "40 52 1c 3b" -convert guess
40 52 1c 3b as DOS Time: 2009-08-28:10:18:00.000
When I first wrote this I had a bug in my function that parses the date-time value string in which I could parse 2009-07-02:10:18 just fine, but I wouldn't be able to parse 2009-09-02:10:18 correctly. This was my code:
success = swscanf_s(timeString, L"%hi%*[\\/- ,]%hi%*[\\/- ,]%hi%*[\\/- ,Tt:.]%hi%*[:.]%hi%*[:.]%hi%*[:.]%hi",
&systemTime->wYear,
&systemTime->wMonth,
&systemTime->wDay,
&systemTime->wHour,
&systemTime->wMinute,
&systemTime->wSecond,
&systemTime->wMilliseconds) > 1;
See the problem?
To convert between these various forms yourself read The Old New Thing date conversion article or Josh Poley's date time article. I previously wrote about date formats I like and dislike.
I knew that the command line tool subst would create virtual drives that map to existing directories but I didn't know that subst lets you name the virtual drives with characters that aren't US-ASCII letters. For instance you can run 'subst 4: C:\windows' and then 'more 4:\win.ini' to dump C:\windows\win.ini. This also works for non-US-ASCII characters like, "C" (aka U+FF23, Fullwidth Latin Capital Letter C), which when displayed by cmd.exe via some best fit style character conversions looks just like the regular US-ASCII 'C'. None of Explorer, IE, or the common file dialogs allow the use of these odd virtual drives -- just cmd.exe, so I'm not sure how this would ever be useful but I thought it was odd and I wanted to share.
PowerShell gives us a real CLI for Windows based around .Net stuff. I don't like the creation of a new shell language but I suppose it makes sense given that they want something C# like but not C# exactly since that's much to verbose and strict for a CLI. One of the functions you can override is the TabExpansion function which is used when you tab complete commands. I really like this and so I've added on to the standard implementation to support replacing a variable name with its value, tab completion of available commands, previous command history, and drive names (there not restricted to just one letter in PS).
Learning the new language was a bit of a chore but MSDN helped. A couple of things to note, a statement that has a return value that you don't do anything with is implicitly the return value for the current function. That's why there's no explicit return's in my TabExpansion function. Also, if you're TabExpansion function fails or returns nothing then the builtin TabExpansion function runs which does just filenames. This is why you can see that the standard TabExpansion function doesn't handle normal filenames: it does extra stuff (like method and property completion on variables that represent .Net objects) but if there's no fancy extra stuff to be done it lets the builtin one take a crack.
Here's my TabExpansion function. Probably has bugs, so watch out!
function EscapePath([string] $path, [string] $original)
{
if ($path.Contains(' ') -and !$original.Contains(' '))
{
'"' $path '"';
}
else
{
$path;
}
}
function PathRelativeTo($pathDest, $pathCurrent)
{
if ($pathDest.PSParentPath.ToString().EndsWith($pathCurrent.Path))
{
'.\' $pathDest.name;
}
else
{
$pathDest.FullName;
}
}
# This is the default function to use for tab expansion. It handles simple
# member expansion on variables, variable name expansion and parameter completion
# on commands. It doesn't understand strings so strings containing ; | ( or { may
# cause expansion to fail.
function TabExpansion($line, $lastWord)
{
switch -regex ($lastWord)
{
# Handle property and method expansion...
'(^.*)(\$(\w|\.) )\.(\w*)$' {
$method = [Management.Automation.PSMemberTypes] `
'Method,CodeMethod,ScriptMethod,ParameterizedProperty'
$base = $matches[1]
$expression = $matches[2]
Invoke-Expression ('$val=' $expression)
$pat = $matches[4] '*'
Get-Member -inputobject $val $pat | sort membertype,name |
where { $_.name -notmatch '^[gs]et_'} |
foreach {
if ($_.MemberType -band $method)
{
# Return a method...
$base $expression '.' $_.name '('
}
else {
# Return a property...
$base $expression '.' $_.name
}
}
break;
}
# Handle variable name expansion...
'(^.*\$)([\w\:]*)$' {
$prefix = $matches[1]
$varName = $matches[2]
foreach ($v in Get-Childitem ('variable:' $varName '*'))
{
if ($v.name -eq $varName)
{
$v.value
}
else
{
$prefix $v.name
}
}
break;
}
# Do completion on parameters...
'^-([\w0-9]*)' {
$pat = $matches[1] '*'
# extract the command name from the string
# first split the string into statements and pipeline elements
# This doesn't handle strings however.
$cmdlet = [regex]::Split($line, '[|;]')[-1]
# Extract the trailing unclosed block e.g. ls | foreach { cp
if ($cmdlet -match '\{([^\{\}]*)$')
{
$cmdlet = $matches[1]
}
# Extract the longest unclosed parenthetical expression...
if ($cmdlet -match '\(([^()]*)$')
{
$cmdlet = $matches[1]
}
# take the first space separated token of the remaining string
# as the command to look up. Trim any leading or trailing spaces
# so you don't get leading empty elements.
$cmdlet = $cmdlet.Trim().Split()[0]
# now get the info object for it...
$cmdlet = @(Get-Command -type 'cmdlet,alias' $cmdlet)[0]
# loop resolving aliases...
while ($cmdlet.CommandType -eq 'alias') {
$cmdlet = @(Get-Command -type 'cmdlet,alias' $cmdlet.Definition)[0]
}
# expand the parameter sets and emit the matching elements
foreach ($n in $cmdlet.ParameterSets | Select-Object -expand parameters)
{
$n = $n.name
if ($n -like $pat) { '-' $n }
}
break;
}
default {
$varNameStar = $lastWord '*';
foreach ($n in @(Get-Childitem $varNameStar))
{
$name = PathRelativeTo ($n) ($PWD);
if ($n.PSIsContainer)
{
EscapePath ($name '\') ($lastWord);
}
else
{
EscapePath ($name) ($lastWord);
}
}
if (!$varNameStar.Contains('\'))
{
foreach ($n in @(Get-Command $varNameStar))
{
if ($n.CommandType.ToString().Equals('Application'))
{
foreach ($ext in @((cat Env:PathExt).Split(';')))
{
if ($n.Path.ToString().ToLower().EndsWith(($ext).ToString().ToLower()))
{
EscapePath($n.Path) ($lastWord);
}
}
}
else
{
EscapePath($n.Name) ($lastWord);
}
}
foreach ($n in @(Get-psdrive $varNameStar))
{
EscapePath($n.name ":") ($lastWord);
}
}
foreach ($n in @(Get-History))
{
if ($n.CommandLine.StartsWith($line) -and $n.CommandLine -ne $line)
{
$lastWord $n.CommandLine.Substring($line.Length);
}
}
# Add the original string to the end of the expansion list.
$lastWord;
break;
}
}
}
Windows allows for application protocols in which, through the registry, you specify a URL scheme and a command line to have that URL passed to your application. Its an easy way to hook a webbrowser up to your application. Anyone can read the doc above and then walk through the registry and pick out the application protocols but just from that info you can't tell what the application expects these URLs to look like. I did a bit of research on some of the application protocols I've seen which is listed below. Good places to look for information on URI schemes: Wikipedia URI scheme, and ESW Wiki UriSchemes.
Scheme | Name | Notes |
---|---|---|
search-ms | Windows Search Protocol |
The search-ms application protocol is a convention for querying the Windows Search index. The protocol enables applications, like Microsoft Windows Explorer, to query the index with
parameter-value arguments, including property arguments, previously saved searches, Advanced Query Syntax, Natural Query Syntax, and language code identifiers (LCIDs) for both the Indexer and
the query itself. See the MSDN docs for search-ms for more info. Example: search-ms:query=food |
Explorer.AssocProtocol.search-ms | ||
OneNote | OneNote Protocol |
From the OneNote help: /hyperlink "pagetarget" - Starts OneNote and opens the page specified by the pagetarget parameter. To obtain the hyperlink for any page in a OneNote
notebook, right-click its page tab and then click Copy Hyperlink to this Page.Example: onenote:///\\GUMMO\Users\davris\Documents\OneNote%20Notebooks\OneNote%202007%20Guide\Getting%20Started%20with%20OneNote.one#section-id={692F45F5-A42A-415B-8C0D-39A10E88A30F}&end |
callto | Callto Protocol |
ESW Wiki Info on callto Skype callto info NetMeeting callto info Example: callto://+12125551234 |
itpc | iTunes Podcast |
Tells iTunes to subscribe to an indicated podcast. iTunes documentation. C:\Program Files\iTunes\iTunes.exe /url "%1" Example: itpc:http://www.npr.org/rss/podcast.php?id=35 |
iTunes.AssocProtocol.itpc | ||
pcast | ||
iTunes.AssocProtocol.pcast | ||
Magnet | Magnet URI | Magnet URL scheme described by Wikipedia. Magnet URLs identify a resource by a hash of that resource so that when used in P2P scenarios no central authority is necessary to create URIs for a resource. |
mailto | Mail Protocol |
RFC 2368 - Mailto URL Scheme. Mailto Syntax Opens mail programs with new message with some parameters filled in, such as the to, from, subject, and body. Example: mailto:?to=david.risney@gmail.com&subject=test&body=Test of mailto syntax |
WindowsMail.Url.Mailto | ||
MMS | mms Protocol |
MSDN describes associated protocols. Wikipedia describes MMS. "C:\Program Files\Windows Media Player\wmplayer.exe" "%L" Also appears to be related to MMS cellphone messages: MMS IETF Draft. |
WMP11.AssocProtocol.MMS | ||
secondlife | [SecondLife] |
Opens SecondLife to the specified location, user, etc. SecondLife Wiki description of the URL scheme. "C:\Program Files\SecondLife\SecondLife.exe" -set SystemLanguage en-us -url "%1" Example: secondlife://ahern/128/128/128 |
skype | Skype Protocol |
Open Skype to call a user or phone number. Skype's documentation Wikipedia summary of skype URL scheme "C:\Program Files\Skype\Phone\Skype.exe" "/uri:%l" Example: skype:+14035551111?call |
skype-plugin | Skype Plugin Protocol Handler |
Something to do with adding plugins to skype? Maybe. "C:\Program Files\Skype\Plugin Manager\skypePM.exe" "/uri:%1" |
svn | SVN Protocol |
Opens TortoiseSVN to browse the repository URL specified in the URL. C:\Program Files\TortoiseSVN\bin\TortoiseProc.exe /command:repobrowser /path:"%1" |
svn+ssh | ||
tsvn | ||
webcal | Webcal Protocol |
Wikipedia describes webcal URL scheme. Webcal URL scheme description. A URL that starts with webcal:// points to an Internet location that contains a calendar in iCalendar format. "C:\Program Files\Windows Calendar\wincal.exe" /webcal "%1" Example: webcal://www.lightstalkers.org/LS.ics |
WindowsCalendar.UrlWebcal.1 | ||
zune | Zune Protocol |
Provides access to some Zune operations such as podcast subscription (via Zune Insider). "c:\Program Files\Zune\Zune.exe" -link:"%1" Example: zune://subscribe/?name=http://feeds.feedburner.com/wallstrip. |
feed | Outlook Add RSS Feed |
Identify a resource that is a feed such as Atom or RSS. Implemented by Outlook to add the indicated feed to Outlook. Feed URI scheme pre-draft document "C:\PROGRA~2\MICROS~1\Office12\OUTLOOK.EXE" /share "%1" |
im | IM Protocol |
RFC 3860 IM URI scheme description Like mailto but for instant messaging clients. Registered by Office Communicator but I was unable to get it to work as described in RFC 3860. "C:\Program Files (x86)\Microsoft Office Communicator\Communicator.exe" "%1" |
tel | Tel Protocol |
RFC 5341 - tel URI scheme IANA assignment RFC 3966 - tel URI scheme description Call phone numbers via the tel URI scheme. Implemented by Office Communicator. "C:\Program Files (x86)\Microsoft Office Communicator\Communicator.exe" "%1" |
This post is about creating a server side z-code interpreter that represents game progress in the URI. Try it with the game Lost Pig.
I enjoy working on URIs and have the mug to prove it. Along those lines I've combined thoughts on URIs with interactive fiction. I have a limited amount of experience with Inform which generates Z-Code so I'll focus on pieces written in that. Of course we can already have URIs identifying the Z-Code files themselves, but I want URIs to identify my place in a piece of interactive fiction. The proper way to do this would be to give Z-Code its own mimetype and associate with that mimetype the format of a fragment that would contain the save state of user's interactive fiction session. A user would install a browser plugin that would generate URIs containing the appropriate fragment while you play the IF piece and be able to load URIs identifying Z-Code files and load the save state that appears in the fragment.
But all of that would be a lot of work, so I made a server side version that approximates this. On the Web Frotz Interpreter page, enter the URI of a Z-Code file to start a game. Enter your commands into the input text box at the bottom and you get a new URI after every command. For example, here's the beginning of Zork. I'm running a slightly modified version of the Unix version of Frotz. Baf's Guide to the IF Archive has lists of IF games to try out.
There are two issues with this thought, the first being the security issues with running arbitrary z-code and the second is the practical URI length limit of about 2K in IE. From the Z-Code standard and the Frotz source it looks like 'save' and 'restore' are the only commands that could do anything interesting outside of the Z-Code virtual machine. As for the length-limit on URIs I'm not sure that much can be done about that. I'm using a base64 encoded copy of the compressed input stream in the URI now. Switching to the actual save state might be smaller after enough user input.
IPv6 address syntax consists of 8 groupings of colon delimited 16-bit hex values making up the 128-bit address. An optional double colon
can replace any consecutive sequence of 0 valued hex values. For example the following is a valid IPv6 address: fe80::2c02:db79
Some IPv6 addresses aren't global and in those cases need a scope ID to describe their context. These get a '%' followed by the scope ID.
For example the previous example with a scope ID of '8' would be: fe80::2c02:db79%8
IPv6 addresses in URIs may appear in the host section of a URI as long as they're enclosed by square brackets. For example:
http://[fe80::2c02:db79]/
. The RFC explicitly notes that there isn't a way to add a scope ID to the IPv6 address in a URI. However a draft document describes adding
scope IDs to IPv6 addresses in URIs. The draft document uses the IPvFuture production from the URI RFC with a 'v1' to add a new
hostname syntax and a '+' instead of a '%' for delimiting the scope id. For example: http://[v1.fe80::2c02:db79+8]/
. However, this is still a draft document, not a final
standard, and I don't know of any system that works this way.
In Windows XPSP2 the IPv6 stack is available but disabled by default. To enable the IPv6 stack, at a command prompt run 'netsh interface ipv6 install'. In Vista IPv6 is the on by default and cannot be turned off, while the IPv4 stack is optional and may be turned off by a command similar to the previous.
Once you have IPv6 on in your OS you can turn on IPv6 for IIS6 or just use IIS7. The address ::1 refers to the local machine.
In some places in Windows like UNC paths, IPv6 addresses aren't allowed. In those cases you can use a Vista DNS IPv6 hack that lives in the OS
name resolution stack that transforms particularly crafted names into IPv6 addresses. Take your IPv6 address, replace the ':'s with '-'s and the '%' with an 's' and then append '.ipv6-literal.net'
to the end. For example: fe80--2c02-db79s8.ipv6-literal.net
. That name will resolve to the same example I've been using in Vista. This transformation occurs inside the system's local
name resolution stack so no DNS servers are involved, although Microsoft does own the ipv6-literal.net domain name.
MSDN describes IPv6 addresses in URIs in Windows and I've described IPv6 addresses in URIs in IE7. File URIs in
IE7 don't support IPv6 addresses. If you want to put a scope ID in a URI in IE7 you use a '%25' to delimit the scope ID and due to a bug you must have at least two digits in your scope ID. So,
to take the previous example: http://[fe80::2c02:db79%2508]/
. Note that its 08 rather than just 8.
C:\windows\system32\wscript.exe C:\users\dave\bin\xsltproc.js C:\Users\Dave\Documents\trunk\development\mce-zune\mce-to-podcast.xslt
C:\ProgramData\Microsoft\eHome\Recording\Recordings.xml --param title "The Daily Show With Jon Stewart" --param max 4 --param baseURI "http://groucho/" --param thisRelURI "tds.xml" -o
"D:\recorded tv\tds.xml"
var createNewXMLObj = function() {
var result = new ActiveXObject("MSXML2.FreeThreadedDOMDocument");
result.validateOnParse = false;
result.async = false;
return result;
}
var args = WScript.arguments;
var ofs = WScript.CreateObject("Scripting.FileSystemObject");
var xslParams = [];
var xmlStyle = null;
var xmlInput = null;
var inputFile = null;
var outputFile = null;
var error = false;
for (var idx = 0; idx < args.length && !error; ++idx)
if (args.item(idx) == "-o") {
if (idx + 1 < args.length) {
outputFile = ofs.GetAbsolutePathName(args.item(idx + 1));
++idx;
}
else
error = true;
}
else if (args.item(idx) == "--param" || args.item(idx) == "-param") {
if (idx + 2 < args.length) {
xslParams[args.item(idx + 1)] = args.item(idx + 2);
idx += 2;
}
else
error = true;
}
else if (xmlStyle == null) {
xmlStyle = createNewXMLObj();
xmlStyle.load(ofs.GetAbsolutePathName(args.item(idx)));
}
else if (xmlInput == null) {
inputFile = ofs.GetAbsolutePathName(args.item(idx));
xmlInput = createNewXMLObj();
xmlInput.load(inputFile);
}
if (xmlStyle == null || xmlInput == null || error) {
WScript.Echo('Usage:\n\t"xsltproc" xsl-stylesheet input-file\n\t\t["-o" output-file] *["--param" name value]');
}
else {
var xslt = new ActiveXObject("MSXML2.XSLTemplate.3.0");
xslt.stylesheet = xmlStyle;
var xslProc = xslt.createProcessor();
xslProc.input = xmlInput;
for (var keyVar in xslParams)
xslProc.addParameter(keyVar, xslParams[keyVar]);
xslProc.transform();
if (outputFile == null)
WScript.Echo(xslProc.output);
else {
var xmlOutput = createNewXMLObj();
xmlOutput.loadXML(xslProc.output);
xmlOutput.save(outputFile);
}
}