Sfoglia il codice sorgente

textopsx: added functions that operate on header value

- imported from modules_s/texops and replaced the xlog-% style with
  pvar-$ style
Daniel-Constantin Mierla 12 anni fa
parent
commit
ba6119e3b4
3 ha cambiato i file con 1989 aggiunte e 0 eliminazioni
  1. 234 0
      modules/textopsx/README
  2. 361 0
      modules/textopsx/doc/functions.xml
  3. 1394 0
      modules/textopsx/textopsx.c

+ 234 - 0
modules/textopsx/README

@@ -20,6 +20,20 @@ Daniel-Constantin Mierla
         1.2.3. remove_body()
         1.2.4. keep_hf(regexp)
         1.2.5. fnmatch(value, expr [, flags])
+        1.2.6. append_hf_value(hf, hvalue)
+        1.2.7. insert_hf_value(hf, hvalue)
+        1.2.8. remove_hf_value(hf_par)
+        1.2.9. remove_hf_value2(hf_par)
+        1.2.10. assign_hf_value(hf, hvalue)
+        1.2.11. assign_hf_value2(hf, hvalue)
+        1.2.12. include_hf_value(hf, hvalue)
+        1.2.13. exclude_hf_value(hf, hvalue)
+        1.2.14. hf_value_exists(hf, hvalue)
+        1.2.15. Selects
+
+              1.2.15.1. @hf_value
+              1.2.15.2. @hf_value2
+              1.2.15.3. @hf_value_exists
 
 1.1. Overview
 
@@ -115,3 +129,223 @@ if(fnmatch("$rU", "123*"))
     ...
 }
 ...
+
+1.2.6. append_hf_value(hf, hvalue)
+
+   Append new header value after an existing header, if no index acquired
+   append at the end of list. Note that a header may consist of comma
+   delimited list of values.
+
+   Meaning of the parameters is as follows:
+     * hf - Header field to be appended. Format: HFNAME [ [IDX] ]. If
+       index is not specified new header is inserted at the end of
+       message.
+     * hvalue - Value to be added, config var formatting supported.
+
+   Example 6. append_hf_value usage
+...
+append_hf_value("foo", "gogo;stamp=$Ts")   # add new header
+append_hf_value("foo[1]", "gogo")  # add new value behind first value
+append_hf_value("foo[-1]", "$var(Bar)") # try add value to the last header, if n
+ot exists add new header
+...
+
+1.2.7. insert_hf_value(hf, hvalue)
+
+   Insert new header value before an existing header, if no index acquired
+   insert before first hf header. Note that a header may consist of comma
+   delimited list of values. To insert value behing last value use
+   appenf_hf_value.
+
+   Meaning of the parameters is as follows:
+     * hf - Header field to be appended. Format: HFNAME [ [IDX] ]. If
+       index is not specified new header is inserted at the top of
+       message.
+     * hvalue - Value to be added, config var formatting supported.
+
+   Example 7. insert_hf_value usage
+...
+insert_hf_value("foo[2]", "gogo")
+insert_hf_value("foo", "$avp(foo)")   # add new header at the top of list
+insert_hf_value("foo[1]", "gogo") # try add to the first header
+...
+
+1.2.8. remove_hf_value(hf_par)
+
+   Remove the header value from existing header, Note that a header may
+   consist of comma delimited list of values.
+
+   Meaning of the parameters is as follows:
+     * hf_par - Header field/param to be removed. Format: HFNAME [ [IDX] ]
+       [. PARAM ] If asterisk is specified as index then all values are
+       affected.
+
+   Example 8. remove_hf_value usage
+...
+remove_hf_value("foo")  # remove foo[1]
+remove_hf_value("foo[*]")  # remove all foo's headers
+remove_hf_value("foo[-1]") # last foo
+remove_hf_value("foo.bar")  # delete parameter
+remove_hf_value("foo[*].bar") # for each foo delete bar parameters
+...
+
+1.2.9. remove_hf_value2(hf_par)
+
+   Remove specified header or parameter. It is expected header in
+   Authorization format (comma delimiters are not treated as multi-value
+   delimiters).
+
+   Meaning of the parameters is as follows:
+     * hf_par - Header/param to be removed. Format: HFNAME [ [IDX] ] [.
+       PARAM ] If asterisk is specified as index then all values are
+       affected.
+
+   Example 9. remove_hf_value2 usage
+...
+remove_hf_value2("foo")  # remove foo[1]
+remove_hf_value2("foo[*]")  # remove all foo's headers, the same as remove_hf_he
+ader("foo[*]");
+remove_hf_value2("foo[-1]") # last foo
+remove_hf_value2("foo.bar")  # delete parameter
+remove_hf_value2("foo[*].bar") # for each foo delete bar parameters
+...
+
+1.2.10. assign_hf_value(hf, hvalue)
+
+   Assign value to specified header value / param.
+
+   Meaning of the parameters is as follows:
+     * hf_para - Header field value / param to be appended. Format: HFNAME
+       [ [IDX] ] [. PARAM] If asterisk is specified as index then all
+       values are affected.
+     * hvalue - Value to be assigned, config var formatting supported. If
+       value is empty then no equal sign apears in param.
+
+   Example 10. assign_hf_value usage
+...
+assign_hf_value("foo", "gogo")  # foo[1]
+assign_hf_value("foo[-1]", "gogo")  # foo[last_foo]
+
+assign_hf_value("foo.bar", "")
+assign_hf_value("foo[3].bar", "")
+assign_hf_value("foo[*]", "")  # remove all foo's, empty value remains
+assign_hf_value("foo[*].bar", "")  # set empty value (ex. lr)
+...
+
+1.2.11. assign_hf_value2(hf, hvalue)
+
+   Assign value to specified header. It is expected header in
+   Authorization format (comma delimiters are not treated as multi-value
+   delimiters).
+
+   Meaning of the parameters is as follows:
+     * hf_para - Header field value / param to be appended. Format: HFNAME
+       [ [IDX] ] [. PARAM] If asterisk is specified as index then all
+       values are affected.
+     * hvalue - Value to be assigned, config var formatting supported. If
+       value is empty then no equal sign apears in param.
+
+   Example 11. assign_hf_value2 usage
+...
+assign_hf_value2("Authorization.integrity-protected", "\"yes\"")
+assign_hf_value2("foo[-1]", "gogo")  # foo[last_foo]
+assign_hf_value2("foo[*].bar", "")  # set empty value (ex. lr)
+...
+
+1.2.12. include_hf_value(hf, hvalue)
+
+   Add value in set if not exists, eg. "Supported: path,100rel".
+
+   Meaning of the parameters is as follows:
+     * hf - Header field name to be affected.
+     * hvalue - config var formatting supported.
+
+   Example 12. include_hf_value usage
+...
+include_hf_value("Supported", "path");
+...
+
+1.2.13. exclude_hf_value(hf, hvalue)
+
+   Remove value from set if exists, eg. "Supported: path,100rel".
+
+   Meaning of the parameters is as follows:
+     * hf - Header field name to be affected.
+     * hvalue - config formatting supported.
+
+   Example 13. exclude_hf_value usage
+...
+exclude_hf_value("Supported", "100rel");
+...
+
+1.2.14. hf_value_exists(hf, hvalue)
+
+   Check if value exists in set. Alternate select
+   @hf_value_exists.HF.VALUE may be used. It returns one or zero.
+
+   Meaning of the parameters is as follows:
+     * hf - Header field name to be affected. Underscores are treated as
+       dashes.
+     * hvalue - config var formatting supported.
+
+   Example 14. hf_value_exists usage
+...
+if (hf_value_exists("Supported", "100rel")) {
+
+}
+
+if (@hf_value_exists.supported.path == "1") {
+
+}
+...
+
+1.2.15. Selects
+
+1.2.15.1. @hf_value
+
+   Get value of required header-value or param. Note that functions called
+   'value2' works with Authorization-like headers where comma is not
+   treated as value delimiter. Formats: @hf_value.HFNAME[IDX] # idx value,
+   negative value counts from bottom @hf_value.HFNAME.PARAM_NAME
+   @hf_value.HFNAME[IDX].PARAM_NAME @hf_value.HFNAME.p.PARAM_NAME # or
+   .param., useful if requred called "uri", "p", "param"
+   @hf_value.HFNAME[IDX].p.PARAM_NAME # dtto @hf_value.HFNAME[IDX].uri #
+   (< & > excluded) @hf_value.HFNAME[*] # return comma delimited list of
+   all values (combines headers) @hf_value.HFNAME # the same as above [*]
+   but may be parsed by cfg.y @hf_value.HFNAME[*].uri # return comma
+   delimited list of uris (< & > excluded) @hf_value.HFNAME.uri # the same
+   as above [*] but may be parsed by cfg.y @hf_value.HFNAME[IDX].name #
+   returns name part, quotes excluded @hf_value.HFNAME.name # returns name
+   part of the first value @hf_value2.HFNAME # returns value of first
+   header @hf_value2.HFNAME[IDX] # returns value of idx's header
+   @hf_value2.HFNAME.PARAM_NAME @hf_value2.HFNAME[IDX].PARAM_NAME
+   @hf_value.HFNAME[IDX].uri # return URI, quotes excluded
+   @hf_value.HFNAME.p.uri # returns param named uri, not URI itself
+   @hf_value.HFNAME.p.name # returns param named name, not name itself
+   @hf_value.HFNAME[IDX].uri.name # any sel_any_uri nested features may be
+   used @hf_value.HFNAME[IDX].nameaddr.name # select_any_nameaddr
+
+   Meaning of the parameters is as follows:
+     * HFNAME - Header field name. Underscores are treated as dashes.
+     * IDX - Value index, negative value counts from bottom
+     * PARAM_NAME - name of parameter
+
+   Example 15. @hf_value select usage
+...
+$a = @hf_value.my_header[1].my_param;
+xplog("L_ERR", "$sel(@hf_value.via[-1]), $sel(@hf_value.from.tag)\n");
+$b = @hf_value.p_associated_uri;
+
+xplog("L_ERR", "Route uris: '$sel(@hf_value.route[*].uri)'\n");
+$rr = @hf_value.route.uri;
+
+$prt = @hf_value2.authorization.integrity_protected;
+...
+
+1.2.15.2. @hf_value2
+
+   TBA.
+
+1.2.15.3. @hf_value_exists
+
+   TBA.

