3

I have the following string which is the value from WWW-Authenticate from a http request:

"Digest realm=\"Web Service Realm via Digest Authentication\", qop=\"auth\", nonce=\"MTU3MjchuUVEIHEUnVNV==\""

I need to convert this into a map so I can easily reference the values for realm and nonce. I have some working code which is very brittle, e.g. to extract the realm:

    headers
    |> String.split(",")
    |> List.first()
    |> String.split("=")
    |> List.last()
    |> String.replace("\"", "")

However, this isn't great because it relies on the realm being the first in the list. Whats the most optimal way of converting this data?

4 Answers 4

4

If you have :cowlib as a dependency (:phoenix depends on it through :plug_cowboy => :cowboy => :cowlib), it includes functions for parsing various headers, including "WWW-Authenticate".

iex> www_authenticate = "Digest realm=\"Web Service Realm via Digest Authentication\", qop=\"auth\", nonce=\"MTU3MjchuUVEIHEUnVNV==\""
"Digest realm=\"Web Service Realm via Digest Authentication\", qop=\"auth\", nonce=\"MTU3MjchuUVEIHEUnVNV==\""
iex> [digest: params] = :cow_http_hd.parse_www_authenticate(www_authenticate)
[
  digest: [
    {"realm", "Web Service Realm via Digest Authentication"},
    {"qop", "auth"},
    {"nonce", "MTU3MjchuUVEIHEUnVNV=="}
  ]
]
iex> Map.new(params)
%{
  "nonce" => "MTU3MjchuUVEIHEUnVNV==",
  "qop" => "auth",
  "realm" => "Web Service Realm via Digest Authentication"
}
Sign up to request clarification or add additional context in comments.

2 Comments

👍 I’d actually even add this dependency instead of reinventing wheels if it’s not there yet.
Thanks, this is exactly what I was looking for. Perhaps I could have asked that in the first place as a default.
4

I would post the recursive solution, just for educational purpose. This, of course, should not probably be used in production.

defmodule Splitter do
  def parse(input), do: do_parse(input, %{}, :none)

  defp do_parse("realm=\"" <> rest, acc, :none),
    do: do_parse(rest, acc, :realm)
  defp do_parse("qop=\"" <> rest, acc, :none),
    do: do_parse(rest, acc, :qop)
  defp do_parse("nonce=\"" <> rest, acc, :none),
    do: do_parse(rest, acc, :nonce)
  defp do_parse("\"" <> rest, acc, key),
    do: do_parse(rest, acc, :none)
  defp do_parse(<<c :: binary-size(1), rest :: binary>>, acc, :none),
    do: do_parse(rest, acc, :none)
  defp do_parse(<<c :: binary-size(1), rest :: binary>>, acc, key),
    do: do_parse(rest, Map.update(acc, key, c, & &1 <> c), key)
  defp do_parse("", acc, _), do: acc
end

Splitter.parse(input)
#⇒ %{
#   nonce: "jefsFENEFJWIfejkshfshfhu332bfesf==",
#   qop: "auth",
#   realm: "Web Something Realm via Digest Authentication"
# }

Comments

2

Regex to the rescue? This does not include any validation or error handling, but it works for your sample case.

~r/([^\s=]+)="([^"]*)"/
|> Regex.scan(headers)
|> Enum.map(fn [_, k, v] -> {k, v} end)
|> Map.new()

results in the map

%{
  "nonce" => "jefsFENEFJWIfejkshfshfhu332bfesf==",
  "qop" => "auth",
  "realm" => "Web Something Realm via Digest Authentication"
}

2 Comments

Hi, I realised the test case wasn't even a string, so I've had to modify your regex to be ~r/([^\s=]+)=\\"([^"]*)"/ but it doesn't seem to match in Elixir, but with RegExr.com it's matching.
Actually, I just made this work by excluding the extra escape ~r/([^\s=]+)=\"([^"]*)"/
0

You can use String.split() in a more generic way:

str = ~s(Digest realm="Web Something Realm via Digest Authentication", qop="auth", nonce="jefsFENEFJWIfejkshfshfhu332bfesf==")
[type, rest] = String.split(str, " ", [parts: 2])
key_vals = String.split(rest, ", ")

for key_val <- key_vals, into: %{} do
  [key, val] = String.split(key_val, "=", [parts: 2]) 
  {key, String.trim(val, ~s{"})}  #return a tuple for each key/val in map
end
|> Map.put_new("type", type) 

=>

%{
  "nonce" => "jefsFENEFJWIfejkshfshfhu332bfesf==",
  "qop" => "auth",
  "realm" => "Web Something Realm via Digest Authentication",
  "type" => "Digest"
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.