anchor.diff:  %%      where record_type is the record type of Record
   record_to_proplist(Record, Fields, TypeKey)
     when is_tuple(Record),
  -       is_list(Fields),
  -       size(Record) - 1 =:= length(Fields) ->
  -    lists:zip([TypeKey | Fields], tuple_to_list(Record)).
  +is_list(Fields),
  +size(Record) - 1 =:= length(Fields) ->
  +  lists:zip([TypeKey | Fields], tuple_to_list(Record)).
   
   
   shell_quote([], Acc) ->
  -    lists:reverse([$\" | Acc]);
  +  lists:reverse([$\" | Acc]);
   shell_quote([C | Rest], Acc) when C =:= $\" orelse C =:= $\` orelse
                                     C =:= $\\ orelse C =:= $\$ ->
  -    shell_quote(Rest, [C, $\\ | Acc]);
  -shell_quote([C | Rest], Acc) ->
  -    shell_quote(Rest, [C | Acc]).
  +                    shell_quote(Rest, [C, $\\ | Acc]);
  +                 shell_quote([C | Rest], Acc) ->
  +                    shell_quote(Rest, [C | Acc]).
   
   test() ->
  -    test_join(),
  -    test_quote_plus(),
  -    test_unquote(),
  -    test_urlencode(),
  -    test_parse_qs(),
  -    test_urlsplit_path(),
  -    test_urlunsplit_path(),
  -    test_urlsplit(),
  -    test_urlunsplit(),
  -    test_path_split(),
  -    test_guess_mime(),
  -    test_parse_header(),
  -    test_shell_quote(),
  -    test_cmd(),
  -    test_cmd_string(),
  -    test_partition(),
  -    test_safe_relative_path(),
  -    ok.
  +  test_join(),
  +  test_quote_plus(),
  +  test_unquote(),
  +  test_urlencode(),
  +  test_parse_qs(),
  +  test_complex_parse_qs(),
  +  test_urlsplit_path(),
  +  test_urlunsplit_path(),
  +  test_urlsplit(),
  +  test_urlunsplit(),
  +  test_path_split(),
  +  test_guess_mime(),
  +  test_parse_header(),
  +  test_shell_quote(),
  +  test_cmd(),
  +  test_cmd_string(),
  +  test_partition(),
  +  test_safe_relative_path(),
  +  ok.
   
   test_shell_quote() ->
  -    "\"foo \\$bar\\\"\\`' baz\"" = shell_quote("foo $bar\"`' baz"),
  -    ok.
  +  "\"foo \\$bar\\\"\\`' baz\"" = shell_quote("foo $bar\"`' baz"),
  +  ok.
   
   test_cmd() ->
  -    "$bling$ `word`!\n" = cmd(["echo", "$bling$ `word`!"]),
  -    ok.
  +  "$bling$ `word`!\n" = cmd(["echo", "$bling$ `word`!"]),
  +  ok.
   
   test_cmd_string() ->
  -    "\"echo\" \"\\$bling\\$ \\`word\\`!\"" = cmd_string(["echo", "$bling$ `word`!"]),
  -    ok.
  +  "\"echo\" \"\\$bling\\$ \\`word\\`!\"" = cmd_string(["echo", "$bling$ `word`!"]),
  +  ok.
   
   test_parse_header() ->
  -    {"multipart/form-data", [{"boundary", "AaB03x"}]} =
  -        parse_header("multipart/form-data; boundary=AaB03x"),
  -    ok.
  +  {"multipart/form-data", [{"boundary", "AaB03x"}]} =
  +    parse_header("multipart/form-data; boundary=AaB03x"),
  +  ok.
   
   test_guess_mime() ->
  -    "text/plain" = guess_mime(""),
  -    "text/plain" = guess_mime(".text"),
  -    "application/zip" = guess_mime(".zip"),
  -    "application/zip" = guess_mime("x.zip"),
  -    "text/html" = guess_mime("x.html"),
  -    "application/xhtml+xml" = guess_mime("x.xhtml"),
  -    ok.
  +  "text/plain" = guess_mime(""),
  +  "text/plain" = guess_mime(".text"),
  +  "application/zip" = guess_mime(".zip"),
  +  "application/zip" = guess_mime("x.zip"),
  +  "text/html" = guess_mime("x.html"),
  +  "application/xhtml+xml" = guess_mime("x.xhtml"),
  +  ok.
   
   test_path_split() ->
  -    {"", "foo/bar"} = path_split("/foo/bar"),
  -    {"foo", "bar"} = path_split("foo/bar"),
  -    {"bar", ""} = path_split("bar"),
  -    ok.
  +  {"", "foo/bar"} = path_split("/foo/bar"),
  +  {"foo", "bar"} = path_split("foo/bar"),
  +  {"bar", ""} = path_split("bar"),
  +  ok.
   
   test_urlsplit() ->
  -    {"", "", "/foo", "", "bar?baz"} = urlsplit("/foo#bar?baz"),
  -    {"http", "host:port", "/foo", "", "bar?baz"} =
  -        urlsplit("http://host:port/foo#bar?baz"),
  -    ok.
  +  {"", "", "/foo", "", "bar?baz"} = urlsplit("/foo#bar?baz"),
  +  {"http", "host:port", "/foo", "", "bar?baz"} =
  +    urlsplit("http://host:port/foo#bar?baz"),
  +  ok.
   
   test_urlsplit_path() ->
  -    {"/foo/bar", "", ""} = urlsplit_path("/foo/bar"),
  -    {"/foo", "baz", ""} = urlsplit_path("/foo?baz"),
  -    {"/foo", "", "bar?baz"} = urlsplit_path("/foo#bar?baz"),
  -    {"/foo", "", "bar?baz#wibble"} = urlsplit_path("/foo#bar?baz#wibble"),
  -    {"/foo", "bar", "baz"} = urlsplit_path("/foo?bar#baz"),
  -    {"/foo", "bar?baz", "baz"} = urlsplit_path("/foo?bar?baz#baz"),
  -    ok.
  +  {"/foo/bar", "", ""} = urlsplit_path("/foo/bar"),
  +  {"/foo", "baz", ""} = urlsplit_path("/foo?baz"),
  +  {"/foo", "", "bar?baz"} = urlsplit_path("/foo#bar?baz"),
  +  {"/foo", "", "bar?baz#wibble"} = urlsplit_path("/foo#bar?baz#wibble"),
  +  {"/foo", "bar", "baz"} = urlsplit_path("/foo?bar#baz"),
  +  {"/foo", "bar?baz", "baz"} = urlsplit_path("/foo?bar?baz#baz"),
  +  ok.
   
   test_urlunsplit() ->
  -    "/foo#bar?baz" = urlunsplit({"", "", "/foo", "", "bar?baz"}),
  -    "http://host:port/foo#bar?baz" =
  -        urlunsplit({"http", "host:port", "/foo", "", "bar?baz"}),
  -    ok.
  +  "/foo#bar?baz" = urlunsplit({"", "", "/foo", "", "bar?baz"}),
  +  "http://host:port/foo#bar?baz" =
  +    urlunsplit({"http", "host:port", "/foo", "", "bar?baz"}),
  +  ok.
   
   test_urlunsplit_path() ->
  -    "/foo/bar" = urlunsplit_path({"/foo/bar", "", ""}),
  -    "/foo?baz" = urlunsplit_path({"/foo", "baz", ""}),
  -    "/foo#bar?baz" = urlunsplit_path({"/foo", "", "bar?baz"}),
  -    "/foo#bar?baz#wibble" = urlunsplit_path({"/foo", "", "bar?baz#wibble"}),
  -    "/foo?bar#baz" = urlunsplit_path({"/foo", "bar", "baz"}),
  -    "/foo?bar?baz#baz" = urlunsplit_path({"/foo", "bar?baz", "baz"}),
  -    ok.
  +  "/foo/bar" = urlunsplit_path({"/foo/bar", "", ""}),
  +  "/foo?baz" = urlunsplit_path({"/foo", "baz", ""}),
  +  "/foo#bar?baz" = urlunsplit_path({"/foo", "", "bar?baz"}),
  +  "/foo#bar?baz#wibble" = urlunsplit_path({"/foo", "", "bar?baz#wibble"}),
  +  "/foo?bar#baz" = urlunsplit_path({"/foo", "bar", "baz"}),
  +  "/foo?bar?baz#baz" = urlunsplit_path({"/foo", "bar?baz", "baz"}),
  +  ok.
   
   test_join() ->
  -    "foo,bar,baz" = join(["foo", "bar", "baz"], $,),
  -    "foo,bar,baz" = join(["foo", "bar", "baz"], ","),
  -    "foo bar" = join([["foo", " bar"]], ","),
  -    "foo bar,baz" = join([["foo", " bar"], "baz"], ","),
  -    "foo" = join(["foo"], ","),
  -    "foobarbaz" = join(["foo", "bar", "baz"], ""),
  -    ok.
  +  "foo,bar,baz" = join(["foo", "bar", "baz"], $,),
  +  "foo,bar,baz" = join(["foo", "bar", "baz"], ","),
  +  "foo bar" = join([["foo", " bar"]], ","),
  +  "foo bar,baz" = join([["foo", " bar"], "baz"], ","),
  +  "foo" = join(["foo"], ","),
  +  "foobarbaz" = join(["foo", "bar", "baz"], ""),
  +  ok.
   
   test_quote_plus() ->
  -    "foo" = quote_plus(foo),
  -    "1" = quote_plus(1),
  -    "1.1" = quote_plus(1.1),
  -    "foo" = quote_plus("foo"),
  -    "foo+bar" = quote_plus("foo bar"),
  -    "foo%0A" = quote_plus("foo\n"),
  -    "foo%0A" = quote_plus("foo\n"),
  -    "foo%3B%26%3D" = quote_plus("foo;&="),
  -    ok.
  +  "foo" = quote_plus(foo),
  +  "1" = quote_plus(1),
  +  "1.1" = quote_plus(1.1),
  +  "foo" = quote_plus("foo"),
  +  "foo+bar" = quote_plus("foo bar"),
  +  "foo%0A" = quote_plus("foo\n"),
  +  "foo%0A" = quote_plus("foo\n"),
  +  "foo%3B%26%3D" = quote_plus("foo;&="),
  +  ok.
   
   test_unquote() ->
  -    "foo bar" = unquote("foo+bar"),
  -    "foo bar" = unquote("foo%20bar"),
  -    "foo\r\n" = unquote("foo%0D%0A"),
  -    ok.
  +  "foo bar" = unquote("foo+bar"),
  +  "foo bar" = unquote("foo%20bar"),
  +  "foo\r\n" = unquote("foo%0D%0A"),
  +  ok.
   
   test_urlencode() ->
  -    "foo=bar&baz=wibble+%0D%0A&z=1" = urlencode([{foo, "bar"},
  -                                                 {"baz", "wibble \r\n"},
  -                                                 {z, 1}]),
  -    ok.
  +  "foo=bar&baz=wibble+%0D%0A&z=1" = urlencode([{foo, "bar"},
  +                                               {"baz", "wibble \r\n"},
  +                                               {z, 1}]),
  +  ok.
   
   test_parse_qs() ->
  -    [{"foo", "bar"}, {"baz", "wibble \r\n"}, {"z", "1"}] =
  -        parse_qs("foo=bar&baz=wibble+%0D%0A&z=1"),
  -    ok.
  +  [{"foo", "bar"}, {"baz", "wibble \r\n"}, {"z", "1"}] =
  +    parse_qs("foo=bar&baz=wibble+%0D%0A&z=1"),
  +  [{"foo", [{"bar", "1"}, {"baz", "2"}]}] =
  +    parse_qs("foo[bar]=1&foo[baz]=2"),
  +  ok.
  +
  +test_complex_parse_qs() ->
  +  [{"foo", [{"bar", "1"}, {"baz", "2"}]}] =
  +    parse_qs("foo[bar]=1&foo[baz]=2"),
  +  ok.
   
   test_partition() ->
  -    {"foo", "", ""} = partition("foo", "/"),
  -    {"foo", "/", "bar"} = partition("foo/bar", "/"),
  -    {"foo", "/", ""} = partition("foo/", "/"),
  -    {"", "/", "bar"} = partition("/bar", "/"),
  -    {"f", "oo/ba", "r"} = partition("foo/bar", "oo/ba"),
  -    ok.
  +  {"foo", "", ""} = partition("foo", "/"),
  +  {"foo", "/", "bar"} = partition("foo/bar", "/"),
  +  {"foo", "/", ""} = partition("foo/", "/"),
  +  {"", "/", "bar"} = partition("/bar", "/"),
  +  {"f", "oo/ba", "r"} = partition("foo/bar", "oo/ba"),
  +  ok.
   
   test_safe_relative_path() ->
  -    "foo" = safe_relative_path("foo"),
  -    "foo/" = safe_relative_path("foo/"),
  -    "foo" = safe_relative_path("foo/bar/.."),
  -    "bar" = safe_relative_path("foo/../bar"),
  -    "bar/" = safe_relative_path("foo/../bar/"),
  -    "" = safe_relative_path("foo/.."),
  -    "" = safe_relative_path("foo/../"),
  -    undefined = safe_relative_path("/foo"),
  -    undefined = safe_relative_path("../foo"),
  -    undefined = safe_relative_path("foo/../.."),
  -    undefined = safe_relative_path("foo//"),
  -    ok.
  +  "foo" = safe_relative_path("foo"),
  +  "foo/" = safe_relative_path("foo/"),
  +  "foo" = safe_relative_path("foo/bar/.."),
  +  "bar" = safe_relative_path("foo/../bar"),
  +  "bar/" = safe_relative_path("foo/../bar/"),
  +  "" = safe_relative_path("foo/.."),
  +  "" = safe_relative_path("foo/../"),
  +  undefined = safe_relative_path("/foo"),
  +  undefined = safe_relative_path("../foo"),
  +  undefined = safe_relative_path("foo/../.."),
  +  undefined = safe_relative_path("foo//"),
  +  ok.
  
positive.code: 
  -define(PERCENT, 37).  % $\%
  -define(FULLSTOP, 46). % $\.
  -define(IS_HEX(C), ((C >= $0 andalso C =< $9) orelse
                      (C >= $a andalso C =< $f) orelse
                      (C >= $A andalso C =< $F))).
  
negative.code: %%      is not safe. safe relative paths can be joined with an absolute path
  %%      and will result in a subdirectory of the absolute path.
  safe_relative_path("/" ++ _) ->
    undefined;
  safe_relative_path(P) ->
    safe_relative_path(P, []).
  
  safe_relative_path("", Acc) ->
    case Acc of
      [] ->
        "";
      _ ->
        string:join(lists:reverse(Acc), "/")
    end;
  safe_relative_path(P, Acc) ->
    case partition(P, "/") of
      {"", "/", _} ->
  %% /foo or foo//bar
        undefined;
      {"..", _, _} when Acc =:= [] ->
        undefined;
      {"..", _, Rest} ->
        safe_relative_path(Rest, tl(Acc));
      {Part, "/", ""} ->
        safe_relative_path("", ["", Part | Acc]);
      {Part, _, Rest} ->
        safe_relative_path(Rest, [Part | Acc])
    end.
  
  %% @spec shell_quote(string()) -> string()
  %% @doc Quote a string according to UNIX shell quoting rules, returns a string
  %%      surrounded by double quotes.
  shell_quote(L) ->
    shell_quote(L, [$\"]).
  
  %% @spec cmd_port([string()], Options) -> port()
  %% @doc open_port({spawn, mochiweb_util:cmd_string(Argv)}, Options).
  cmd_port(Argv, Options) ->
                       open_port({spawn, cmd_string(Argv)}, Options).
  
  %% @spec cmd([string()]) -> string()
  %% @doc os:cmd(cmd_string(Argv)).
  cmd(Argv) ->
    os:cmd(cmd_string(Argv)).
  
  %% @spec cmd_string([string()]) -> string()
  %% @doc Create a shell quoted command string from a list of arguments.
  cmd_string(Argv) ->
    join([shell_quote(X) || X <- Argv], " ").
  
  %% @spec join([string()], Separator) -> string()
  %% @doc Join a list of strings together with the given separator
  %%      string or char.
  join([], _Separator) ->
    [];
  join([S], _Separator) ->
    lists:flatten(S);
  join(Strings, Separator) ->
    lists:flatten(revjoin(lists:reverse(Strings), Separator, [])).
  
  revjoin([], _Separator, Acc) ->
    Acc;
  revjoin([S | Rest], Separator, []) ->
    revjoin(Rest, Separator, [S]);
  revjoin([S | Rest], Separator, Acc) ->
    revjoin(Rest, Separator, [S, Separator | Acc]).
  
  %% @spec quote_plus(atom() | integer() | float() | string() | binary()) -> string()
  %% @doc URL safe encoding of the given term.
  quote_plus(Atom) when is_atom(Atom) ->
    quote_plus(atom_to_list(Atom));
  quote_plus(Int) when is_integer(Int) ->
    quote_plus(integer_to_list(Int));
  quote_plus(Binary) when is_binary(Binary) ->
    quote_plus(binary_to_list(Binary));
  quote_plus(Float) when is_float(Float) ->
    quote_plus(mochinum:digits(Float));
  quote_plus(String) ->
    quote_plus(String, []).
  
  quote_plus([], Acc) ->
    lists:reverse(Acc);
  quote_plus([C | Rest], Acc) when ?QS_SAFE(C) ->
    quote_plus(Rest, [C | Acc]);
  quote_plus([$\s | Rest], Acc) ->
    quote_plus(Rest, [$+ | Acc]);
  quote_plus([C | Rest], Acc) ->
    <<Hi:4, Lo:4>> = <<C>>,
    quote_plus(Rest, [hexdigit(Lo), hexdigit(Hi), ?PERCENT | Acc]).
  
  %% @spec urlencode([{Key, Value}]) -> string()
  %% @doc URL encode the property list.
  urlencode(Props) ->
    RevPairs = lists:foldl(fun ({K, V}, Acc) ->
                               [[quote_plus(K), $=, quote_plus(V)] | Acc]
                           end, [], Props),
    lists:flatten(revjoin(RevPairs, $&, [])).
  
  %% @spec parse_qs(string() | binary()) -> [{Key, Value}]
  %% @doc Parse a query string or application/x-www-form-urlencoded.
  -spec(parse_qs/1 :: (list()) -> [tuple()]).
  parse_qs(Binary) when is_binary(Binary) ->
    parse_qs(binary_to_list(Binary));
  parse_qs([$? | Rest]) ->
    parse_qs(Rest);
  parse_qs(String) ->
    parse_qs(String, []).
  
  parse_qs([], Acc) ->
    lists:reverse(Acc);
  parse_qs(String, Acc) ->
    case parse_qs_key(String) of
      {[], Key, Rest} ->
        {Value, Rest1} = parse_qs_value(Rest),
        parse_qs(Rest1, [{Key, Value} | Acc]);
      {Parents, Key, Rest} ->
        {Value, Rest1} = parse_qs_value(Rest),
        Acc1 = parse_qs_merge(Parents, Key, Value, Acc),
        parse_qs(Rest1, Acc1)
    end.
  
  parse_qs_key(String) ->
    parse_qs_key(String, [], []).
  
  parse_qs_key([], Parents, Acc) ->
    {Parents, qs_revdecode(Acc), ""};
  parse_qs_key([$= | Rest], Parents, Acc) ->
    {Parents, qs_revdecode(Acc), Rest};
  parse_qs_key(Rest=[$; | _], Parents, Acc) ->
    {Parents, qs_revdecode(Acc), Rest};
  parse_qs_key(Rest=[$& | _], Parents, Acc) ->
    {Parents, qs_revdecode(Acc), Rest};
  parse_qs_key([?LBRACE | Rest], Parents, Acc) ->
    case parse_qs_parent(Rest) of
      {valid, Key, Rest1} ->
        parse_qs_key(Rest1, [qs_revdecode(Acc) | Parents], Key);
      {invalid, KeyRest, Rest1} ->
        {Parents, qs_revdecode([KeyRest | Acc]), Rest1}
    end;
  parse_qs_key([C | Rest], Parents, Acc) ->
    parse_qs_key(Rest, Parents, [C | Acc]).
  
  
  parse_qs_parent(String) ->
    parse_qs_parent(String, []).
  parse_qs_parent([], Acc) ->
    {invalid, Acc, ""};
  parse_qs_parent([$= | Rest], Acc) ->
    {invalid, Acc, Rest};
  parse_qs_parent(Rest=[$; | _], Acc) ->
    {invalid, Acc, Rest};
  parse_qs_parent(Rest=[$& | _], Acc) ->
    {invalid, Acc, Rest};
  parse_qs_parent([?RBRACE | Rest], Acc) ->
    {valid, Acc, Rest};
  parse_qs_parent([C | Rest], Acc) ->
    parse_qs_parent(Rest, [C | Acc]).
  
  
  parse_qs_value(String) ->
    parse_qs_value(String, []).
  
  parse_qs_value([], Acc) ->
    {qs_revdecode(Acc), ""};
  parse_qs_value([$; | Rest], Acc) ->
    {qs_revdecode(Acc), Rest};
  parse_qs_value([$& | Rest], Acc) ->
    {qs_revdecode(Acc), Rest};
  parse_qs_value([C | Rest], Acc) ->
    parse_qs_value(Rest, [C | Acc]).
  
  
  parse_qs_merge([], Key, Value, Acc) ->
    lists:ukeysort(1, [{Key, Value}] ++ Acc);
  parse_qs_merge([H|T], Key, Value, Acc) ->
    case proplists:get_value(H, Acc) of
      undefined ->
        [{H, parse_qs_merge(T, Key, Value, []) ++ Acc}];
      PropList ->
        Merger = parse_qs_merge(T, Key, Value, PropList),
        lists:ukeysort(1, [{H, Merger}] ++ Acc)
    end.
  
  
  %% @spec unquote(string() | binary()) -> string()
  %% @doc Unquote a URL encoded string.
  unquote(Binary) when is_binary(Binary) ->
    unquote(binary_to_list(Binary));
  unquote(String) ->
    qs_revdecode(lists:reverse(String)).
  
  qs_revdecode(S) ->
    qs_revdecode(S, []).
  
  qs_revdecode([], Acc) ->
    Acc;
  qs_revdecode([$+ | Rest], Acc) ->
    qs_revdecode(Rest, [$\s | Acc]);
  qs_revdecode([Lo, Hi, ?PERCENT | Rest], Acc) when ?IS_HEX(Lo), ?IS_HEX(Hi) ->
    qs_revdecode(Rest, [(unhexdigit(Lo) bor (unhexdigit(Hi) bsl 4)) | Acc]);
  qs_revdecode([C | Rest], Acc) ->
    qs_revdecode(Rest, [C | Acc]).
  
  %% @spec urlsplit(Url) -> {Scheme, Netloc, Path, Query, Fragment}
  %% @doc Return a 5-tuple, does not expand % escapes. Only supports HTTP style
  %%      URLs.
  urlsplit(Url) ->
    {Scheme, Url1} = urlsplit_scheme(Url),
    {Netloc, Url2} = urlsplit_netloc(Url1),
    {Path, Query, Fragment} = urlsplit_path(Url2),
    {Scheme, Netloc, Path, Query, Fragment}.
  
  urlsplit_scheme(Url) ->
    urlsplit_scheme(Url, []).
  
  urlsplit_scheme([], Acc) ->
    {"", lists:reverse(Acc)};
  urlsplit_scheme(":" ++ Rest, Acc) ->
    {string:to_lower(lists:reverse(Acc)), Rest};
  urlsplit_scheme([C | Rest], Acc) ->
    urlsplit_scheme(Rest, [C | Acc]).
  
  urlsplit_netloc("//" ++ Rest) ->
    urlsplit_netloc(Rest, []);
  urlsplit_netloc(Path) ->
    {"", Path}.
  
  urlsplit_netloc(Rest=[C | _], Acc) when C =:= $/; C =:= $?; C =:= $# ->
    {lists:reverse(Acc), Rest};
  urlsplit_netloc([C | Rest], Acc) ->
    urlsplit_netloc(Rest, [C | Acc]).
  
  
  %% @spec path_split(string()) -> {Part, Rest}
  