+ 361 - 0
modules/textopsx/doc/functions.xml

@@ -146,4 +146,365 @@ if(fnmatch("$rU", "123*"))
 		</example>
 	</section>
 
+    <section id="append_hf_value">
+	<title>
+	    <function>append_hf_value(hf, hvalue)</function>
+	</title>
+	<para>
+		Append new header value after an existing header, if no index acquired append at the end of
+		list. Note that a header may consist of comma delimited list of values.
+	</para>
+	<para>Meaning of the parameters is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>hf</emphasis> - Header field to be appended. Format: HFNAME [ [IDX] ].
+		If index is not specified new header is inserted at the end of message.
+		</para>
+	    </listitem>
+	    <listitem>
+		<para><emphasis>hvalue</emphasis> - Value to be added, config var formatting supported.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title><function>append_hf_value</function> usage</title>
+	    <programlisting>
+...
+append_hf_value("foo", "gogo;stamp=$Ts")   # add new header
+append_hf_value("foo[1]", "gogo")  # add new value behind first value
+append_hf_value("foo[-1]", "$var(Bar)") # try add value to the last header, if not exists add new header
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="insert_hf_value">
+	<title>
+	    <function>insert_hf_value(hf, hvalue)</function>
+	</title>
+	<para>
+		Insert new header value before an existing header, if no index acquired insert before first
+		hf header. Note that a header may consist of comma delimited list of values.
+		To insert value behing last value use <function>appenf_hf_value</function>.
+	</para>
+	<para>Meaning of the parameters is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>hf</emphasis> - Header field to be appended. Format: HFNAME [ [IDX] ].
+		If index is not specified new header is inserted at the top of message.
+		</para>
+	    </listitem>
+	    <listitem>
+		<para><emphasis>hvalue</emphasis> - Value to be added, config var formatting supported.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title><function>insert_hf_value</function> usage</title>
+	    <programlisting>
+...
+insert_hf_value("foo[2]", "gogo")
+insert_hf_value("foo", "$avp(foo)")   # add new header at the top of list
+insert_hf_value("foo[1]", "gogo") # try add to the first header
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="remove_hf_value">
+	<title>
+	    <function>remove_hf_value(hf_par)</function>
+	</title>
+	<para>
+		Remove the header value from existing header, Note that a header may consist of comma delimited list of values.
+	</para>
+	<para>Meaning of the parameters is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>hf_par</emphasis> - Header field/param to be removed. Format: HFNAME [ [IDX] ] [. PARAM ]
+		If asterisk is specified as index then all values are affected.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title><function>remove_hf_value</function> usage</title>
+	    <programlisting>
+...
+remove_hf_value("foo")  # remove foo[1]
+remove_hf_value("foo[*]")  # remove all foo's headers
+remove_hf_value("foo[-1]") # last foo
+remove_hf_value("foo.bar")  # delete parameter
+remove_hf_value("foo[*].bar") # for each foo delete bar parameters
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="remove_hf_value2">
+	<title>
+	    <function>remove_hf_value2(hf_par)</function>
+	</title>
+	<para>
+		Remove specified header or parameter. It is expected header in Authorization format (comma delimiters are not treated as multi-value delimiters).
+
+	</para>
+	<para>Meaning of the parameters is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>hf_par</emphasis> - Header/param to be removed. Format: HFNAME [ [IDX] ] [. PARAM ]
+		If asterisk is specified as index then all values are affected.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title><function>remove_hf_value2</function> usage</title>
+	    <programlisting>
+...
+remove_hf_value2("foo")  # remove foo[1]
+remove_hf_value2("foo[*]")  # remove all foo's headers, the same as remove_hf_header("foo[*]");
+remove_hf_value2("foo[-1]") # last foo
+remove_hf_value2("foo.bar")  # delete parameter
+remove_hf_value2("foo[*].bar") # for each foo delete bar parameters
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="assign_hf_value">
+	<title>
+	    <function>assign_hf_value(hf, hvalue)</function>
+	</title>
+	<para>
+		Assign value to specified header value / param.
+	</para>
+	<para>Meaning of the parameters is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+			<para><emphasis>hf_para</emphasis> - Header field value / param to be appended.
+				Format: HFNAME [ [IDX] ] [. PARAM]
+		If asterisk is specified as index then all values are affected.
+		</para>
+	    </listitem>
+	    <listitem>
+			<para><emphasis>hvalue</emphasis> - Value to be assigned, config var
+				formatting supported. If value is empty then no equal sign apears in param.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title><function>assign_hf_value</function> usage</title>
+	    <programlisting>
+...
+assign_hf_value("foo", "gogo")  # foo[1]
+assign_hf_value("foo[-1]", "gogo")  # foo[last_foo]
+
+assign_hf_value("foo.bar", "")
+assign_hf_value("foo[3].bar", "")
+assign_hf_value("foo[*]", "")  # remove all foo's, empty value remains
+assign_hf_value("foo[*].bar", "")  # set empty value (ex. lr)
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="assign_hf_value2">
+	<title>
+	    <function>assign_hf_value2(hf, hvalue)</function>
+	</title>
+	<para>
+		Assign value to specified header. It is expected header in Authorization format (comma delimiters are not treated as multi-value delimiters).
+	</para>
+	<para>Meaning of the parameters is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>hf_para</emphasis> - Header field value / param to be appended. Format: HFNAME [ [IDX] ] [. PARAM]
+		If asterisk is specified as index then all values are affected.
+		</para>
+	    </listitem>
+	    <listitem>
+			<para><emphasis>hvalue</emphasis> - Value to be assigned, config var formatting supported.
+				If value is empty then no equal sign apears in param.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title><function>assign_hf_value2</function> usage</title>
+	    <programlisting>
+...
+assign_hf_value2("Authorization.integrity-protected", "\"yes\"")
+assign_hf_value2("foo[-1]", "gogo")  # foo[last_foo]
+assign_hf_value2("foo[*].bar", "")  # set empty value (ex. lr)
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="include_hf_value">
+	<title>
+	    <function>include_hf_value(hf, hvalue)</function>
+	</title>
+	<para>
+		Add value in set if not exists, eg. "Supported: path,100rel".
+	</para>
+	<para>Meaning of the parameters is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>hf</emphasis> - Header field name to be affected.
+		</para>
+	    </listitem>
+	    <listitem>
+		<para><emphasis>hvalue</emphasis> - config var formatting supported.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title><function>include_hf_value</function> usage</title>
+	    <programlisting>
+...
+include_hf_value("Supported", "path");
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="exclude_hf_value">
+	<title>
+	    <function>exclude_hf_value(hf, hvalue)</function>
+	</title>
+	<para>
+		Remove value from set if exists, eg. "Supported: path,100rel".
+	</para>
+	<para>Meaning of the parameters is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>hf</emphasis> - Header field name to be affected.
+		</para>
+	    </listitem>
+	    <listitem>
+		<para><emphasis>hvalue</emphasis> - config formatting supported.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title><function>exclude_hf_value</function> usage</title>
+	    <programlisting>
+...
+exclude_hf_value("Supported", "100rel");
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="hf_value_exists">
+	<title>
+	    <function>hf_value_exists(hf, hvalue)</function>
+	</title>
+	<para>
+		Check if value exists in set. Alternate select <emphasis>@hf_value_exists.HF.VALUE</emphasis>
+		may be used. It returns one or zero.
+	</para>
+	<para>Meaning of the parameters is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>hf</emphasis> - Header field name to be affected. Underscores are treated as dashes.
+		</para>
+	    </listitem>
+	    <listitem>
+		<para><emphasis>hvalue</emphasis> - config var formatting supported.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title><function>hf_value_exists</function> usage</title>
+	    <programlisting>
+...
+if (hf_value_exists("Supported", "100rel")) {
+
+}
+
+if (@hf_value_exists.supported.path == "1") {
+
+}
+...
+	    </programlisting>
+	</example>
+    </section>
+
+	<section>
+	<title>Selects</title>
+    <section id="sel.hf_value">
+	<title>@hf_value</title>
+	<para>
+		Get value of required header-value or param. Note that functions called 'value2'
+		works with Authorization-like headers where comma is not treated as value delimiter. Formats:
+		@hf_value.HFNAME[IDX]    # idx value, negative value counts from bottom
+		@hf_value.HFNAME.PARAM_NAME
+		@hf_value.HFNAME[IDX].PARAM_NAME
+		@hf_value.HFNAME.p.PARAM_NAME  # or .param., useful if requred called "uri", "p", "param"
+		@hf_value.HFNAME[IDX].p.PARAM_NAME # dtto
+		@hf_value.HFNAME[IDX].uri # (&lt; &amp; &gt; excluded)
+		@hf_value.HFNAME[*]     # return comma delimited list of all values (combines headers)
+		@hf_value.HFNAME        # the same as above [*] but may be parsed by cfg.y
+		@hf_value.HFNAME[*].uri # return comma delimited list of uris (&lt; &amp; &gt; excluded)
+		@hf_value.HFNAME.uri    # the same as above [*] but may be parsed by cfg.y
+		@hf_value.HFNAME[IDX].name  # returns name part, quotes excluded
+		@hf_value.HFNAME.name   # returns name part of the first value
+
+		@hf_value2.HFNAME        # returns value of first header
+		@hf_value2.HFNAME[IDX]   # returns value of idx's header
+		@hf_value2.HFNAME.PARAM_NAME
+		@hf_value2.HFNAME[IDX].PARAM_NAME
+
+		@hf_value.HFNAME[IDX].uri  # return URI, quotes excluded
+		@hf_value.HFNAME.p.uri  # returns param named uri, not URI itself
+		@hf_value.HFNAME.p.name # returns param named name, not name itself
+		@hf_value.HFNAME[IDX].uri.name #  any sel_any_uri nested features may be used
+		@hf_value.HFNAME[IDX].nameaddr.name # select_any_nameaddr
+	</para>
+	<para>Meaning of the parameters is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>HFNAME</emphasis> - Header field name. Underscores are treated as dashes.
+		</para>
+	    </listitem>
+	    <listitem>
+		<para><emphasis>IDX</emphasis> - Value index, negative value counts from bottom
+		</para>
+	    </listitem>
+	    <listitem>
+		<para><emphasis>PARAM_NAME</emphasis> - name of parameter
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title><function>@hf_value select</function> usage</title>
+	    <programlisting>
+...
+$a = @hf_value.my_header[1].my_param;
+xplog("L_ERR", "$sel(@hf_value.via[-1]), $sel(@hf_value.from.tag)\n");
+$b = @hf_value.p_associated_uri;
+
+xplog("L_ERR", "Route uris: '$sel(@hf_value.route[*].uri)'\n");
+$rr = @hf_value.route.uri;
+
+$prt = @hf_value2.authorization.integrity_protected;
+...
+	    </programlisting>
+	</example>
+    </section>
+    <section id="sel.hf_value2">
+	<title>@hf_value2</title>
+	<para>
+		TBA.
+	</para>
+    </section>
+    <section id="sel.hf_value_exists">
+	<title>@hf_value_exists</title>
+	<para>
+		TBA.
+	</para>
+    </section>
+
+    </section>
+
 </section>

