anchor.diff: +%%
  +%% Note: some functions do several passes over the same string() to
  +%% parse it, e.g. 1 pass to find start and end delimiter and one pass
  +%% for reading the the elements from start to end index, and probably
  +%% a additional scan to reach the remainder of the string.
  +%% This performance can be improved, by using a accumulator equiped
  +%% parser that returns the Match and Rest part of the string, in a
  +%% single scan.
  +%%
  +%% Note: elements are often only partially parsed - this reduces the
  +%% amount of parsing done on entries which will only be passed along, 
  +%% but while this improves performance it increases the need for later
  +%% exception handling.
  +%%--------------------------------------------------------------------
  +
  +
   -module(sipheader).
  --export([to/1, from/1, contact/1, via/1, via_print/1, to_print/1,
  -	 contact_print/1, auth_print/1, auth_print/2, auth/1, comma/1,
  -	 httparg/1, cseq/1, cseq_print/1, via_params/1, contact_params/1,
  -	 build_header/1, dict_to_param/1, param_to_dict/1, dialogueid/1,
  -	 get_tag/1, topvia/1, via_sentby/1, get_client_transaction_id/1,
  -	 get_server_transaction_id/1, get_server_transaction_ack_id_2543/1, 
  -	 get_via_branch/1, get_via_branch_full/1, remove_loop_cookie/1,
  +
  +%%--------------------------------------------------------------------
  +%% External exports
  +%%--------------------------------------------------------------------
  +-export([
  +	 expire/1,
  +	 to/1,
  +	 from/1,
  +	 contact/1,
  +	 route/1,
  +	 record_route/1,
  +	 via/1,
  +	 via_print/1,
  +	 contact_print/1,
  +	 auth_print/1,
  +	 auth_print/2,
  +	 auth/1,
  +	 comma/1,
  +	 httparg/1,
  +	 cseq/1,
  +	 cseq_print/1,
  +	 callid/1,
  +	 via_params/1,
  +	 build_header_binary/1,
  +	 dict_to_param/1,
  +	 param_to_dict/1,
  +	 dialogueid/1,
  +	 get_tag/1,
  +	 topvia/1,
  +	 via_sentby/1,
  +	 get_client_transaction_id/1,
  +	 get_server_transaction_id/1,
  +	 get_server_transaction_ack_id_2543/1,
  +	 get_via_branch/1,
  +	 get_via_branch_full/1,
  +	 remove_loop_cookie/1,
   	 get_server_transaction_id_using_3261_response_header/1,
  -	 via_is_equal/2, via_is_equal/3]).
  -
  +	 via_is_equal/2,
  +	 via_is_equal/3,
  +
  +	 test/0
  +	]).
  +
  +%%--------------------------------------------------------------------
  +%% Internal exports
  +%%--------------------------------------------------------------------
  +
  +%%--------------------------------------------------------------------
  +%% Include files
  +%%--------------------------------------------------------------------
   -include("siprecords.hrl").
   
  +%%--------------------------------------------------------------------
  +%% Records
  +%%--------------------------------------------------------------------
  +
  +%%--------------------------------------------------------------------
  +%% Macros
  +%%--------------------------------------------------------------------
  +
  +%%====================================================================
  +%% External functions
  +%%====================================================================
  +
  +%%--------------------------------------------------------------------
  +%% Function: comma(String)
  +%% Descrip.: split a comma separated string into separate strings,
  +%%           along the commas (don't include them). 
  +%%           Each substring is cleared of any preceding or trailing
  +%%           spaces. example: "foo, bar, zop, \"quoted, hi\"" -> 
  +%%           ["foo","bar","zop", "\"quoted, hi\""]
  +%%           
  +%% Returns : list() of string()
  +%%--------------------------------------------------------------------
   comma(String) ->
  -    comma([], String, false).
  -
  -% comma(Parsed, Rest, Inquote)
  -
  -comma(Parsed, [$\\, Char | Rest], true) ->
  -    comma(Parsed ++ [$\\, Char], Rest, true);
  -comma(Parsed, [$" | Rest], false) ->
  -    comma(Parsed ++ [$"], Rest, true);
  -comma(Parsed, [$" | Rest], true) ->
  -    comma(Parsed ++ [$"], Rest, false);
  -comma(Parsed, [$, | Rest], false) ->
  -    [string:strip(Parsed, both) | comma([], Rest, false)];
  -comma(Parsed, [Char | Rest], Inquote) ->
  -    comma(Parsed ++ [Char], Rest, Inquote);
  -comma(Parsed, [], false) ->
  -    [string:strip(Parsed, both)].
  -
  -% name-addr = [ display-name ] "<" addr-spec ">"
  -% display-name = *token | quoted-string
  -
  -
  -% {Displayname, URL}
  +    comma([], String, false, false).
  +
  +% comma(Parsed, Rest, Inquote, InUriQuote)
  +
  +%% Parsed     = current segment of string since last comma (,)
  +%% Rest       = the remainder of the string to split along comma (,)
  +%% Inquote    = keep track if currently inside a quoted string segment
  +%%              where special chars like "," should be ignored
  +%% InUriQuote = keep track if currently inside a quoted sipurl segment
  +%%              where special chars like "," should be ignored
  +
  +%% Note: Parsed stores its chars in reverse order during 
  +%% scaning - to improve performance, "Parsed ++ [C]" which is easier
  +%% to understand is considerably more costly, code becomes  O(N^2) 
  +%% instead of O(N)
  +%% Note: quote URIs (<...>) can't contain quoted strings.
  +%% Quoted strings on the other hand, can contain "<" and ">" - this
  +%% means that comma doesn't have to deal with nested quotes. 
  +
  +
  +%% start of quoted uri
  +comma(Parsed, [$< | Rest], false, false) ->
  +    comma([$< | Parsed], Rest, false, true);
  +
  +%% end of quoted uri
  +comma(Parsed, [$> | Rest], false, true) ->
  +    comma([$> | Parsed], Rest, false, false);
  +
  +%% inside quoted uri - ignore comma or any other special chars
  +comma(Parsed, [Char | Rest], false, true) ->
  +    comma([Char | Parsed], Rest, false, true);
  +
  +%% -------------
  +
  +%% an escape code e.g. "\," has been found, don't treat it in any special manner 
  +%% only done inside a quoted segment of the string
  +comma(Parsed, [$\\, Char | Rest], true, false) ->
  +    comma([Char, $\\ | Parsed], Rest, true, false);
  +
  +%% Inquote = false, we have now entered inside a quoted ("...") string segment 
  +comma(Parsed, [$" | Rest], false, false) ->
  +    comma([$" | Parsed], Rest, true, false);
  +
  +%% Inquote = true and a new quote (") found - end of quoted string segment  
  +comma(Parsed, [$" | Rest], true, false) ->
  +    comma([$" | Parsed], Rest, false, false);
  +
  +%% regular char, store in current string
  +comma(Parsed, [Char | Rest], true, false) ->
  +    comma([Char | Parsed], Rest, true, false);
  +
  +%% -------------
  +
  +%% Inquote = false, so comma is a comma that splits string
  +%% start looking for the next one  
  +comma(Parsed, [$, | Rest], false, false) ->
  +    [lists:reverse(string:strip(Parsed, both)) | comma([], Rest, false, false)];
  +
  +%% regular char, store in current string
  +comma(Parsed, [Char | Rest], false, false) ->
  +    comma([Char | Parsed], Rest, false, false);
  +
  +%% end of string, clean up last comma separated entry (it's a error
  +%% if Inquote or InUriQuote = true, the quotes are then unbalanced)
  +comma(Parsed, [], false, false) ->
  +    [lists:reverse(string:strip(Parsed, both))].
  +
  +%--------------------------------------------------------------------
  +%% Function: expire(Header)
  +%%           Header = keylist record()
  +%% Descrip.: get the value of the "Expires" header
  +%% Returns : [Expire] | []
  +%%           Expire = numerical string()
  +%%--------------------------------------------------------------------
  +expire(Header) when is_record(Header, keylist) -> 
  +    keylist:fetch("Expires", Header).
  +
  +%%--------------------------------------------------------------------
  +%% Function: to([String])
  +%%           to(Header)
  +%%           String = string(), the contens of a TO header (usually 
  +%%           the result of keylist:fetch(to, Header))
  +%%           Header = keylist record()
  +%% Descrip.: parse header data
  +%% Returns : {Displayname, URI} (see name_header/1)
  +%%--------------------------------------------------------------------
  +to(Header) when is_record(Header, keylist) -> 
  +    to(keylist:fetch("To", Header));
   
   to([String]) ->
       name_header(String).
   
  +%%--------------------------------------------------------------------
  +%% Function: from([String])
  +%%           from(Header)
  +%%           String = string(), the contens of a FROM header (usually 
  +%%           the result of keylist:fetch("From", Header))
  +%%           Header = keylist record()
  +%% Descrip.: parse header data
  +%% Returns : {Displayname, URI} (see name_header/1)
  +%%--------------------------------------------------------------------
  +from(Header) when is_record(Header, keylist) -> 
  +    from(keylist:fetch("From", Header));
  +
   from([String]) ->
       name_header(String).
   
  -contact([]) ->
  -    [];
  -    
  -contact([String | Rest]) ->
  -    Headers = comma(String),
  -    lists:append(lists:map(fun(H) ->
  -				   parse_contact(H)
  -			   end, Headers),
  -		 contact(Rest)).
  -
  -parse_contact("'*'") ->
  -    {none, {wildcard, []}};
  -
  -parse_contact("*") ->
  -    {none, {wildcard, []}};
  -
  -parse_contact("'*';" ++ ParamStr) ->
  -    Parameters = string:tokens(ParamStr, ";"),
  -    {none, {wildcard, Parameters}};
  -
  -parse_contact("*;" ++ ParamStr) ->
  -    Parameters = string:tokens(ParamStr, ";"),
  -    {none, {wildcard, Parameters}};
  -
  -parse_contact(String) ->
  -    name_header(String).
  -
  -contact_params({_, {wildcard, Parameters}}) ->
  -    param_to_dict(Parameters);
  -contact_params({_, URI}) when record(URI, sipurl) ->
  -    param_to_dict(URI#sipurl.param).
  +%%--------------------------------------------------------------------
  +%% Function: contact(Header)
  +%%           route(Header)
  +%%           record_route(Header)
  +%%           Header = keylist record()
  +%% Descrip.: return the contact/route/record-route/... entries 
  +%%           contained in Header 
  +%% Returns : list() of contact record()         
  +%% XXX should we use contact record() for all of these headers ???
  +%% they may benefit from their own record type.
  +%%--------------------------------------------------------------------
  +contact(Header) when is_record(Header, keylist) -> 
  +    contact(Header, "Contact").
  +
  +route(Header) when is_record(Header, keylist) -> 
  +    contact(Header, "Route").
  +
  +record_route(Header) when is_record(Header, keylist) -> 
  +    contact(Header, "Record-Route").
  +
  +
  +contact(Header, HeaderFieldName) when is_record(Header, keylist) -> 
  +    V = keylist:fetch(HeaderFieldName, Header),
  +    contact:parse(V).
  +
  +%%--------------------------------------------------------------------
  +%% Function: via(ViaList)
  +%%           via(Header)
  +%%           ViaList = list() of string(), string() = the contents of 
  +%%                     Via: header. StrList is usually the result of 
  +%%                     keylist:fetch("Via", Header)
  +%%           Header = keylist record()
  +%% Descrip.: parse header data
  +%% Returns : list() of via record()
  +%%--------------------------------------------------------------------
  +via(Header) when is_record(Header, keylist) -> 
  +    via(keylist:fetch("Via", Header));
   
   via([]) ->
       [];
  
positive.code:     lists:append(lists:map(fun(H) ->
  				   [Protocol, Sentby] = string:tokens(H, " "),
  				   [Hostport | Parameters ] = string:tokens(Sentby, ";"),
  				   {Host, P} = sipurl:parse_hostport(Hostport),
  				   Port = case util:isnumeric(P) of
  					      true ->
  						  list_to_integer(P);
  					      _ ->
  						  none
  					  end,
  
negative.code: 			   end, Headers),
  		 via(Rest)).
  
  %%--------------------------------------------------------------------
  %% Function: topvia(Header)
  %%           Header = keylist record()
  %% Descrip.: get the first Via entry from Header
  %% Returns : via record() |
  %%           none - if not found in Header
  %%--------------------------------------------------------------------
  topvia(Header) when is_record(Header, keylist) ->
      case via(Header) of
  	[] -> none;
  	[TopVia | _] when is_record(TopVia, via) -> TopVia;
  	_ -> error
      end.
  
  %%--------------------------------------------------------------------
  %% Function:
  %% Descrip.: print string() separated by ";"
  %% Returns :
  %%--------------------------------------------------------------------
  print_parameters([]) ->
      "";
  print_parameters([A | B]) ->
      ";" ++ A ++ print_parameters(B).
  
  %%--------------------------------------------------------------------
  %% Function:
  %% Descrip.: print via record() or list() of via record()
  %% Returns :
  %%--------------------------------------------------------------------
  via_print(Via) when is_record(Via, via) ->
      via_print([Via]);
  via_print(Via) when is_list(Via) ->
      lists:map(fun(H) ->
  		      {Protocol, Host, Port, Parameters} = {H#via.proto, H#via.host, H#via.port, H#via.param},
  		      Protocol ++ " " ++ sipurl:print_hostport(Host, Port) ++ print_parameters(Parameters)
  	      end, Via).
  
  %%--------------------------------------------------------------------
  %% Function:
  %% Descrip.: convert parameters stored in Via to a dictionary
  %% Returns : dict()
  %%--------------------------------------------------------------------
  via_params(Via) when is_record(Via, via) ->
      param_to_dict(Via#via.param).
  
  %%--------------------------------------------------------------------
  %% Function: contact_print(Contacts)
  %%           Contacts = list() of contact record(), containing contact 
  %%           from contact/1
  %% Descrip.: Take a list of contact records, and return a list of
  %%           those contacts as strings
  %% Returns : list() of string()
  %%--------------------------------------------------------------------
  contact_print(ContactList) when is_list(ContactList) ->
      contact_print2(ContactList, []).
  
  contact_print2([Contact | R], Res) when is_record(Contact, contact) ->
      contact_print2(R, [contact:print(Contact) | Res]);
  contact_print2([], Res) ->
      lists:reverse(Res).
  
  
  %%--------------------------------------------------------------------
  %% Function:
  %% Descrip.:
  %% Returns :
  %%--------------------------------------------------------------------
  auth_print(Auth) ->
      auth_print(Auth, false).
  
  auth_print(Auth, Stale) ->
      {Realm, Nonce, Opaque} = Auth,
      ["Digest realm=\"" ++ Realm ++ "\", nonce=\"" ++ Nonce ++ "\", opaque=\"" ++ Opaque ++ "\"" ++
       case Stale of
  	 true ->
  	     ", stale=true";
  	 _ ->
  	     ""
       end
      ].
  
  %%--------------------------------------------------------------------
  %% Function: auth([In])
  %%           In = string()
  %% Descrip.: parse authrization header 
  %% Returns : throw() | dict() 
  %%--------------------------------------------------------------------
  auth([In]) ->
      %% lowercase first word (to implement case insensitivity)
      %% XXX what does this code do ???
      case string:chr(In, $\ ) of
  	0 ->
  	    In;	% uhm, is really broken
  	Index ->
  	    H = string:substr(In, 1, Index - 1),
  	    LH = httpd_util:to_lower(H),
  	    T = string:substr(In, Index + 1),
  	    Out = lists:flatten(LH ++ " " ++ T),
  	    auth2(Out)
      end.
  
  auth2("gssapi " ++ String) ->
      Headers = comma(String),
      F = fun get_name_and_value/1,
      L = lists:map(F , Headers),
      dict:from_list(L);
  
  auth2("digest " ++ String) ->
      Headers = comma(String),
      F = fun get_name_and_value/1,
      L = lists:map(F, Headers),
      dict:from_list(L);
  
  auth2(In) ->
      logger:log(error, "sipheader:auth() called with unrecognized authentication data (~p)", [In]),
      throw({siperror, 500, "Server Internal Error"}).
  
  get_name_and_value(Str) ->
      H = string:strip(Str,left),
      Index = string:chr(H, $=),
      Name = string:substr(H, 1, Index - 1),
      Value = string:substr(H, Index + 1),
      {Name, unquote(Value)}.
  
  %% removes single pair of quotes, returns contens in between these first two quotes
  unquote([$" | QString]) ->
      Index = string:chr(QString, $"),
      string:substr(QString, 1, Index - 1);
  