WinHTTP escaping problem

WinHttpCrackUrl and WinHttpCreateUrl API functions are breaking URL string into components and recompose back to string. There was a mess with passwords and security issues since when putting password into URL is no more acceptable. Experienced users might remember the times when URL could embed password, e.g. Password is lo longer accepted by major applications in a typed in string and no more allowed by updated RFC 3986 “Uniform Resource Identifier (URI): Generic Syntax”:

3.2.1.  User Information

   The userinfo subcomponent may consist of a user name and, optionally,
   scheme-specific information about how to gain authorization to access
   the resource.  The user information, if present, is followed by a
   commercial at-sign ("@") that delimits it from the host.

      userinfo    = *( unreserved / pct-encoded / sub-delims / ":" )

   Use of the format "user:password" in the userinfo field is
   deprecated.  Applications should not render as clear text any data
   after the first colon (":") character found within a userinfo
   subcomponent unless the data after the colon is the empty string
   (indicating no password).

What if we don’t have URLs typed in? But it still convenient to keep password as a part of URL? Luckily there is such thing as compatibility, so we can rely on WinHTTP subsystem to process passwords for us. The problem however is escapement. The most tricky is that it is not a bug, it is documented but is unintuitive. The cracking part would unescape all components if ICU_DECODE flag is provided. The composing part however will only escape (ICU_ESCAPE) the part to the right from port number (whether it is specified or expected to be)!

For example (see source code below):

.lpszScheme http
.nScheme 1
.nPort 80
.lpszUserName user
.lpszPassword pa@ss
.lpszUrlPath /path
.lpszExtraInfo ?name=value /:@
pszUrl /:@

So, if you care for proper escapement it appears that you have to do it yourself after/before WinHTTP does the rest of the task.

So let us mention what is the proper RFC-compliant escaping after all:

  userinfo      = *( unreserved / pct-encoded / sub-delims / ":" )
   pct-encoded   = "%" HEXDIG HEXDIG
   unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
   sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
                 / "*" / "+" / "," / ";" / "="

Visual Studio C++.NET 2008 source code and binary:

Leave a Reply