+ 1394 - 0
modules/textopsx/textopsx.c

@@ -35,6 +35,10 @@
 #include "../../msg_translator.h"
 #include "../../tcp_options.h"
 #include "../../mod_fix.h"
+#include "../../parser/parse_hname2.h"
+#include "../../select.h"
+#include "../../select_buf.h"
+
 
 #include "api.h"
 
@@ -52,10 +56,26 @@ static int w_fnmatch3_f(sip_msg_t*, char*, char*, char*);
 static int fixup_fnmatch(void** param, int param_no);
 
 static int w_remove_body_f(struct sip_msg*, char*, char *);
+
+static int incexc_hf_value_f(struct sip_msg* msg, char* , char *);
+static int include_hf_value_fixup(void**, int);
+static int exclude_hf_value_fixup(void**, int);
+static int hf_value_exists_fixup(void**, int);
+
+static int insupddel_hf_value_f(struct sip_msg* msg, char* _hname, char* _val);
+static int append_hf_value_fixup(void** param, int param_no);
+static int insert_hf_value_fixup(void** param, int param_no);
+static int remove_hf_value_fixup(void** param, int param_no);
+static int assign_hf_value_fixup(void** param, int param_no);
+static int remove_hf_value2_fixup(void** param, int param_no);
+static int assign_hf_value2_fixup(void** param, int param_no);
+
 static int bind_textopsx(textopsx_api_t *tob);
 
 static int mod_init(void);
 
+extern select_row_t sel_declaration[];
+
 /* cfg functions */
 static cmd_export_t cmds[] = {
 	{"msg_apply_changes",    (cmd_function)msg_apply_changes_f,     0,
@@ -70,6 +90,26 @@ static cmd_export_t cmds[] = {
 		fixup_fnmatch, ANY_ROUTE },
 	{"fnmatch",              (cmd_function)w_fnmatch3_f,            3,
 		fixup_fnmatch, ANY_ROUTE },
+
+	{"append_hf_value",        insupddel_hf_value_f,         2, append_hf_value_fixup,
+			REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE},
+	{"insert_hf_value",        insupddel_hf_value_f,         2, insert_hf_value_fixup,
+			REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE},
+	{"remove_hf_value",        insupddel_hf_value_f,         1, remove_hf_value_fixup,
+			REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE},
+	{"assign_hf_value",        insupddel_hf_value_f,         2, assign_hf_value_fixup,
+			REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE},
+	{"remove_hf_value2",       insupddel_hf_value_f,         1, remove_hf_value2_fixup,
+			REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE},
+	{"assign_hf_value2",       insupddel_hf_value_f,         2, assign_hf_value2_fixup,
+			REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE},
+	{"include_hf_value", incexc_hf_value_f,      2, include_hf_value_fixup,
+			REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE},
+	{"exclude_hf_value", incexc_hf_value_f,      2, exclude_hf_value_fixup,
+			REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE},
+	{"hf_value_exists",  incexc_hf_value_f,      2, hf_value_exists_fixup,
+			REQUEST_ROUTE|ONREPLY_ROUTE|FAILURE_ROUTE|BRANCH_ROUTE},
+
 	{"bind_textopsx",        (cmd_function)bind_textopsx,           1,
 		0, ANY_ROUTE },
 
@@ -99,6 +139,7 @@ static int mod_init(void)
 #ifdef USE_TCP
 	tcp_set_clone_rcvbuf(1);
 #endif
+	register_select_table(sel_declaration);
 	return 0;
 }
 
@@ -428,3 +469,1356 @@ static int bind_textopsx(textopsx_api_t *tob){
 	tob->msg_apply_changes = msg_apply_changes_f;
 	return 0;
 }
+
+
+/**
+ * functions operating on header value
+ */
+#define HNF_ALL 0x01
+#define HNF_IDX 0x02
+
+#define MAX_HF_VALUE_STACK 10
+
+enum {hnoInsert, hnoAppend, hnoAssign, hnoRemove, hnoInclude, hnoExclude, hnoIsIncluded, hnoGetValue, hnoGetValueUri, hnoGetValueName, hnoRemove2, hnoAssign2, hnoGetValue2};
+
+struct hname_data {
+	int oper;
+	int htype;
+	str hname;
+	int flags;
+	int idx;
+	str param;
+};
+
+#define is_space(_p) ((_p) == '\t' || (_p) == '\n' || (_p) == '\r' || (_p) == ' ')
+
+#define eat_spaces(_p) \
+	while( is_space(*(_p)) ){\
+	(_p)++;}
+
+#define is_alphanum(_p) (((_p) >= 'a' && (_p) <= 'z') || ((_p) >= 'A' && (_p) <= 'Z') || ((_p) >= '0' && (_p) <= '9') || (_p) == '_' || (_p) == '-')
+
+#define eat_while_alphanum(_p) \
+	while ( is_alphanum(*(_p)) ) {\
+		(_p)++; }
+
+static int fixup_hvalue_param(void** param, int param_no) {
+	return fixup_spve_null(param, 1);
+}
+
+static int eval_hvalue_param(sip_msg_t *msg, gparam_t *val, str *s) {
+	if(fixup_get_svalue(msg, val, s)<0) {
+		LM_ERR("could not get string param value\n");
+		return E_UNSPEC;
+	}
+	return 1;
+}
+
+/* parse:  hname [ ([] | [*] | [number]) ] [ "." param ] */
+static int fixup_hname_param(char *hname, struct hname_data** h) {
+	struct hdr_field hdr;
+	char *savep, savec;
+
+	*h = pkg_malloc(sizeof(**h));
+	if (!*h) return E_OUT_OF_MEM;
+	memset(*h, 0, sizeof(**h));
+
+	memset(&hdr, 0, sizeof(hdr));
+	eat_spaces(hname);
+	(*h)->hname.s = hname;
+	savep = hname;
+	eat_while_alphanum(hname);
+	(*h)->hname.len = hname - (*h)->hname.s;
+	savec = *hname;
+	*hname = ':';
+	parse_hname2((*h)->hname.s, (*h)->hname.s+(*h)->hname.len+3, &hdr);
+	*hname = savec;
+
+	if (hdr.type == HDR_ERROR_T) goto err;
+	(*h)->htype = hdr.type;
+
+	eat_spaces(hname);
+	savep = hname;
+	if (*hname == '[') {
+		hname++;
+		eat_spaces(hname);
+		savep = hname;
+		(*h)->flags |= HNF_IDX;
+		if (*hname == '*') {
+			(*h)->flags |= HNF_ALL;
+			hname++;
+		}
+		else if (*hname != ']') {
+			char* c;
+			(*h)->idx = strtol(hname, &c, 10);
+			if (hname == c) goto err;
+			hname = c;
+		}
+		eat_spaces(hname);
+		savep = hname;
+		if (*hname != ']') goto err;
+		hname++;
+	}
+	eat_spaces(hname);
+	savep = hname;
+	if (*hname == '.') {
+		hname++;
+		eat_spaces(hname);
+		savep = hname;
+		(*h)->param.s = hname;
+		eat_while_alphanum(hname);
+		(*h)->param.len = hname-(*h)->param.s;
+		if ((*h)->param.len == 0) goto err;
+	}
+	else {
+		(*h)->param.s = hname;
+	}
+	savep = hname;
+	if (*hname != '\0') goto err;
+	(*h)->hname.s[(*h)->hname.len] = '\0';
+	(*h)->param.s[(*h)->param.len] = '\0';
+	return 0;
+err:
+	pkg_free(*h);
+	LOG(L_ERR, "ERROR: textops: cannot parse header near '%s'\n", savep);
+	return E_CFG;
+}
+
+static int fixup_hname_str(void** param, int param_no) {
+	if (param_no == 1) {
+		struct hname_data* h;
+		int res = fixup_hname_param(*param, &h);
+		if (res < 0) return res;
+		*param = h;
+	}
+	else if (param_no == 2) {
+		return fixup_hvalue_param(param, param_no);
+	}
+	return 0;
+}
+
+
+static int find_next_hf(struct sip_msg* msg, struct hname_data* hname, struct hdr_field** hf) {
+	if (!*hf) {
+		if (parse_headers(msg, HDR_EOH_F, 0) == -1) {
+			LOG(L_ERR, "ERROR: textops: find_next_hf: Error while parsing message\n");
+			return -1;
+		}
+		*hf = msg->headers;
+	}
+	else {
+		*hf = (*hf)->next;
+	}
+	for (; *hf; *hf = (*hf)->next) {
+		if (hname->htype == HDR_OTHER_T) {
+			if ((*hf)->name.len==hname->hname.len && strncasecmp((*hf)->name.s, hname->hname.s, (*hf)->name.len)==0)
+				return 1;
+		}
+		else if (hname->htype == (*hf)->type) {
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static int find_next_value(char** start, char* end, str* val, str* lump_val) {
+	int quoted = 0;
+	lump_val->s = *start;
+	while (*start < end && is_space(**start) ) (*start)++;
+	val->s = *start;
+	while ( *start < end && (**start != ',' || quoted) ) {
+		if (**start == '\"' && (!quoted || (*start)[-1]!='\\') )
+			quoted = ~quoted;
+		(*start)++;
+	}
+	val->len = *start - val->s;
+	while (val->len > 0 && is_space(val->s[val->len-1])) val->len--;
+/* we cannot automatically strip quotes!!! an example why: "name" <sip:ssss>;param="bar" 
+	if (val->len >= 2 && val->s[0] == '\"' && val->s[val->len-1] == '\"') {
+		val->s++;
+		val->len -= 2;
+	}
+*/
+	while (*start < end && **start != ',') (*start)++;
+	if (*start < end) {
+		(*start)++;
+	}
+	lump_val->len = *start - lump_val->s;
+	return (*start < end);
+}
+
+static void adjust_lump_val_for_delete(struct hdr_field* hf, str* lump_val) {
+	if ( lump_val->s+lump_val->len == hf->body.s+hf->body.len ) {
+		if (lump_val->s > hf->body.s) {
+		/* in case if is it last value in header save position of last delimiter to remove it with rightmost value */
+			lump_val->s--;
+			lump_val->len++;
+		}
+	}
+}
+
+static int find_hf_value_idx(struct sip_msg* msg, struct hname_data* hname, struct hdr_field** hf, str* val, str* lump_val) {
+	int res;
+	char *p;
+	if ( hname->flags & HNF_ALL || hname->idx == 0) return -1;
+	*hf = 0;
+	if (hname->idx > 0) {
+		int idx;
+		idx = hname->idx;
+		do {
+			res = find_next_hf(msg, hname, hf);
+			if (res < 0) return -1;
+			if (*hf) {
+				if (val) {
+					lump_val->len = 0;
+					p = (*hf)->body.s;
+					do {
+						res = find_next_value(&p, (*hf)->body.s+(*hf)->body.len, val, lump_val);
+						idx--;
+					} while (res && idx);
+				}
+				else {
+					idx--;
+				}
+			}
+		} while (*hf && idx);
+	}
+	else if (hname->idx < 0) {  /* search from the bottom */
+		struct hf_value_stack {
+			str val, lump_val;
+			struct hdr_field* hf;
+		} stack[MAX_HF_VALUE_STACK];
+		int stack_pos, stack_num;
+
+		if ( -hname->idx > MAX_HF_VALUE_STACK ) return -1;
+		stack_pos = stack_num = 0;
+		do {
+			res = find_next_hf(msg, hname, hf);
+			if (res < 0) return -1;
+			if (*hf) {
+				stack[stack_pos].lump_val.len = 0;
+				p = (*hf)->body.s;
+				do {
+					stack[stack_pos].hf = *hf;
+					if (val)
+						res = find_next_value(&p, (*hf)->body.s+(*hf)->body.len, &stack[stack_pos].val, &stack[stack_pos].lump_val);
+					else
+						res = 0;
+					stack_pos++;
+					if (stack_pos >= MAX_HF_VALUE_STACK)
+						stack_pos = 0;
+					if (stack_num < MAX_HF_VALUE_STACK)
+						stack_num++;
+
+				} while (res);
+			}
+		} while (*hf);
+
+		if (-hname->idx <= stack_num) {
+			stack_pos += hname->idx;
+			if (stack_pos < 0)
+				stack_pos += MAX_HF_VALUE_STACK;
+			*hf = stack[stack_pos].hf;
+			if (val) {
+				*val = stack[stack_pos].val;
+				*lump_val = stack[stack_pos].lump_val;
+			}
+		}
+		else {
+			*hf = 0;
+		}
+	}
+	else
+		return -1;
+	return *hf?1:0;
+}
+
+static int find_hf_value_param(struct hname_data* hname, str* param_area, str* value, str* lump_upd, str* lump_del) {
+	int i, j, found;
+
+	i = 0;
+	while (1) {
+		lump_del->s = param_area->s + i;
+		for (; i < param_area->len && is_space(param_area->s[i]); i++);
+		if (i < param_area->len && param_area->s[i] == ';') {	/* found a param ? */
+			i++;
+			for (; i < param_area->len && is_space(param_area->s[i]); i++);
+			j = i;
+			for (; i < param_area->len && !is_space(param_area->s[i]) && param_area->s[i]!='=' && param_area->s[i]!=';'; i++);
+
+			found = hname->param.len == i-j && !strncasecmp(hname->param.s, param_area->s+j, i-j);
+			lump_upd->s = param_area->s+i;
+			value->s = param_area->s+i;
+			value->len = 0;
+			for (; i < param_area->len && is_space(param_area->s[i]); i++);
+			if (i < param_area->len && param_area->s[i]=='=') {
+				i++;
+				for (; i < param_area->len && is_space(param_area->s[i]); i++);
+				value->s = param_area->s+i;
+				if (i < param_area->len) {
+					if (param_area->s[i]=='\"') {
+						i++;
+						value->s++;
+						for (; i<param_area->len; i++) {
+							if (param_area->s[i]=='\"') {
+								i++;
+								break;
+							}
+							value->len++;
+						}
+					}
+					else {
+						for (; i<param_area->len && !is_space(param_area->s[i]) && param_area->s[i]!=';'; i++, value->len++);
+					}
+				}
+			}
+			if (found) {
+				lump_del->len = param_area->s+i - lump_del->s;
+				lump_upd->len = param_area->s+i - lump_upd->s;
+				return 1;
+			}
+		}
+		else { /* not found, return last correct position, should be end of param area */
+			lump_del->len = 0;
+			return 0;
+		}
+	}
+}
+
+/* parse:  something param_name=param_value something [ "," something param_name="param_value" ....]
+ * 'something' is required by Authenticate
+ */
+static int find_hf_value2_param(struct hname_data* hname, str* param_area, str* value, str* lump_upd, str* lump_del, char* delim) {
+	int i, j, k, found, comma_flag;
+
+	i = 0;
+	*delim = 0;
+	lump_del->len = 0;
+	while (i < param_area->len) {
+
+		lump_del->s = param_area->s + i;
+		while (i<param_area->len && is_space(param_area->s[i])) i++;
+		comma_flag = i < param_area->len && param_area->s[i] == ',';
+		if (comma_flag) i++;
+		while (i<param_area->len && is_space(param_area->s[i])) i++;
+
+		if (i < param_area->len && is_alphanum(param_area->s[i])) {	/* found a param name ? */
+			j = i;
+			if (!*delim) *delim = ' ';
+			while (i<param_area->len && is_alphanum(param_area->s[i])) i++;
+
+			k = i;
+			while (i<param_area->len && is_space(param_area->s[i])) i++;
+			lump_upd->s = param_area->s + i;
+			if (i < param_area->len && param_area->s[i] == '=') {	/* if equal then it's the param */
+				*delim = ',';
+				i++;
+				found = hname->param.len == k-j && !strncasecmp(hname->param.s, param_area->s+j, k-j);
+				while (i<param_area->len && is_space(param_area->s[i])) i++;
+
+				value->s = param_area->s+i;
+				value->len = 0;
+				if (i < param_area->len) {
+					if (param_area->s[i]=='\"') {
+						i++;
+						value->s++;
+						for (; i<param_area->len; i++) {
+							if (param_area->s[i]=='\"') {
+								i++;
+								break;
+							}
+							value->len++;
+						}
+					}
+					else {
+						for (; i<param_area->len && !is_space(param_area->s[i]) && param_area->s[i]!=','; i++, value->len++);
+					}
+				}
+				if (found) {
+					lump_upd->len = param_area->s+i - lump_upd->s;
+					lump_del->len = param_area->s+i - lump_del->s;
+
+					while (i<param_area->len && is_space(param_area->s[i])) i++;
+
+					if (!comma_flag && i < param_area->len && param_area->s[i]==',') {
+						i++;
+						lump_del->len = param_area->s+i - lump_del->s;
+					}
+					return 1;
+				}
+			}
+			while (i<param_area->len && is_space(param_area->s[i])) i++;
+		}
+		else {
+			while (i<param_area->len && !is_space(param_area->s[i]) && !param_area->s[i]!=',') i++;
+		}
+	}
+	lump_del->s = param_area->s + i;
+	return 0;
+}
+
+static int insert_header_lump(struct sip_msg* msg, char* msg_position, int lump_before, str* hname, str *val) {
+	struct lump* anchor;
+	char *s;
+	int len;
+
+	anchor = anchor_lump(msg, msg_position - msg->buf, 0, 0);
+	if (anchor == 0) {
+		LOG(L_ERR, "ERROR: textops: insert_header_lump(): Can't get anchor\n");
+		return -1;
+	}
+
+	len=hname->len+2+val->len+2;
+
+	s = (char*)pkg_malloc(len);
+	if (!s) {
+		LOG(L_ERR, "ERROR: textops: insert_header_lump(): not enough memory\n");
+		return -1;
+	}
+
+	memcpy(s, hname->s, hname->len);
+	s[hname->len] = ':';
+	s[hname->len+1] = ' ';
+	memcpy(s+hname->len+2, val->s, val->len);
+	s[hname->len+2+val->len] = '\r';
+	s[hname->len+2+val->len+1] = '\n';
+
+	if ( (lump_before?insert_new_lump_before(anchor, s, len, 0):insert_new_lump_after(anchor, s, len, 0)) == 0) {
+		LOG(L_ERR, "ERROR: textops: insert_header_lump(): Can't insert lump\n");
+		pkg_free(s);
+		return -1;
+	}
+	return 1;
+}
+
+static int insert_value_lump(struct sip_msg* msg, struct hdr_field* hf, char* msg_position, int lump_before, str *val) {
+	struct lump* anchor;
+	char *s;
+	int len;
+
+	anchor = anchor_lump(msg, msg_position - msg->buf, 0, 0);
+	if (anchor == 0) {
+		LOG(L_ERR, "ERROR: textops: insert_value_lump(): Can't get anchor\n");
+		return -1;
+	}
+
+	len=val->len+1;
+
+	s = (char*)pkg_malloc(len);
+	if (!s) {
+		LOG(L_ERR, "ERROR: textops: insert_value_lump(): not enough memory\n");
+		return -1;
+	}
+
+	if (!hf) {
+		memcpy(s, val->s, val->len);
+		len--;
+	}
+	else if (msg_position == hf->body.s+hf->body.len) {
+		s[0] = ',';
+		memcpy(s+1, val->s, val->len);
+	}
+	else {
+		memcpy(s, val->s, val->len);
+		s[val->len] = ',';
+	}
+	if ( (lump_before?insert_new_lump_before(anchor, s, len, 0):insert_new_lump_after(anchor, s, len, 0)) == 0) {
+		LOG(L_ERR, "ERROR: textops: insert_value_lump(): Can't insert lump\n");
+		pkg_free(s);
+		return -1;
+	}
+	return 1;
+}
+
+static int delete_value_lump(struct sip_msg* msg, struct hdr_field* hf, str *val) {
+	struct lump* l;
+	/* TODO: check already existing lumps */
+	if (hf && val->s == hf->body.s && val->len == hf->body.len) 	/* check if remove whole haeder? */
+		l=del_lump(msg, hf->name.s-msg->buf, hf->len, 0);
+	else
+		l=del_lump(msg, val->s-msg->buf, val->len, 0);
+	if (l==0) {
+		LOG(L_ERR, "ERROR: textops: delete_value_lump: not enough memory\n");
+		return -1;
+	}
+	return 1;
+}
+
+static int incexc_hf_value_str_f(struct sip_msg* msg, char* _hname, str* _pval) {
+	struct hname_data* hname = (void*) _hname;
+	struct hdr_field* hf, *lump_hf;
+	str val, hval1, hval2;
+	char *p;
+	int res;
+
+	val = *_pval;
+	if (!val.len) return -1;
+	hf = 0;
+	lump_hf = 0;
+	while (1) {
+		if (find_next_hf(msg, hname, &hf) < 0) return -1;
+		if (!hf) break;
+		hval2.len = 0;
+		p = hf->body.s;
+		do {
+			res = find_next_value(&p, hf->body.s+hf->body.len, &hval1, &hval2);
+			if (hval1.len && val.len == hval1.len && strncasecmp(val.s, hval1.s, val.len) == 0) {
+				switch (hname->oper) {
+					case hnoIsIncluded:
+					case hnoInclude:
+						return 1;
+					case hnoExclude:
+						adjust_lump_val_for_delete(hf, &hval2);
+						delete_value_lump(msg, hf, &hval2);
+					default:
+						break;
+				}
+			}
+		} while (res);
+		switch (hname->oper) {
+			case hnoInclude:
+				if (!lump_hf) {
+					lump_hf = hf;
+				}
+				break;
+			default:
+				break;
+		}
+	}
+	switch (hname->oper) {
+		case hnoIsIncluded:
+			return -1;
+		case hnoInclude:
+			if (lump_hf)
+				return insert_value_lump(msg, lump_hf, lump_hf->body.s+lump_hf->body.len, 1, &val);
+			else
+				return insert_header_lump(msg, msg->unparsed, 1, &hname->hname, &val);
+		default:
+			return 1;
+	}
+}
+
+static int incexc_hf_value_f(struct sip_msg* msg, char* _hname, char* _val)
+{
+	str val;
+	int res;
+	
+	res = eval_hvalue_param(msg, (void*) _val, &val);
+	
+	if (res < 0) return res;
+	if (!val.len) return -1;
+
+	return incexc_hf_value_str_f(msg, _hname, &val);
+}
+
+#define INCEXC_HF_VALUE_FIXUP(_func,_oper) \
+static int _func (void** param, int param_no) {\
+	char* p = *param; \
+	int res=fixup_hname_str(param, param_no); \
+	if (res < 0) return res; \
+	if (param_no == 1) {\
+		if ( ((struct hname_data*)*param)->flags & HNF_IDX || ((struct hname_data*)*param)->param.len ) { \
+			LOG(L_ERR, "ERROR: textops: neither index nor param may be specified in '%s'\n", p);\
+			return E_CFG;\
+		}\
+		((struct hname_data*)*param)->oper = _oper;\
+	}\
+	return 0;\
+}
+
+INCEXC_HF_VALUE_FIXUP(include_hf_value_fixup, hnoInclude)
+INCEXC_HF_VALUE_FIXUP(exclude_hf_value_fixup, hnoExclude)
+INCEXC_HF_VALUE_FIXUP(hf_value_exists_fixup, hnoIsIncluded)
+
+static void get_uri_and_skip_until_params(str *param_area, str *name, str *uri) {
+	int i, quoted, uri_pos, uri_done;
+
+	name->len = 0;
+	uri->len = 0;
+	uri_done = 0;
+        name->s = param_area->s;
+	for (i=0; i<param_area->len && param_area->s[i]!=';'; ) {	/* [ *(token LSW)/quoted-string ] "<" addr-spec ">" | addr-spec */
+		/* skip name */
+		for (quoted=0, uri_pos=i; i<param_area->len; i++) {
+			if (!quoted) {
+				if (param_area->s[i] == '\"') {
+					quoted = 1;
+					uri_pos = -1;
+				}
+				else if (param_area->s[i] == '<' || param_area->s[i] == ';' || is_space(param_area->s[i])) break;
+			}
+			else if (param_area->s[i] == '\"' && param_area->s[i-1] != '\\') quoted = 0;
+		}
+		if (!name->len)
+			name->len = param_area->s+i-name->s;
+		if (uri_pos >= 0 && !uri_done) {
+			uri->s = param_area->s+uri_pos;
+			uri->len = param_area->s+i-uri->s;
+		}
+		/* skip uri */
+		while (i<param_area->len && is_space(param_area->s[i])) i++;
+		if (i<param_area->len && param_area->s[i]=='<') {
+			uri->s = param_area->s+i;
+			uri->len = 0;
+			for (quoted=0; i<param_area->len; i++) {
+				if (!quoted) {
+					if (param_area->s[i] == '\"') quoted = 1;
+					else if (param_area->s[i] == '>') {
+						uri->len = param_area->s+i-uri->s+1;
+						uri_done = 1;
+						break;
+					}
+				}
+				else if (param_area->s[i] == '\"' && param_area->s[i-1] != '\\') quoted = 0;
+			}
+		}
+	}
+        param_area->s+= i;
+	param_area->len-= i;
+	if (uri->s == name->s)
+		name->len = 0;
+}
+
+static int assign_hf_do_lumping(struct sip_msg* msg,struct hdr_field* hf, struct hname_data* hname, str* value, int upd_del_fl, str* lump_upd, str* lump_del, char delim) {
+	int len, i;
+	char *s;
+	struct lump* anchor;
+
+	if (upd_del_fl) {
+		len = value?lump_upd->len:lump_del->len;
+		if (len > 0) {
+			if (!del_lump(msg, (value?lump_upd->s:lump_del->s)-msg->buf, len, 0)) {
+				LOG(L_ERR, "ERROR: textops: assign_hf_do_lumping: not enough memory\n");
+				return -1;
+			}
+		}
+		if (value && value->len) {
+			anchor = anchor_lump(msg, lump_upd->s - msg->buf, 0, 0);
+			if (anchor == 0) {
+				LOG(L_ERR, "ERROR: textops: assign_hf_do_lumping: Can't get anchor\n");
+				return -1;
+			}
+
+			len = 1+value->len;
+			s = pkg_malloc(len);
+			if (!s) {
+				LOG(L_ERR, "ERROR: textops: assign_hf_do_lumping: not enough memory\n");
+				return -1;
+			}
+			s[0]='=';
+			memcpy(s+1, value->s, value->len);
+			if ( (insert_new_lump_before(anchor, s, len, 0)) == 0) {
+				LOG(L_ERR, "ERROR: textops: assign_hf_do_lumping: Can't insert lump\n");
+				pkg_free(s);
+				return -1;
+			}
+		}
+	}
+	else {
+		if (!value) return -1;
+
+		anchor = anchor_lump(msg, lump_del->s - msg->buf, 0, 0);
+		if (anchor == 0) {
+			LOG(L_ERR, "ERROR: textops: assign_hf_do_lumping: Can't get anchor\n");
+			return -1;
+		}
+
+		len = 1+hname->param.len+(value->len?value->len+1:0);
+		s = pkg_malloc(len);
+		if (!s) {
+			LOG(L_ERR, "ERROR: textops: assign_hf_do_lumping: not enough memory\n");
+			return -1;
+		}
+		if (delim) {
+			s[0] = delim;
+			i = 1;
+		}
+		else {
+			i = 0;
+			len--;
+		}
+		memcpy(s+i, hname->param.s, hname->param.len);
+		if (value->len) {
+			s[hname->param.len+i]='=';
+			memcpy(s+i+hname->param.len+1, value->s, value->len);
+		}
+
+		if ( (insert_new_lump_before(anchor, s, len, 0)) == 0) {
+			LOG(L_ERR, "ERROR: textops: assign_hf_do_lumping: Can't insert lump\n");
+			pkg_free(s);
+			return -1;
+		}
+	}
+	return 1;
+}
+
+
+static int assign_hf_process_params(struct sip_msg* msg, struct hdr_field* hf, struct hname_data* hname, str* value, str* value_area) {
+	int r, r2, res=0;
+	str param_area, lump_upd, lump_del, dummy_val, dummy_name, dummy_uri;
+	param_area = *value_area;
+	get_uri_and_skip_until_params(&param_area, &dummy_name, &dummy_uri);
+	do {
+		r = find_hf_value_param(hname, &param_area, &dummy_val, &lump_upd, &lump_del);
+		r2 = assign_hf_do_lumping(msg, hf, hname, value, r, &lump_upd, &lump_del, ';');
+		if (res == 0)
+			res = r2;
+		if (r && !value) {   /* remove all parameters */
+			param_area.len -= lump_del.s+lump_del.len-param_area.s;
+			param_area.s = lump_del.s+lump_del.len;
+		}
+	} while (!value && r);
+	return res;
+}
+
+static int assign_hf_process2_params(struct sip_msg* msg, struct hdr_field* hf, struct hname_data* hname, str* value) {
+	int r, r2, res = 0;
+	str param_area, lump_upd, lump_del, dummy_val;
+	char delim;
+
+	param_area = hf->body;
+
+	do {
+		r = find_hf_value2_param(hname, &param_area, &dummy_val, &lump_upd, &lump_del, &delim);
+		r2 = assign_hf_do_lumping(msg, hf, hname, value, r, &lump_upd, &lump_del, delim);
+		if (res == 0)
+			res = r2;
+		if (r && !value) {   /* remove all parameters */
+			param_area.len -= lump_del.s+lump_del.len-param_area.s;
+			param_area.s = lump_del.s+lump_del.len;
+		}
+	} while (!value && r);
+	return res;
+
+}
+
+static int insupddel_hf_value_f(struct sip_msg* msg, char* _hname, char* _val) {
+	struct hname_data* hname = (void*) _hname;
+	struct hdr_field* hf;
+	str val, hval1, hval2;
+	int res;
+
+	if (_val) {
+		res = eval_hvalue_param(msg, (void*) _val, &val);
+		if (res < 0) return res;
+	}
+	switch (hname->oper) {
+		case hnoAppend:
+			if ((hname->flags & HNF_IDX) == 0) {
+				if (parse_headers(msg, HDR_EOH_F, 0) == -1) {
+					LOG(L_ERR, "ERROR: textops: Error while parsing message\n");
+					return -1;
+				}
+				return insert_header_lump(msg, msg->unparsed, 1, &hname->hname, &val);
+			}
+			else {
+				res = find_hf_value_idx(msg, hname, &hf, &hval1, &hval2);
+				if (res < 0) return res;
+				if (hf) {
+					return insert_value_lump(msg, hf, hval2.s+hval2.len,
+							res /* insert after, except it is last value in header */, &val);
+				}
+				else {
+					return insert_header_lump(msg, msg->unparsed, 1, &hname->hname, &val);
+				}
+			}
+		case hnoInsert:
+			/* if !HNF_IDX is possible parse only until first hname header
+			 * but not trivial for HDR_OTHER_T header, not implemented */
+			res = find_hf_value_idx(msg, hname, &hf, &hval1, &hval2);
+			if (res < 0) return res;
+			if (hf && (hname->flags & HNF_IDX) == 0) {
+				return insert_header_lump(msg, hf->name.s, 1, &hname->hname, &val);
+			}
+			else if (!hf && hname->idx == 1) {
+				return insert_header_lump(msg, msg->unparsed, 1, &hname->hname, &val);
+			}
+			else if (hf) {
+				return insert_value_lump(msg, hf, hval2.s, 1, &val);
+			}
+			else
+				return -1;
+
+		case hnoRemove:
+		case hnoAssign:
+			if (hname->flags & HNF_ALL) {
+				struct hdr_field* hf = 0;
+				int fl = -1;
+				do {
+					res = find_next_hf(msg, hname, &hf);
+					if (res < 0) return res;
+					if (hf) {
+						if (!hname->param.len) {
+							fl = 1;
+							delete_value_lump(msg, hf, &hf->body);
+						}
+						else {
+							char *p;
+							hval2.len = 0;
+							p = hf->body.s;
+							do {
+								res = find_next_value(&p, hf->body.s+hf->body.len, &hval1, &hval2);
+								if (assign_hf_process_params(msg, hf, hname, _val?&val:0, &hval1) > 0)
+									fl = 1;
+							} while (res);
+						}
+					}
+				} while (hf);
+				return fl;
+			}
+			else {
+				res = find_hf_value_idx(msg, hname, &hf, &hval1, &hval2);
+				if (res < 0) return res;
+				if (hf) {
+					if (!hname->param.len) {
+						if (hname->oper == hnoRemove) {
+							adjust_lump_val_for_delete(hf, &hval2);
+							return delete_value_lump(msg, hf, &hval2);
+						}
+						else {
+							res = delete_value_lump(msg, 0 /* delete only value part */, &hval1);
+							if (res < 0) return res;
+							if (val.len) {
+								return insert_value_lump(msg, 0 /* do not add delims */, hval1.s, 1, &val);
+							}
+							return 1;
+						}
+					}
+					else {
+						return assign_hf_process_params(msg, hf, hname, _val?&val:0, &hval1);
+					}
+				}
+			}
+			break;
+		case hnoRemove2:
+		case hnoAssign2:
+			if (hname->flags & HNF_ALL) {
+				struct hdr_field* hf = 0;
+				int fl = -1;
+				do {
+					res = find_next_hf(msg, hname, &hf);
+					if (res < 0) return res;
+					if (hf) {
+						if (!hname->param.len) {  /* the same as hnoRemove/hnoAssign */
+							fl = 1;
+							delete_value_lump(msg, hf, &hf->body);
+						}
+						else {
+
+							if (assign_hf_process2_params(msg, hf, hname, _val?&val:0) > 0)
+								fl = 1;
+						}
+					}
+				} while (hf);
+				return fl;
+			}
+			else {
+				res = find_hf_value_idx(msg, hname, &hf, 0, 0);
+				if (res < 0) return res;
+				if (hf) {
+					if (!hname->param.len) {
+						if (hname->oper == hnoRemove2) {
+							return delete_value_lump(msg, hf, &hf->body);
+						}
+						else {
+							res = delete_value_lump(msg, 0 /* delete only value part */, &hf->body);
+							if (res < 0) return res;
+							if (val.len) {
+								return insert_value_lump(msg, 0 /* do not add delims */, hf->body.s, 1, &val);
+							}
+							return 1;
+						}
+					}
+					else {
+						return assign_hf_process2_params(msg, hf, hname, _val?&val:0);
+					}
+				}
+			}
+			break;
+	}
+	return -1;
+}
+
+static int append_hf_value_fixup(void** param, int param_no) {
+	int res=fixup_hname_str(param, param_no);
+	if (res < 0) return res;
+	if (param_no == 1) {
+		if ( ((struct hname_data*)*param)->flags & HNF_ALL ) {
+			LOG(L_ERR, "ERROR: textops: asterisk not supported\n");
+			return E_CFG;
+		} else if ( (((struct hname_data*)*param)->flags & HNF_IDX) == 0 || !((struct hname_data*)*param)->idx ) {
+			((struct hname_data*)*param)->idx = -1;
+		}
+		if (((struct hname_data*)*param)->idx < -MAX_HF_VALUE_STACK) {
+			LOG(L_ERR, "ERROR: textops: index cannot be lower than %d\n", -MAX_HF_VALUE_STACK);
+			return E_CFG;
+		}
+		if ( ((struct hname_data*)*param)->param.len ) {
+			LOG(L_ERR, "ERROR: textops: param not supported\n");
+			return E_CFG;
+		}
+		((struct hname_data*)*param)->oper = hnoAppend;
+	}
+	return 0;
+}
+
+static int insert_hf_value_fixup(void** param, int param_no) {
+	int res=fixup_hname_str(param, param_no);
+	if (res < 0) return res;
+	if (param_no == 1) {
+		if ( ((struct hname_data*)*param)->flags & HNF_ALL ) {
+			LOG(L_ERR, "ERROR: textops: asterisk not supported\n");
+			return E_CFG;
+		} else if ( (((struct hname_data*)*param)->flags & HNF_IDX) == 0 || !((struct hname_data*)*param)->idx ) {
+			((struct hname_data*)*param)->idx = 1;
+		}
+		if (((struct hname_data*)*param)->idx < -MAX_HF_VALUE_STACK) {
+			LOG(L_ERR, "ERROR: textops: index cannot be lower than %d\n", -MAX_HF_VALUE_STACK);
+			return E_CFG;
+		}
+		if ( ((struct hname_data*)*param)->param.len ) {
+			LOG(L_ERR, "ERROR: textops: param not supported\n");
+			return E_CFG;
+		}
+		((struct hname_data*)*param)->oper = hnoInsert;
+	}
+	return 0;
+}
+
+static int remove_hf_value_fixup(void** param, int param_no) {
+	int res=fixup_hname_str(param, param_no);
+	if (res < 0) return res;
+	if (param_no == 1) {
+		if ( (((struct hname_data*)*param)->flags & HNF_IDX) == 0 || !((struct hname_data*)*param)->idx ) {
+			((struct hname_data*)*param)->idx = 1;
+			((struct hname_data*)*param)->flags |= HNF_IDX;
+		}
+		if (((struct hname_data*)*param)->idx < -MAX_HF_VALUE_STACK) {
+			LOG(L_ERR, "ERROR: textops: index cannot be lower than %d\n", -MAX_HF_VALUE_STACK);
+			return E_CFG;
+		}
+		((struct hname_data*)*param)->oper = hnoRemove;
+	}
+	return 0;
+}
+
+static int assign_hf_value_fixup(void** param, int param_no) {
+	int res=fixup_hname_str(param, param_no);
+	if (res < 0) return res;
+	if (param_no == 1) {
+		if ( (((struct hname_data*)*param)->flags & HNF_ALL) && !((struct hname_data*)*param)->param.len) {
+			LOG(L_ERR, "ERROR: textops: asterisk not supported without param\n");
+			return E_CFG;
+		} else if ( (((struct hname_data*)*param)->flags & HNF_IDX) == 0 || !((struct hname_data*)*param)->idx ) {
+			((struct hname_data*)*param)->idx = 1;
+			((struct hname_data*)*param)->flags |= HNF_IDX;
+		}
+		if (((struct hname_data*)*param)->idx < -MAX_HF_VALUE_STACK) {
+			LOG(L_ERR, "ERROR: textops: index cannot be lower than %d\n", -MAX_HF_VALUE_STACK);
+			return E_CFG;
+		}
+		((struct hname_data*)*param)->oper = hnoAssign;
+	}
+	return 0;
+}
+
+static int remove_hf_value2_fixup(void** param, int param_no) {
+	int res=remove_hf_value_fixup(param, param_no);
+	if (res < 0) return res;
+	if (param_no == 1) {
+		((struct hname_data*)*param)->oper = hnoRemove2;
+	}
+	return 0;
+}
+
+static int assign_hf_value2_fixup(void** param, int param_no) {
+	int res=assign_hf_value_fixup(param, param_no);
+	if (res < 0) return res;
+	if (param_no == 1) {
+		((struct hname_data*)*param)->oper = hnoAssign2;
+	}
+	return 0;
+}
+
+
+/* select implementation */
+static int sel_hf_value(str* res, select_t* s, struct sip_msg* msg) {  /* dummy */
+	return 0;
+}
+
+#define _ALLOC_INC_SIZE 1024
+
+static int sel_hf_value_name(str* res, select_t* s, struct sip_msg* msg) {
+	struct hname_data* hname;
+	struct hdr_field* hf;
+	str val, hval1, hval2, huri, dummy_name;
+	int r;
+	if (!msg) {
+		struct hdr_field hdr;
+		char buf[50];
+		int i, n;
+
+		if (s->params[1].type == SEL_PARAM_STR) {
+			hname = pkg_malloc(sizeof(*hname));
+			if (!hname) return E_OUT_OF_MEM;
+			memset(hname, 0, sizeof(*hname));
+
+			for (i=s->params[1].v.s.len-1; i>0; i--) {
+				if (s->params[1].v.s.s[i]=='_')
+					s->params[1].v.s.s[i]='-';
+			}
+			i = snprintf(buf, sizeof(buf)-1, "%.*s: X\n", s->params[1].v.s.len, s->params[1].v.s.s);
+			buf[i] = 0;
+
+			hname->hname = s->params[1].v.s;
+			parse_hname2(buf, buf+i, &hdr);
+
+			if (hdr.type == HDR_ERROR_T) return E_CFG;
+			hname->htype = hdr.type;
+
+			s->params[1].v.p = hname;
+			s->params[1].type = SEL_PARAM_PTR;
+		}
+		else {
+			hname = s->params[1].v.p;
+		}
+		n = s->param_offset[select_level+1] - s->param_offset[select_level];  /* number of values before NESTED */
+		if (n > 2 && s->params[2].type == SEL_PARAM_INT) {
+			hname->idx = s->params[2].v.i;
+			hname->flags |= HNF_IDX;
+			if (hname->idx < -MAX_HF_VALUE_STACK) {
+				LOG(L_ERR, "ERROR: textops: index cannot be lower than %d\n", -MAX_HF_VALUE_STACK);
+				return E_CFG;
+			}
+			if (hname->idx == 0)
+				hname->idx = 1;
+			i = 3;
+		}
+		else {
+			i = 2;
+			hname->idx = 1;
+		}
+		if (n > i && s->params[i].type == SEL_PARAM_STR) {
+			hname->param = s->params[i].v.s;
+			for (i=hname->param.len-1; i>0; i--) {
+				if (hname->param.s[i]=='_')
+					hname->param.s[i]='-';
+			}
+
+		}
+		s->params[1].v.p = hname;
+		s->params[1].type = SEL_PARAM_PTR;
+		hname->oper = hnoGetValue;
+
+		return 0;
+	}
+
+	res->len = 0;
+	res->s = 0;
+	hname = s->params[1].v.p;
+
+	switch (hname->oper) {
+		case hnoGetValueUri:
+			if (hname->flags & HNF_ALL || (hname->flags & HNF_IDX) == 0) {
+				char *buf = NULL;
+				int buf_len = 0;
+				
+				hf = 0;
+				do {
+					r = find_next_hf(msg, hname, &hf);
+					if (r < 0) break;
+					if (hf) {
+						char *p;
+						str huri;
+						hval2.len = 0;
+						p = hf->body.s;
+						do {
+							r = find_next_value(&p, hf->body.s+hf->body.len, &hval1, &hval2);
+							get_uri_and_skip_until_params(&hval1, &dummy_name, &huri);
+							if (huri.len) {
+							/* TODO: normalize uri, lowercase except quoted params, add/strip < > */
+								if (*huri.s == '<') {
+									huri.s++;
+									huri.len -= 2;
+								}
+							}							
+							if (res->len == 0) {  
+								*res = huri; /* first value, if is also last value then we don't need any buffer */
+							}
+							else {
+								if (buf) {
+									if (res->len+huri.len+1 > buf_len) {
+										buf_len = res->len+huri.len+1+_ALLOC_INC_SIZE;
+										res->s = pkg_realloc(buf, buf_len);
+										if (!res->s) {
+											pkg_free(buf);
+											LOG(L_ERR, "ERROR: textops: cannot realloc buffer\n");
+											res->len = 0;
+											return E_OUT_OF_MEM;
+										}
+										buf = res->s;
+									}
+								}
+								else {
+									/* 2nd value */
+									buf_len = res->len+huri.len+1+_ALLOC_INC_SIZE;
+									buf = pkg_malloc(buf_len);
+									if (!buf) { 
+										LOG(L_ERR, "ERROR: testops: out of memory\n");
+										res->len = 0;
+										return E_OUT_OF_MEM;
+									}
+									/* copy 1st value */
+									memcpy(buf, res->s, res->len);								
+									res->s = buf;
+								}
+								res->s[res->len] = ',';
+								res->len++;
+								if (huri.len) {
+									memcpy(res->s+res->len, huri.s, huri.len);
+									res->len += huri.len;
+								}
+							}
+						
+						} while (r);
+					}
+				} while (hf);
+				if (buf) {
+					res->s = get_static_buffer(res->len);
+					if (!res->s) {
+						pkg_free(buf);
+						res->len = 0;
+						LOG(L_ERR, "ERROR: testops: cannot allocate static buffer\n");
+						return E_OUT_OF_MEM;
+					}
+					memcpy(res->s, buf, res->len);
+					pkg_free(buf);
+				}
+			}
+			else {
+				r = find_hf_value_idx(msg, hname, &hf, &hval1, &hval2);
+				if (r > 0) {
+					get_uri_and_skip_until_params(&hval1, &dummy_name, res);
+					if (res->len && *res->s == '<') {
+						res->s++;   	/* strip < & > */
+						res->len-=2;
+					}
+				}
+			}
+			break;
+		case hnoGetValueName:
+			if ((hname->flags & HNF_ALL) == 0) {
+				r = find_hf_value_idx(msg, hname, &hf, &hval1, &hval2);
+				if (r > 0) {
+					get_uri_and_skip_until_params(&hval1, res, &dummy_name);
+					if (res->len >= 2 && res->s[0] == '\"' && res->s[res->len-1]=='\"' ) {
+						res->s++;   	/* strip quotes */
+						res->len-=2;
+					}
+				}
+			}
+			break;
+		case hnoGetValue:
+			if (hname->flags & HNF_ALL || (hname->flags & HNF_IDX) == 0) {
+				char *buf = NULL;
+				int buf_len = 0;
+
+				hf = 0;
+				do {
+					r = find_next_hf(msg, hname, &hf);
+					
+					if (r < 0) break;
+					if (hf) {
+						char *p;
+						hval2.len = 0;
+						p = hf->body.s;
+						do {
+							r = find_next_value(&p, hf->body.s+hf->body.len, &hval1, &hval2);
+							if (res->len == 0) {  
+								*res = hval1; /* first value, if is also last value then we don't need any buffer */
+							}
+							else {
+								if (buf) {
+									if (res->len+hval1.len+1 > buf_len) {
+										buf_len = res->len+hval1.len+1+_ALLOC_INC_SIZE;
+										res->s = pkg_realloc(buf, buf_len);
+										if (!res->s) {
+											pkg_free(buf);
+											LOG(L_ERR, "ERROR: textops: cannot realloc buffer\n");
+											res->len = 0;
+											return E_OUT_OF_MEM;
+										}
+										buf = res->s;
+									}
+								}
+								else {
+									/* 2nd value */
+									buf_len = res->len+hval1.len+1+_ALLOC_INC_SIZE;
+									buf = pkg_malloc(buf_len);
+									if (!buf) { 
+										LOG(L_ERR, "ERROR: testops: out of memory\n");
+										res->len = 0;
+										return E_OUT_OF_MEM;
+									}
+									/* copy 1st value */
+									memcpy(buf, res->s, res->len);								
+									res->s = buf;
+								}
+								res->s[res->len] = ',';
+								res->len++;
+								if (hval1.len) {
+									memcpy(res->s+res->len, hval1.s, hval1.len);
+									res->len += hval1.len;
+								}
+							}
+						} while (r);
+					}
+				} while (hf);
+				if (buf) {
+					res->s = get_static_buffer(res->len);
+					if (!res->s) {
+						pkg_free(buf);
+						res->len = 0;
+						LOG(L_ERR, "ERROR: testops: cannot allocate static buffer\n");
+						return E_OUT_OF_MEM;
+					}
+					memcpy(res->s, buf, res->len);
+					pkg_free(buf);
+				}
+			}
+			else {
+				r = find_hf_value_idx(msg, hname, &hf, &hval1, &hval2);
+				if (r > 0) {
+					if (hname->param.len) {
+						str d1, d2;
+						get_uri_and_skip_until_params(&hval1, &dummy_name, &huri);
+						if (find_hf_value_param(hname, &hval1, &val, &d1, &d2)) {
+							*res = val;
+						}
+					}
+					else {
+						*res = hval1;
+					}
+				}
+			}
+			break;
+		case hnoGetValue2:
+			r = find_hf_value_idx(msg, hname, &hf, 0, 0);
+			if (r > 0) {
+				if (hname->param.len) {
+					str d1, d2;
+					char c;
+					if (find_hf_value2_param(hname, &hf->body, &val, &d1, &d2, &c)) {
+						*res = val;
+					}
+				}
+				else {
+					*res = hf->body;
+				}
+			}
+			break;
+		default:
+			break;
+	}
+	return 0;
+}
+
+static int sel_hf_value_name_param_name(str* res, select_t* s, struct sip_msg* msg) {
+	return sel_hf_value_name(res, s, msg);
+}
+
+static int sel_hf_value_name_param_name2(str* res, select_t* s, struct sip_msg* msg) {
+	if (!msg) { /* eliminate "param" level */
+		int n;
+		n = s->param_offset[select_level+1] - s->param_offset[select_level];
+		s->params[n-2] = s->params[n-1];
+	}
+	return sel_hf_value_name(res, s, msg);
+}
+
+static int sel_hf_value_name_uri(str* res, select_t* s, struct sip_msg* msg) {
+	int r;
+	r = sel_hf_value_name(res, s, msg);
+	if (!msg && r==0) {
+		((struct hname_data*) s->params[1].v.p)->oper = hnoGetValueUri;
+	}
+	return r;
+}
+
+static int sel_hf_value_name_name(str* res, select_t* s, struct sip_msg* msg) {
+	int r;
+	r = sel_hf_value_name(res, s, msg);
+	if (!msg && r==0) {
+		((struct hname_data*) s->params[1].v.p)->oper = hnoGetValueName;
+	}
+	return r;
+}
+
+static int sel_hf_value_exists(str* res, select_t* s, struct sip_msg* msg) {  /* dummy */
+	return 0;
+}
+
+static int sel_hf_value_exists_param(str* res, select_t* s, struct sip_msg* msg) {
+	static char ret_val[] = "01";
+    int r;
+
+	if (!msg) {
+		r = sel_hf_value_name(res, s, msg);
+		if (r == 0)
+			((struct hname_data*) s->params[1].v.p)->oper = hnoIsIncluded;
+		return r;
+	}
+	r = incexc_hf_value_str_f(msg, s->params[1].v.p, &s->params[2].v.s);
+	res->s = &ret_val[r > 0];
+	res->len = 1;
+
+	return 0;
+}
+
+static int sel_hf_value2(str* res, select_t* s, struct sip_msg* msg) {  /* dummy */
+	return 0;
+}
+
+static int sel_hf_value2_name(str* res, select_t* s, struct sip_msg* msg) {
+	int r;
+	r = sel_hf_value_name(res, s, msg);
+	if (!msg && r==0) {
+		((struct hname_data*) s->params[1].v.p)->oper = hnoGetValue2;
+	}
+	return r;
+}
+
+static int sel_hf_value2_name_param_name(str* res, select_t* s, struct sip_msg* msg) {
+	return sel_hf_value2_name(res, s, msg);
+}
+
+SELECT_F(select_any_nameaddr)
+SELECT_F(select_any_uri)
+SELECT_F(select_anyheader_params)
+
+select_row_t sel_declaration[] = {
+	{ NULL, SEL_PARAM_STR, STR_STATIC_INIT("hf_value"), sel_hf_value, SEL_PARAM_EXPECTED},
+
+	{ sel_hf_value, SEL_PARAM_STR, STR_NULL, sel_hf_value_name, CONSUME_NEXT_INT | OPTIONAL | FIXUP_CALL},
+	{ sel_hf_value_name, SEL_PARAM_STR, STR_STATIC_INIT("param"), sel_hf_value_name_param_name2, CONSUME_NEXT_STR | FIXUP_CALL},
+	{ sel_hf_value_name, SEL_PARAM_STR, STR_STATIC_INIT("p"), sel_hf_value_name_param_name2, CONSUME_NEXT_STR | FIXUP_CALL},
+	{ sel_hf_value_name, SEL_PARAM_STR, STR_STATIC_INIT("uri"), sel_hf_value_name_uri, FIXUP_CALL},
+	{ sel_hf_value_name, SEL_PARAM_STR, STR_STATIC_INIT("name"), sel_hf_value_name_name, FIXUP_CALL},
+	{ sel_hf_value_name, SEL_PARAM_STR, STR_STATIC_INIT("nameaddr"), select_any_nameaddr, NESTED | CONSUME_NEXT_STR}, /* it duplicates param,p,name,... */
+	{ sel_hf_value_name, SEL_PARAM_STR, STR_STATIC_INIT("params"), select_anyheader_params, NESTED},
+
+	{ sel_hf_value_name_uri, SEL_PARAM_INT, STR_NULL, select_any_uri, NESTED},
+	{ sel_hf_value_name, SEL_PARAM_STR, STR_NULL, sel_hf_value_name_param_name, FIXUP_CALL},
+
+	{ NULL, SEL_PARAM_STR, STR_STATIC_INIT("hf_value_exists"), sel_hf_value_exists, CONSUME_NEXT_STR | SEL_PARAM_EXPECTED},
+	{ sel_hf_value_exists, SEL_PARAM_STR, STR_NULL, sel_hf_value_exists_param, FIXUP_CALL},
+
+	{ NULL, SEL_PARAM_STR, STR_STATIC_INIT("hf_value2"), sel_hf_value2, SEL_PARAM_EXPECTED},
+	{ sel_hf_value2, SEL_PARAM_STR, STR_NULL, sel_hf_value2_name, CONSUME_NEXT_INT | OPTIONAL | FIXUP_CALL},
+	{ sel_hf_value2_name, SEL_PARAM_STR, STR_STATIC_INIT("params"), select_anyheader_params, NESTED},
+	{ sel_hf_value2_name, SEL_PARAM_STR, STR_NULL, sel_hf_value2_name_param_name, FIXUP_CALL},
+	{ sel_hf_value2_name_param_name, SEL_PARAM_STR, STR_STATIC_INIT("nameaddr"), select_any_nameaddr, NESTED},
+	{ sel_hf_value2_name_param_name, SEL_PARAM_STR, STR_STATIC_INIT("uri"), select_any_uri, NESTED},
+
+	{ NULL, SEL_PARAM_INT, STR_NULL, NULL, 0}
+};