From: Sipwise Development Team <support@sipwise.com>
Date: Mon, 18 Nov 2019 10:36:39 +0100
Subject: add_pv_headers_module

---
 src/Makefile.groups                             |    3 +-
 src/modules/pv_headers/Makefile                 |   12 +
 src/modules/pv_headers/README                   |  337 +++++++
 src/modules/pv_headers/doc/Makefile             |    4 +
 src/modules/pv_headers/doc/functions.xml        |  131 +++
 src/modules/pv_headers/doc/params.xml           |  186 ++++
 src/modules/pv_headers/doc/pv_headers.xml       |   39 +
 src/modules/pv_headers/doc/pv_headers_admin.xml |  116 +++
 src/modules/pv_headers/pv_headers.c             |  548 +++++++++++
 src/modules/pv_headers/pv_headers.h             |   57 ++
 src/modules/pv_headers/pvh_func.c               |  360 +++++++
 src/modules/pv_headers/pvh_func.h               |   42 +
 src/modules/pv_headers/pvh_hash.c               |  134 +++
 src/modules/pv_headers/pvh_hash.h               |   41 +
 src/modules/pv_headers/pvh_hdr.c                |  295 ++++++
 src/modules/pv_headers/pvh_hdr.h                |   47 +
 src/modules/pv_headers/pvh_str.c                |  146 +++
 src/modules/pv_headers/pvh_str.h                |   40 +
 src/modules/pv_headers/pvh_xavp.c               | 1158 +++++++++++++++++++++++
 src/modules/pv_headers/pvh_xavp.h               |   62 ++
 20 files changed, 3757 insertions(+), 1 deletion(-)
 create mode 100644 src/modules/pv_headers/Makefile
 create mode 100644 src/modules/pv_headers/README
 create mode 100644 src/modules/pv_headers/doc/Makefile
 create mode 100644 src/modules/pv_headers/doc/functions.xml
 create mode 100644 src/modules/pv_headers/doc/params.xml
 create mode 100644 src/modules/pv_headers/doc/pv_headers.xml
 create mode 100644 src/modules/pv_headers/doc/pv_headers_admin.xml
 create mode 100644 src/modules/pv_headers/pv_headers.c
 create mode 100644 src/modules/pv_headers/pv_headers.h
 create mode 100644 src/modules/pv_headers/pvh_func.c
 create mode 100644 src/modules/pv_headers/pvh_func.h
 create mode 100644 src/modules/pv_headers/pvh_hash.c
 create mode 100644 src/modules/pv_headers/pvh_hash.h
 create mode 100644 src/modules/pv_headers/pvh_hdr.c
 create mode 100644 src/modules/pv_headers/pvh_hdr.h
 create mode 100644 src/modules/pv_headers/pvh_str.c
 create mode 100644 src/modules/pv_headers/pvh_str.h
 create mode 100644 src/modules/pv_headers/pvh_xavp.c
 create mode 100644 src/modules/pv_headers/pvh_xavp.h

diff --git a/src/Makefile.groups b/src/Makefile.groups
index 001307f..e426b78 100644
--- a/src/Makefile.groups
+++ b/src/Makefile.groups
@@ -15,7 +15,8 @@ mod_list_basic=async auth benchmark blst cfg_rpc cfgutils corex counters \
 				   nat_traversal nathelper path pike pv ratelimit rr rtimer \
 				   rtpproxy sanity sdpops siputils sl statistics textops \
 				   textopsx tm tmx topoh xlog rtpengine stun sipt tcpops \
-				   auth_xkeys smsops tsilo cfgt statsc topos sipdump kemix
+				   auth_xkeys smsops tsilo cfgt statsc topos sipdump kemix \
+				   pv_headers
 
 # - extra used modules, with no extra dependency
 mod_list_extra=avp auth_diameter call_control call_obj dmq domainpolicy msrp \
diff --git a/src/modules/pv_headers/Makefile b/src/modules/pv_headers/Makefile
new file mode 100644
index 0000000..42b80dd
--- /dev/null
+++ b/src/modules/pv_headers/Makefile
@@ -0,0 +1,12 @@
+#
+# pv_headers module makefile
+#
+#
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=pv_headers.so
+LIBS=
+
+include ../../Makefile.modules
diff --git a/src/modules/pv_headers/README b/src/modules/pv_headers/README
new file mode 100644
index 0000000..f637eb2
--- /dev/null
+++ b/src/modules/pv_headers/README
@@ -0,0 +1,337 @@
+pv_headers Module
+
+Kirill Solomko
+
+   Sipwise GmbH
+   <ksolomko@sipwise.com>
+
+Edited by
+
+Victor Seva
+
+   Sipwise GmbH
+   <vseva@sipwise.com>
+
+   Copyright © 2018 Sipwise GmbH
+     __________________________________________________________________
+
+   Table of Contents
+
+   1. Admin Guide
+
+        1. Overview
+        2. Dependencies
+
+              2.1. Kamailio Modules
+              2.2. External Libraries or Applications
+
+        3. Parameters
+
+              3.1. xavi_name (string)
+              3.2. header_value_size (int)
+              3.3. header_collect_flag (int)
+              3.4. header_apply_flag (int)
+              3.5. skip_headers (string)
+              3.6. split_headers (string)
+              3.7. auto_msg (int)
+
+        4. Functions
+
+              4.1. pvh_collect_headers()
+              4.2. pvh_apply_headers()
+              4.3. pvh_reset_headers()
+              4.4. pvh_check_header(hname)
+              4.5. pvh_append_header(hname, hvalue)
+              4.6. pvh_modify_header(hname, hvalue, [idx])
+              4.7. pvh_remove_header(hname, [idx])
+
+        5. Exported Variables
+
+   List of Examples
+
+   1.1. Set xavi_name parameter
+   1.2. Set header_value_size parameter
+   1.3. Set header_collect_flag parameter
+   1.4. Set header_apply_flag parameter
+   1.5. Set skip_headers parameter
+   1.6. Set split_headers parameter
+   1.7. Set auto_msg parameter
+
+Chapter 1. Admin Guide
+
+   Table of Contents
+
+   1. Overview
+   2. Dependencies
+
+        2.1. Kamailio Modules
+        2.2. External Libraries or Applications
+
+   3. Parameters
+
+        3.1. xavi_name (string)
+        3.2. header_value_size (int)
+        3.3. header_collect_flag (int)
+        3.4. header_apply_flag (int)
+        3.5. skip_headers (string)
+        3.6. split_headers (string)
+        3.7. auto_msg (int)
+
+   4. Functions
+
+        4.1. pvh_collect_headers()
+        4.2. pvh_apply_headers()
+        4.3. pvh_reset_headers()
+        4.4. pvh_check_header(hname)
+        4.5. pvh_append_header(hname, hvalue)
+        4.6. pvh_modify_header(hname, hvalue, [idx])
+        4.7. pvh_remove_header(hname, [idx])
+
+   5. Exported Variables
+
+1. Overview
+
+   The main goal of the module is to offload the intermediate header
+   processing into the XAVI dynamic container as well as provide with high
+   level methods and pseudovariables to simplify SIP message header
+   modifications.
+
+2. Dependencies
+
+   2.1. Kamailio Modules
+   2.2. External Libraries or Applications
+
+2.1. Kamailio Modules
+
+   The following modules must be loaded before this module:
+     * uac.
+     * tm.
+       Needed only if "auto_msg" parameter is set to 1.
+
+2.2. External Libraries or Applications
+
+   The following libraries or applications must be installed before
+   running Kamailio with this module loaded:
+     * None.
+
+3. Parameters
+
+   3.1. xavi_name (string)
+   3.2. header_value_size (int)
+   3.3. header_collect_flag (int)
+   3.4. header_apply_flag (int)
+   3.5. skip_headers (string)
+   3.6. split_headers (string)
+   3.7. auto_msg (int)
+
+3.1. xavi_name (string)
+
+   Name of the XAVI where the collected headers are stored.
+
+   Default value is "headers".
+
+   Example 1.1. Set xavi_name parameter
+...
+modparam("pv_headers", "xavi_name", "headers")
+...
+
+   Result: $xavi(headers[0]=>From) $xavi(headers[0]=>To)
+   $xavi(headers[0]=>Call-ID)
+
+3.2. header_value_size (int)
+
+   Defines an internal maximum SIP header value size. Header values longer
+   than this setting will be stripped down when collected or applied.
+
+   Default value is 1024.
+
+   Example 1.2. Set header_value_size parameter
+...
+modparam("pv_headers", "header_value_size", 512)
+...
+
+3.3. header_collect_flag (int)
+
+   Used to mark that headers are collected for the SIP message, leading to
+   subsequent headers collection on this message to be declined with an
+   error. Should be used only in branches and replies.
+
+   Default value is 27.
+
+   Example 1.3. Set header_collect_flag parameter
+...
+modparam("pv_headers", "header_collect_flag", 37)
+...
+
+3.4. header_apply_flag (int)
+
+   Used to mark that headers are applied for the SIP message, leading to
+   subsequent headers applies on this message to be declined with an
+   error. Should be used only in branches and replies.
+
+   Default value is 28.
+
+   Example 1.4. Set header_apply_flag parameter
+...
+modparam("pv_headers", "header_apply_flag", 38)
+...
+
+3.5. skip_headers (string)
+
+   A comma separated headers list that must be excluded from processing
+   (they are skipped when pvh_apply_headers() changes the SIP message
+   headers).
+
+   If the parameter is set to an empty string then all the SIP message
+   headers are processed.
+
+   Default value is
+   "Record-Route,Via,Route,Content-Length,Max-Forwards,CSeq".
+
+   Example 1.5. Set skip_headers parameter
+...
+modparam("pv_headers", "skip_headers", "Record-Route,Via,Route")
+...
+
+3.6. split_headers (string)
+
+   A comma separated headers list that must be split into multi headers if
+   their value is a comma separated list.
+
+   If the parameter is set to an empty string then no headers are split.
+
+   Default value is "".
+
+   Example 1.6. Set split_headers parameter
+...
+modparam("pv_headers", "split_headers", "Diversion")
+...
+
+   Result: Received Diversion header: Diversion:
+   <user1@test.local>,<user2@test.local>,<user3@test.local> After split:
+   Diversion: <user1@test.local> Diversion: <user2@test.local> Diversion:
+   <user3@test.local>
+
+   Becomes handy if used together with pvh_modify_header() or
+   pvh_remove_header() to change or remove value 2 for instance.
+
+3.7. auto_msg (int)
+
+   Defines wether the headers are automatically collected for incoming
+   messages, as well as automatically applied for forwarded messages.
+
+   It is enabled by default and requires the 'tm' module to be loaded,
+   otherwise the mode is disabled and manual invocation of
+   pvh_collect_headers()/pvh_apply_headers() is required.
+
+   Default value is 1 (enabled).
+
+   Example 1.7. Set auto_msg parameter
+...
+modparam("pv_headers", "auto_msg", 1)
+...
+
+4. Functions
+
+   4.1. pvh_collect_headers()
+   4.2. pvh_apply_headers()
+   4.3. pvh_reset_headers()
+   4.4. pvh_check_header(hname)
+   4.5. pvh_append_header(hname, hvalue)
+   4.6. pvh_modify_header(hname, hvalue, [idx])
+   4.7. pvh_remove_header(hname, [idx])
+
+   \
+
+4.1.  pvh_collect_headers()
+
+   Collects all headers from the message into the XAVP. It should be used
+   preferably just when the SIP message is received by Kamailio.
+
+   This function can be used from ANY_ROUTE.
+
+4.2.  pvh_apply_headers()
+
+   Applies the current XAVP headers state to the real headers. and should
+   be called only once per branch when the message is about to leave
+   Kamailio.
+
+   The following rules apply:
+     * all headers in the XAVP except for ones provided in the
+       skip_headers parameter and From/To are recreated in the SIP
+       message.
+     * From/To headers are processed by the uac module if it is loaded.
+     * From/To headers are not changed in the reply messages.
+     * headers with NULL value are removed if exist in the SIP message.
+     * the initial order of the SIP headers is preserved.
+
+   This function can be used from ANY_ROUTE.
+
+4.3.  pvh_reset_headers()
+
+   Collects all headers from the message into the XAVP. It should be used
+   preferably just when the SIP message is received by >Kamailio.
+
+   This function can be used from ANY_ROUTE.
+
+4.4.  pvh_check_header(hname)
+
+   Checks if the header "hname" already exists in the XAVP.
+
+   This function can be used from ANY_ROUTE but only after
+   pvh_collect_headers() or with "auto_msg" parameter enabled.
+
+4.5.  pvh_append_header(hname, hvalue)
+
+   Appends a new header "hname" with the value "hvalue" into the XAVP.
+   Please note that subsequent "pv_append_header" calls will result in
+   multiple headers.
+
+   If the provided "hvalue" is $null then the header is added into the
+   XAVP but it is not going to be added into the message.
+
+   This function can be used from ANY_ROUTE but only after
+   pvh_collect_headers() or with "auto_msg" parameter enabled.
+
+4.6.  pvh_modify_header(hname, hvalue, [idx])
+
+   Modifies an existing header in the XAVP "hname" with the value "hvalue"
+   into the XAVP. Index order is top to bottom. Please note that
+   subsequent pvh_append_header calls will result in multiple headers.
+
+   Please note that if the header "hname"does not exist it will be
+   explicitly appended. If there are multiple headers with the same name
+   and "idx" is omitted, only the first one will be affected.
+
+   This function can be used from ANY_ROUTE but only after
+   pvh_collect_headers() or with "auto_msg" parameter enabled.
+
+4.7.  pvh_remove_header(hname, [idx])
+
+   Removes an existing header "hname" from the XAVP. Index order is top to
+   bottom.
+
+   If there are multiple headers with the same name and "idx" is omitted,
+   all of them will be removed.
+
+   This function can be used from ANY_ROUTE but only after
+   pvh_collect_headers() or with "auto_msg" parameter enabled.
+
+5. Exported Variables
+
+     * $x_hdr
+     * $x_fu
+     * $x_fU
+     * $x_fd
+     * $x_fn
+     * $x_ft
+     * $x_tu
+     * $x_tU
+     * $x_td
+     * $x_tn
+     * $x_tt
+     * $x_rs
+     * $x_rr
+
+   Exported pseudo-variables are documented at
+   https://www.kamailio.org/wiki/.
diff --git a/src/modules/pv_headers/doc/Makefile b/src/modules/pv_headers/doc/Makefile
new file mode 100644
index 0000000..b143109
--- /dev/null
+++ b/src/modules/pv_headers/doc/Makefile
@@ -0,0 +1,4 @@
+docs = pv_headers.xml
+
+docbook_dir = ../../../../doc/docbook
+include $(docbook_dir)/Makefile.module
diff --git a/src/modules/pv_headers/doc/functions.xml b/src/modules/pv_headers/doc/functions.xml
new file mode 100644
index 0000000..ef7f133
--- /dev/null
+++ b/src/modules/pv_headers/doc/functions.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+
+
+<section id="pv_headers.funtions" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+    </sectioninfo>\
+
+    <title>Functions</title>
+	<section id="pv_headers.f.pvh_collect_headers">
+		<title>
+		<function moreinfo="none">pvh_collect_headers()</function>
+		</title>
+		<para>
+		Collects all headers from the message into the XAVP.
+		It should be used preferably just when the &sip; message is received by &kamailio;.
+		</para>
+		<para>
+		This function can be used from ANY_ROUTE.
+		</para>
+	</section>
+	<section id="pv_headers.f.pvh_apply_headers">
+		<title>
+		<function moreinfo="none">pvh_apply_headers()</function>
+		</title>
+		<para>
+		Applies the current XAVP headers state to the real headers.
+		and should be called only once per branch when the message is about to leave &kamailio;.
+		</para>
+		<para>
+		The following rules apply:
+		<itemizedlist>
+			<listitem>
+			all headers in the XAVP except for ones provided in the
+			<link linked="pv_headers.p.skip_headers">skip_headers</link> parameter and
+			From/To are recreated in the &sip; message.
+			</listitem>
+			<listitem>From/To headers are processed by the uac module if it is loaded.</listitem>
+			<listitem>From/To headers are not changed in the reply messages.</listitem>
+			<listitem>headers with NULL value are removed if exist in the &sip; message.</listitem>
+			<listitem>the initial order of the &sip; headers is preserved.</listitem>
+		</itemizedlist>
+		</para>
+		<para>
+		This function can be used from ANY_ROUTE.
+		</para>
+	</section>
+	<section id="pv_headers.f.pvh_reset_headers">
+		<title>
+		<function moreinfo="none">pvh_reset_headers()</function>
+		</title>
+		<para>
+		Collects all headers from the message into the XAVP.
+		It should be used preferably just when the &sip; message is received by >&kamailio;.
+		</para>
+		<para>
+		This function can be used from ANY_ROUTE.
+		</para>
+	</section>
+	<section id="pv_headers.f.pvh_check_header">
+		<title>
+		<function moreinfo="none">pvh_check_header(hname)</function>
+		</title>
+		<para>
+		Checks if the header <quote>hname</quote> already exists in the XAVP.
+		</para>
+		<para>
+		This function can be used from ANY_ROUTE but only after <link linked="pv_headers.f.pvh_collect_headers">
+		pvh_collect_headers()</link> or with <quote>auto_msg</quote> parameter enabled.
+		</para>
+	</section>
+	<section id="pv_headers.f.pvh_append_header">
+		<title>
+		<function moreinfo="none">pvh_append_header(hname, hvalue)</function>
+		</title>
+		<para>
+		Appends a new header <quote>hname</quote> with the value <quote>hvalue</quote> into the XAVP.
+		Please note that subsequent "pv_append_header" calls will result in multiple headers.
+		</para>
+		<para>
+		If the provided <quote>hvalue</quote> is $null then the header is added into the XAVP but it is
+		not going to be added into the message.
+		</para>
+		<para>
+		This function can be used from ANY_ROUTE but only after <link linked="pv_headers.f.pvh_collect_headers">
+		pvh_collect_headers()</link> or with <quote>auto_msg</quote> parameter enabled.
+		</para>
+	</section>
+	<section id="pv_headers.f.pvh_modify_header">
+		<title>
+		<function moreinfo="none">pvh_modify_header(hname, hvalue, [idx])</function>
+		</title>
+		<para>
+		Modifies an existing header in the XAVP <quote>hname</quote> with the value <quote>hvalue</quote> into the XAVP.
+		Index order is top to bottom.
+		Please note that subsequent <link linked="pv_headers.f.pvh_append_header">pvh_append_header</link> calls
+		will result in multiple headers.
+		</para>
+		<para>
+		Please note that if the header <quote>hname</quote>does not exist it will be explicitly appended.
+		If there are multiple headers with the same name and <quote>idx</quote> is omitted, only the first one
+		will be affected.
+		</para>
+		<para>
+		This function can be used from ANY_ROUTE but only after <link linked="pv_headers.f.pvh_collect_headers">
+		pvh_collect_headers()</link> or with <quote>auto_msg</quote> parameter enabled.
+		</para>
+	</section>
+	<section id="pv_headers.f.pvh_remove_header">
+		<title>
+		<function moreinfo="none">pvh_remove_header(hname, [idx])</function>
+		</title>
+		<para>
+		Removes an existing header <quote>hname</quote> from the XAVP. Index order is top to bottom.
+		</para>
+		<para>
+		If there are multiple headers with the same name and <quote>idx</quote> is omitted, all of them will be removed.
+		</para>
+		<para>
+		This function can be used from ANY_ROUTE but only after <link linked="pv_headers.f.pvh_collect_headers">
+		pvh_collect_headers()</link> or with <quote>auto_msg</quote> parameter enabled.
+		</para>
+	</section>
+	</section>
diff --git a/src/modules/pv_headers/doc/params.xml b/src/modules/pv_headers/doc/params.xml
new file mode 100644
index 0000000..2bc6ddc
--- /dev/null
+++ b/src/modules/pv_headers/doc/params.xml
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+
+<section id="pv_headers.parameters" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+    </sectioninfo>
+
+	<title>Parameters</title>
+	<section id="pv_headers.p.xavi_name">
+		<title><varname>xavi_name</varname> (string)</title>
+		<para>
+			Name of the XAVI where the collected headers are stored.
+		</para>
+		<para>
+		<emphasis>
+			Default value is <quote>headers</quote>.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>xavi_name</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("pv_headers", "xavi_name", "headers")
+...
+</programlisting>
+		<para>
+		Result:
+			$xavi(headers[0]=>From)
+			$xavi(headers[0]=>To)
+			$xavi(headers[0]=>Call-ID)
+		</para>
+		</example>
+	</section>
+	<section id="pv_headers.p.header_value_size">
+		<title><varname>header_value_size</varname> (int)</title>
+		<para>
+			Defines an internal maximum &sip; header value size. Header values
+			longer than this setting will be stripped down when collected or applied.
+		</para>
+		<para>
+		<emphasis>
+			Default value is 1024.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>header_value_size</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("pv_headers", "header_value_size", 512)
+...
+</programlisting>
+		</example>
+	</section>
+	<section id="pv_headers.p.header_collect_flag">
+		<title><varname>header_collect_flag</varname> (int)</title>
+		<para>
+			Used to mark that headers are collected for the &sip; message, leading to
+			subsequent headers collection on this message to be declined with an error.
+			Should be used only in branches and replies.
+		</para>
+		<para>
+		<emphasis>
+			Default value is 27.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>header_collect_flag</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("pv_headers", "header_collect_flag", 37)
+...
+</programlisting>
+		</example>
+	</section>
+	<section id="pv_headers.p.header_apply_flag">
+		<title><varname>header_apply_flag</varname> (int)</title>
+		<para>
+			Used to mark that headers are applied for the &sip; message, leading to subsequent
+			headers applies on this message to be declined with an error.
+			Should be used only in branches and replies.
+		</para>
+		<para>
+		<emphasis>
+			Default value is 28.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>header_apply_flag</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("pv_headers", "header_apply_flag", 38)
+...
+</programlisting>
+		</example>
+	</section>
+	<section id="pv_headers.p.skip_headers">
+		<title><varname>skip_headers</varname> (string)</title>
+		<para>
+		A comma separated headers list that must be excluded from processing (they are skipped when
+		<link linked="pv_headers.f.pvh_apply_headers">pvh_apply_headers()</link>
+		changes the &sip; message headers).
+		</para>
+		<para>
+		If the parameter is set to an empty string then all the &sip; message headers are processed.
+		</para>
+		<para>
+		<emphasis>
+			Default value is <quote>Record-Route,Via,Route,Content-Length,Max-Forwards,CSeq</quote>.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>skip_headers</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("pv_headers", "skip_headers", "Record-Route,Via,Route")
+...
+</programlisting>
+		</example>
+	</section>
+	<section id="pv_headers.p.split_headers">
+		<title><varname>split_headers</varname> (string)</title>
+		<para>
+		A comma separated headers list that must be split into multi headers if
+		their value is a comma separated list.
+		</para>
+		<para>
+		If the parameter is set to an empty string then no headers are split.
+		</para>
+		<para>
+		<emphasis>
+			Default value is <quote></quote>.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>split_headers</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("pv_headers", "split_headers", "Diversion")
+...
+</programlisting>
+		<para>
+		Result:
+			Received Diversion header:
+				Diversion: &lt;user1@test.local&gt;,&lt;user2@test.local&gt;,&lt;user3@test.local&gt;
+			After split:
+				Diversion: &lt;user1@test.local&gt;
+				Diversion: &lt;user2@test.local&gt;
+				Diversion: &lt;user3@test.local&gt;
+		</para>
+		<para>
+		Becomes handy if used together with <link linked="pv_headers.f.pvh_modify_header">pvh_modify_header()</link>
+		or <link linked="pv_headers.f.pvh_remove_header">pvh_remove_header()</link> to change or remove value 2 for instance.
+		</para>
+		</example>
+	</section>
+	<section id="pv_headers.p.auto_msg">
+		<title><varname>auto_msg</varname> (int)</title>
+		<para>
+		Defines wether the headers are automatically collected for incoming
+		messages, as well as automatically applied for forwarded messages.
+		</para>
+		<para>It is enabled by default and requires the 'tm' module to be loaded, otherwise the mode is disabled
+		and manual invocation of <link linked="pv_headers.f.pvh_collect_headers">pvh_collect_headers()</link>/<link linked="pv_headers.f.pvh_apply_headers">pvh_apply_headers()</link> is required.
+		</para>
+		<para>
+		<emphasis>
+			Default value is 1 (enabled).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>auto_msg</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("pv_headers", "auto_msg", 1)
+...
+</programlisting>
+		</example>
+	</section>
+	</section>
\ No newline at end of file
diff --git a/src/modules/pv_headers/doc/pv_headers.xml b/src/modules/pv_headers/doc/pv_headers.xml
new file mode 100644
index 0000000..a5211b9
--- /dev/null
+++ b/src/modules/pv_headers/doc/pv_headers.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+	"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"
+	[ <!ENTITY % local.common.attrib
+	 "xmlns:xi CDATA #FIXED 'http://www.w3.org/2001/XInclude'">
+	 <!-- Include general documentation entities -->
+	 <!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+	 %docentities;
+	]
+>
+
+<book id="pv_headers" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <bookinfo>
+	<title>pv_headers Module</title>
+	<productname class="trade">&kamailioname;</productname>
+	<authorgroup>
+	    <author>
+		<firstname>Kirill</firstname>
+		<surname>Solomko</surname>
+			<email>ksolomko@sipwise.com</email>
+		<affiliation><orgname>Sipwise GmbH</orgname></affiliation>
+	    </author>
+	    <editor>
+		<firstname>Victor</firstname>
+		<surname>Seva</surname>
+		    <email>vseva@sipwise.com</email>
+		<affiliation><orgname>Sipwise GmbH</orgname></affiliation>
+	    </editor>
+	</authorgroup>
+	<copyright>
+	    <year>2018</year>
+	    <holder>Sipwise GmbH</holder>
+	</copyright>
+    </bookinfo>
+    <toc></toc>
+
+	<xi:include href="pv_headers_admin.xml"/>
+
+</book>
diff --git a/src/modules/pv_headers/doc/pv_headers_admin.xml b/src/modules/pv_headers/doc/pv_headers_admin.xml
new file mode 100644
index 0000000..3fd9b0b
--- /dev/null
+++ b/src/modules/pv_headers/doc/pv_headers_admin.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+<!ENTITY % local.common.attrib
+	 "xmlns:xi CDATA #FIXED 'http://www.w3.org/2001/XInclude'">
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+
+<!-- Module User's Guide -->
+
+<chapter>
+
+	<title>&adminguide;</title>
+
+	<section>
+	<title>Overview</title>
+	<para>
+		The main goal of the module is to offload the intermediate header processing
+	into the XAVI dynamic container as well as provide with high level methods
+	and pseudovariables to simplify &sip; message header modifications.
+	</para>
+	</section>
+	<section>
+	<title>Dependencies</title>
+	<section>
+		<title>&kamailio; Modules</title>
+		<para>
+		The following modules must be loaded before this module:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>uac</emphasis>.
+			</para>
+			</listitem>
+			<listitem>
+			<para>
+				<emphasis>tm</emphasis>.
+			</para>
+			<para>
+			Needed only if <quote><link linked="pv_headers.p.auto_msg">auto_msg</link></quote> parameter is set to 1.
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	<section>
+		<title>External Libraries or Applications</title>
+		<para>
+		The following libraries or applications must be installed before running
+		&kamailio; with this module loaded:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>None</emphasis>.
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	</section>
+
+	<xi:include href="params.xml"/>
+	<xi:include href="functions.xml"/>
+
+	<section>
+		<title>Exported Variables</title>
+		<itemizedlist>
+			<listitem><para>
+				<emphasis>$x_hdr</emphasis>
+			</para></listitem>
+			<listitem><para>
+				<emphasis>$x_fu</emphasis>
+			</para></listitem>
+			<listitem><para>
+				<emphasis>$x_fU</emphasis>
+			</para></listitem>
+			<listitem><para>
+				<emphasis>$x_fd</emphasis>
+			</para></listitem>
+			<listitem><para>
+				<emphasis>$x_fn</emphasis>
+			</para></listitem>
+			<listitem><para>
+				<emphasis>$x_ft</emphasis>
+			</para></listitem>
+			<listitem><para>
+				<emphasis>$x_tu</emphasis>
+			</para></listitem>
+			<listitem><para>
+				<emphasis>$x_tU</emphasis>
+			</para></listitem>
+			<listitem><para>
+				<emphasis>$x_td</emphasis>
+			</para></listitem>
+			<listitem><para>
+				<emphasis>$x_tn</emphasis>
+			</para></listitem>
+			<listitem><para>
+				<emphasis>$x_tt</emphasis>
+			</para></listitem>
+			<listitem><para>
+				<emphasis>$x_rs</emphasis>
+			</para></listitem>
+			<listitem><para>
+				<emphasis>$x_rr</emphasis>
+			</para></listitem>
+		</itemizedlist>
+		<para>
+		Exported pseudo-variables are documented at &kamwikilink;.
+		</para>
+	</section>
+
+</chapter>
diff --git a/src/modules/pv_headers/pv_headers.c b/src/modules/pv_headers/pv_headers.c
new file mode 100644
index 0000000..6dfac3d
--- /dev/null
+++ b/src/modules/pv_headers/pv_headers.c
@@ -0,0 +1,548 @@
+/*
+ * pv_headers
+ *
+ * Copyright (C)
+ * 2020 Victor Seva <vseva@sipwise.com>
+ * 2018 Kirill Solomko <ksolomko@sipwise.com>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "../../core/sr_module.h"
+#include "../../core/script_cb.h"
+#include "../../core/mod_fix.h"
+#include "../../modules/tm/tm_load.h"
+#include "../../core/kemi.h"
+
+#include "pv_headers.h"
+#include "pvh_func.h"
+#include "pvh_hash.h"
+#include "pvh_xavp.h"
+
+MODULE_VERSION
+
+#define MODULE_NAME "pv_headers"
+#define XAVP_NAME "headers"
+
+uac_api_t uac;
+static tm_api_t tmb;
+
+str xavi_name = str_init(XAVP_NAME);
+str xavi_helper_xname = str_init("modparam_pv_headers");
+str xavi_parsed_xname = str_init("parsed_pv_headers");
+unsigned int header_name_size = 255;
+unsigned int header_value_size = 1024;
+int FL_PV_HDRS_COLLECTED = 27;
+int FL_PV_HDRS_APPLIED = 28;
+static str skip_headers_param =
+		str_init("Record-Route,Via,Route,Content-Length,Max-Forwards,CSeq");
+static str split_headers_param = STR_NULL;
+static str single_headers_param = str_init("");
+static int auto_msg_param = 1;
+
+str _hdr_from = {"From", 4};
+str _hdr_to = {"To", 2};
+str _hdr_reply_reason = {"@Reply-Reason", 13};
+int _branch = T_BR_UNDEFINED;
+int _reply_counter = -1;
+
+static void mod_destroy(void);
+static int mod_init(void);
+
+static void handle_tm_t(tm_cell_t *t, int type, struct tmcb_params *params);
+static int handle_msg_cb(struct sip_msg *msg, unsigned int flags, void *cb);
+static int handle_msg_branch_cb(
+		struct sip_msg *msg, unsigned int flags, void *cb);
+static int handle_msg_reply_cb(
+		struct sip_msg *msg, unsigned int flags, void *cb);
+
+/**
+ *
+ */
+static int pvh_get_branch_index(struct sip_msg *msg, int *br_idx)
+{
+	int os = 0;
+	int len = 0;
+	char parsed_br_idx[header_value_size];
+
+	if(msg->add_to_branch_len > header_value_size) {
+		LM_ERR("branch name is too long\n");
+		return -1;
+	}
+
+	os = msg->add_to_branch_len;
+	while(os > 0 && memcmp(msg->add_to_branch_s + os - 1, ".", 1))
+		os--;
+	len = msg->add_to_branch_len - os;
+	if(os > 0 && len > 0) {
+		memcpy(parsed_br_idx, msg->add_to_branch_s + os, len);
+		parsed_br_idx[len] = '\0';
+		*br_idx = atoi(parsed_br_idx) + 1;
+	} else {
+		*br_idx = 0;
+	}
+
+	return 1;
+}
+
+static int w_pvh_collect_headers(struct sip_msg *msg, char *p1, char *p2)
+{
+	sr_xavp_t **backup_xavis = NULL;
+
+	if(pvh_get_branch_index(msg, &_branch) < 0)
+		return -1;
+	if(msg->first_line.type == SIP_REPLY) {
+		if((_reply_counter = pvh_reply_append(backup_xavis)) < 0) {
+			return -1;
+		}
+	}
+	return pvh_collect_headers(msg);
+}
+
+static int ki_pvh_collect_headers(struct sip_msg *msg)
+{
+	sr_xavp_t **backup_xavis = NULL;
+
+	if(pvh_get_branch_index(msg, &_branch) < 0)
+		return -1;
+	if(msg->first_line.type == SIP_REPLY) {
+		if((_reply_counter = pvh_reply_append(backup_xavis)) < 0) {
+			return -1;
+		}
+	}
+	return pvh_collect_headers(msg);
+}
+
+static int w_pvh_apply_headers(struct sip_msg *msg, char *p1, char *p2)
+{
+	if(pvh_get_branch_index(msg, &_branch) < 0)
+		return -1;
+	return pvh_apply_headers(msg);
+}
+
+static int ki_pvh_apply_headers(struct sip_msg *msg)
+{
+	if(pvh_get_branch_index(msg, &_branch) < 0)
+		return -1;
+	return pvh_apply_headers(msg);
+}
+
+static int w_pvh_reset_headers(struct sip_msg *msg, char *p1, char *p2)
+{
+	if(pvh_get_branch_index(msg, &_branch) < 0)
+		return -1;
+	return pvh_reset_headers(msg);
+}
+
+static int w_pvh_check_header(struct sip_msg *msg, char *p1, char *p2)
+{
+	str hname = STR_NULL;
+
+	if(fixup_get_svalue(msg, (gparam_p)p1, &hname) < 0)
+		return -1;
+
+	return pvh_check_header(msg, &hname);
+}
+
+static int w_pvh_append_header(struct sip_msg *msg, char *p1, char *p2)
+{
+	str hname = STR_NULL, hvalue = STR_NULL;
+
+	if(fixup_get_svalue(msg, (gparam_p)p1, &hname) < 0)
+		return -1;
+
+	if(p2 && fixup_get_svalue(msg, (gparam_p)p2, &hvalue) < 0)
+		return -1;
+
+	return pvh_append_header(msg, &hname, &hvalue);
+}
+
+static int w_pvh_modify_header(
+		struct sip_msg *msg, char *p1, char *p2, char *p3)
+{
+	int indx = 0;
+	str hname = STR_NULL, hvalue = STR_NULL;
+
+	if(fixup_get_svalue(msg, (gparam_p)p1, &hname) < 0)
+		return -1;
+
+	if(p2 && fixup_get_svalue(msg, (gparam_p)p2, &hvalue) < 0)
+		return -1;
+
+	if(p3 && fixup_get_ivalue(msg, (gparam_p)p3, &indx) < 0)
+		return -1;
+
+	return pvh_modify_header(msg, &hname, &hvalue, indx);
+}
+
+static int w_pvh_remove_header(
+		struct sip_msg *msg, char *p1, char *p2, char *p3)
+{
+	int indx = -1;
+	str hname = STR_NULL;
+
+	if(fixup_get_svalue(msg, (gparam_p)p1, &hname) < 0)
+		return -1;
+
+	if(p2 && fixup_get_ivalue(msg, (gparam_p)p2, &indx) < 0)
+		return -1;
+
+	return pvh_remove_header(msg, &hname, indx);
+}
+
+/* clang-format off */
+static cmd_export_t cmds[] = {
+	{"pvh_collect_headers", (cmd_function)w_pvh_collect_headers, 0, 0, 0,
+			ANY_ROUTE},
+	{"pvh_apply_headers", (cmd_function)w_pvh_apply_headers, 0, 0, 0,
+			ANY_ROUTE},
+	{"pvh_reset_headers", (cmd_function)w_pvh_reset_headers, 0, 0, 0,
+			ANY_ROUTE},
+	{"pvh_check_header", (cmd_function)w_pvh_check_header, 1,
+			fixup_spve_null, fixup_free_spve_null, ANY_ROUTE},
+	{"pvh_append_header", (cmd_function)w_pvh_append_header, 2,
+			fixup_spve_spve, fixup_free_spve_spve, ANY_ROUTE},
+	{"pvh_modify_header", (cmd_function)w_pvh_modify_header, 2,
+			fixup_spve_spve, fixup_free_spve_spve, ANY_ROUTE},
+	{"pvh_modify_header", (cmd_function)w_pvh_modify_header, 3,
+			fixup_spve_all, fixup_free_spve_all, ANY_ROUTE},
+	{"pvh_remove_header", (cmd_function)w_pvh_remove_header, 1,
+			fixup_spve_null, fixup_free_spve_null, ANY_ROUTE},
+	{"pvh_remove_header", (cmd_function)w_pvh_remove_header, 2,
+			fixup_spve_spve, fixup_free_spve_spve, ANY_ROUTE},
+	{0, 0, 0, 0, 0, 0}
+};
+
+static pv_export_t mod_pvs[] = {
+	{{"x_hdr", (sizeof("x_hdr") - 1)}, PVT_OTHER, pvh_get_header,
+			pvh_set_header, pvh_parse_header_name, pv_parse_index, 0, 0},
+	{{"x_fu", (sizeof("x_fu") - 1)}, PVT_OTHER, pvh_get_uri, pvh_set_uri, 0,
+			0, pv_init_iname, 1},
+	{{"x_fU", (sizeof("x_fU") - 1)}, PVT_OTHER, pvh_get_uri, pvh_set_uri, 0,
+			0, pv_init_iname, 2},
+	{{"x_fd", (sizeof("x_fd") - 1)}, PVT_OTHER, pvh_get_uri, pvh_set_uri, 0,
+			0, pv_init_iname, 3},
+	{{"x_fn", (sizeof("x_fn") - 1)}, PVT_OTHER, pvh_get_uri, pvh_set_uri, 0,
+			0, pv_init_iname, 4},
+	{{"x_ft", (sizeof("x_ft") - 1)}, PVT_OTHER, pvh_get_uri, /* ro */ 0, 0,
+			0, pv_init_iname, 5},
+	{{"x_tu", (sizeof("x_tu") - 1)}, PVT_OTHER, pvh_get_uri, pvh_set_uri, 0,
+			0, pv_init_iname, 6},
+	{{"x_tU", (sizeof("x_tU") - 1)}, PVT_OTHER, pvh_get_uri, pvh_set_uri, 0,
+			0, pv_init_iname, 7},
+	{{"x_td", (sizeof("x_td") - 1)}, PVT_OTHER, pvh_get_uri, pvh_set_uri, 0,
+			0, pv_init_iname, 8},
+	{{"x_tn", (sizeof("x_tn") - 1)}, PVT_OTHER, pvh_get_uri, pvh_set_uri, 0,
+			0, pv_init_iname, 9},
+	{{"x_tt", (sizeof("x_tt") - 1)}, PVT_OTHER, pvh_get_uri, /* ro */ 0, 0,
+			0, pv_init_iname, 10},
+	{{"x_rs", (sizeof("x_rs") - 1)}, PVT_OTHER, pvh_get_reply_sr,
+			pvh_set_reply_sr, 0, 0, pv_init_iname, 1},
+	{{"x_rr", (sizeof("x_rr") - 1)}, PVT_OTHER, pvh_get_reply_sr,
+			pvh_set_reply_sr, 0, 0, pv_init_iname, 2},
+	{{0, 0}, 0, 0, 0, 0, 0, 0, 0}
+};
+
+static param_export_t params[] = {
+	{"xavi_name", PARAM_STR, &xavi_name},
+	{"header_value_size", PARAM_INT, &header_value_size},
+	{"header_collect_flag", PARAM_INT, &FL_PV_HDRS_COLLECTED},
+	{"header_apply_flag", PARAM_INT, &FL_PV_HDRS_APPLIED},
+	{"skip_headers", PARAM_STR, &skip_headers_param},
+	{"split_headers", PARAM_STR, &split_headers_param},
+	{"auto_msg", PARAM_INT, &auto_msg_param},
+	{0, 0, 0}
+};
+
+struct module_exports exports = {
+	MODULE_NAME,	 /* module name */
+	DEFAULT_DLFLAGS, /* dlopen flags */
+	cmds,			 /* exported functions */
+	params,			 /* exported parameters */
+	0,				 /* RPC method exports */
+	mod_pvs,		 /* exported pseudo-variables */
+	0,				 /* response handling function */
+	mod_init,		 /* module initialization function */
+	0,				 /* per-child init function */
+	mod_destroy		 /* module destroy function */
+};
+/* clang-format on */
+
+int mod_init(void)
+{
+	LM_INFO("%s module init...\n", MODULE_NAME);
+
+	if(load_uac_api(&uac) < 0) {
+		LM_NOTICE("could not bind to the 'uac' module, From/To headers will "
+				  "not be modifed\n");
+	}
+
+	if(load_tm_api(&tmb) < 0) {
+		LM_NOTICE("could not bind to the 'tm' module, automatic headers "
+				  "collect/apply is disabled\n");
+		auto_msg_param = 0;
+	}
+	if(auto_msg_param) {
+		if(register_script_cb(handle_msg_cb, PRE_SCRIPT_CB | REQUEST_CB, 0)
+				< 0) {
+			LM_ERR("cannot register PRE_SCRIPT_CB REQUEST_CB callbacks\n");
+			return -1;
+		}
+		if(register_script_cb(
+				   handle_msg_branch_cb, PRE_SCRIPT_CB | BRANCH_CB, 0)
+				< 0) {
+			LM_ERR("cannot register PRE_SCRIPT_CB BRANCH_CB callbacks\n");
+			return -1;
+		}
+		if(register_script_cb(
+				   handle_msg_reply_cb, PRE_SCRIPT_CB | ONREPLY_CB, 0)
+				< 0) {
+			LM_ERR("cannot register PRE_SCRIPT_CB ONREPLY_CB callbacks\n");
+			return -1;
+		}
+	}
+
+	pvh_str_hash_init(&skip_headers, &skip_headers_param, "skip_headers");
+	pvh_str_hash_init(&split_headers, &split_headers_param, "split_headers");
+	pvh_str_hash_init(&single_headers, &single_headers_param, "single_headers");
+
+	return 0;
+}
+
+void mod_destroy(void)
+{
+	LM_INFO("%s module unload...\n", MODULE_NAME);
+}
+
+/* just for debug */
+static inline char *tm_type_to_string(int type)
+{
+	switch(type) {
+		case TMCB_REQUEST_IN:
+			return "TMCB_REQUEST_IN";
+		case TMCB_RESPONSE_IN:
+			return "TMCB_RESPONSE_IN";
+		case TMCB_E2EACK_IN:
+			return "TMCB_E2EACK_IN";
+		case TMCB_REQUEST_PENDING:
+			return "TMCB_REQUEST_PENDING";
+		case TMCB_REQUEST_FWDED:
+			return "TMCB_REQUEST_FWDED";
+		case TMCB_RESPONSE_FWDED:
+			return "TMCB_RESPONSE_FWDED";
+		case TMCB_ON_FAILURE_RO:
+			return "TMCB_ON_FAILURE_RO";
+		case TMCB_ON_FAILURE:
+			return "TMCB_ON_FAILURE";
+		case TMCB_REQUEST_OUT:
+			return "TMCB_REQUEST_OUT";
+		case TMCB_RESPONSE_OUT:
+			return "TMCB_RESPONSE_OUT";
+		case TMCB_LOCAL_COMPLETED:
+			return "TMCB_LOCAL_COMPLETED";
+		case TMCB_LOCAL_RESPONSE_OUT:
+			return "TMCB_LOCAL_RESPONSE_OUT";
+		case TMCB_ACK_NEG_IN:
+			return "TMCB_ACK_NEG_IN";
+		case TMCB_REQ_RETR_IN:
+			return "TMCB_REQ_RETR_IN";
+		case TMCB_LOCAL_RESPONSE_IN:
+			return "TMCB_LOCAL_RESPONSE_IN";
+		case TMCB_LOCAL_REQUEST_IN:
+			return "TMCB_LOCAL_REQUEST_IN";
+		case TMCB_DLG:
+			return "TMCB_DLG";
+		case TMCB_DESTROY:
+			return "TMCB_DESTROY";
+		case TMCB_E2ECANCEL_IN:
+			return "TMCB_E2ECANCEL_IN";
+		case TMCB_E2EACK_RETR_IN:
+			return "TMCB_E2EACK_RETR_IN";
+		case TMCB_RESPONSE_READY:
+			return "TMCB_RESPONSE_READY";
+		case TMCB_DONT_ACK:
+			return "TMCB_DONT_ACK";
+		case TMCB_REQUEST_SENT:
+			return "TMCB_REQUEST_SENT";
+		case TMCB_RESPONSE_SENT:
+			return "TMCB_RESPONSE_SENT";
+		case TMCB_ON_BRANCH_FAILURE:
+			return "TMCB_ON_BRANCH_FAILURE";
+		case TMCB_ON_BRANCH_FAILURE_RO:
+			return "TMCB_ON_BRANCH_FAILURE_RO";
+		case TMCB_MAX:
+			return "TMCB_MAX";
+	}
+
+	return "UNKNOWN";
+}
+
+void handle_tm_t(tm_cell_t *t, int type, struct tmcb_params *params)
+{
+	struct sip_msg *msg = NULL;
+
+	LM_DBG("T:%p params->branch:%d type:%s\n", t, params->branch,
+			tm_type_to_string(type));
+
+
+	if(type & TMCB_REQUEST_FWDED) {
+		msg = params->req;
+	} else if(type & (TMCB_ON_BRANCH_FAILURE | TMCB_RESPONSE_FWDED)) {
+		msg = params->rpl;
+	} else {
+		LM_ERR("unknown callback: %d\n", type);
+		return;
+	}
+
+
+	LM_DBG("T:%p picked_branch:%d label:%d branches:%d\n", t,
+			tmb.t_get_picked_branch(), t->label, t->nr_of_outgoings);
+
+	if(msg != NULL && msg != FAKED_REPLY) {
+		pvh_get_branch_index(msg, &_branch);
+		LM_DBG("T:%p set branch:%d\n", t, _branch);
+		pvh_apply_headers(msg);
+	}
+	return;
+}
+
+static int msg_cbs =
+		TMCB_REQUEST_FWDED | TMCB_RESPONSE_FWDED | TMCB_ON_BRANCH_FAILURE;
+
+int handle_msg_cb(struct sip_msg *msg, unsigned int flags, void *cb)
+{
+	if(pvh_parse_msg(msg) != 0)
+		return 1;
+
+	if(tmb.register_tmcb(msg, 0, msg_cbs, handle_tm_t, 0, 0) <= 0) {
+		LM_ERR("cannot register TM callbacks\n");
+		return -1;
+	}
+
+	_branch = 0;
+	LM_DBG("msg:%p set branch:%d\n", msg, _branch);
+	pvh_collect_headers(msg);
+	return 1;
+}
+
+int handle_msg_branch_cb(struct sip_msg *msg, unsigned int flags, void *cb)
+{
+
+	LM_DBG("msg:%p previous branch:%d\n", msg, _branch);
+
+	if(flags & PRE_SCRIPT_CB) {
+		pvh_get_branch_index(msg, &_branch);
+		LM_DBG("msg:%p set branch:%d\n", msg, _branch);
+		pvh_clone_branch_xavi(msg, &xavi_name);
+	}
+
+	return 1;
+}
+
+int handle_msg_reply_cb(struct sip_msg *msg, unsigned int flags, void *cb)
+{
+	tm_cell_t *t = NULL;
+	sr_xavp_t **backup_xavis = NULL;
+	sr_xavp_t **list = NULL;
+
+	if(pvh_parse_msg(msg) != 0)
+		return 1;
+	LM_DBG("msg:%p previous branch:%d\n", msg, _branch);
+
+	if(tmb.t_check(msg, &_branch) == -1) {
+		LM_ERR("failed find UAC branch\n");
+	} else {
+		t = tmb.t_gett();
+		if(t == NULL || t == T_UNDEFINED) {
+			LM_DBG("cannot lookup the transaction\n");
+		} else {
+			LM_DBG("T:%p t_check-branch:%d xavi_list:%p branches:%d\n", t,
+					_branch, &t->xavis_list, t->nr_of_outgoings);
+			list = &t->xavis_list;
+			backup_xavis = xavi_set_list(&t->xavis_list);
+		}
+	}
+
+	pvh_get_branch_index(msg, &_branch);
+	LM_DBG("T:%p set branch:%d picked_branch:%d\n", t, _branch,
+			tmb.t_get_picked_branch());
+
+	if((_reply_counter = pvh_reply_append(list)) < 0) {
+		return -1;
+	}
+	pvh_collect_headers(msg);
+	if(backup_xavis) {
+		xavi_set_list(backup_xavis);
+		LM_DBG("restored backup_xavis:%p\n", *backup_xavis);
+	}
+	if(t) {
+		tmb.unref_cell(t);
+		LM_DBG("T:%p unref\n", t);
+	}
+	tmb.t_sett(T_UNDEFINED, T_BR_UNDEFINED);
+	LM_DBG("reset tm\n");
+
+	return 1;
+}
+
+/* clang-format off */
+static sr_kemi_t pvh_kemi_exports[] = {
+	{ str_init("pv_headers"), str_init("pvh_collect_headers"),
+		SR_KEMIP_INT, ki_pvh_collect_headers,
+			{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
+				SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE}
+	},
+	{ str_init("pv_headers"), str_init("pvh_apply_headers"),
+		SR_KEMIP_INT, ki_pvh_apply_headers,
+			{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
+				SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE}
+	},
+	{ str_init("pv_headers"), str_init("pvh_reset_headers"),
+		SR_KEMIP_INT, pvh_reset_headers,
+			{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
+				SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE}
+	},
+	{ str_init("pv_headers"), str_init("pvh_check_header"),
+		SR_KEMIP_INT, pvh_check_header,
+			{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
+				SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE}
+	},
+	{ str_init("pv_headers"), str_init("pvh_append_header"),
+		SR_KEMIP_INT, pvh_append_header,
+			{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
+				SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE}
+	},
+	{ str_init("pv_headers"), str_init("pvh_modify_header"),
+		SR_KEMIP_INT, pvh_modify_header,
+			{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_INT,
+			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE}
+	},
+	{ str_init("pv_headers"), str_init("pvh_remove_header"),
+		SR_KEMIP_INT, pvh_remove_header,
+			{ SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_NONE,
+				SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE}
+	},
+	{{0, 0}, {0, 0}, 0, NULL, {0, 0, 0, 0, 0, 0}}
+};
+/* clang-format on */
+
+int mod_register(char *path, int *dlflags, void *p1, void *p2)
+{
+	sr_kemi_modules_add(pvh_kemi_exports);
+	return 0;
+}
diff --git a/src/modules/pv_headers/pv_headers.h b/src/modules/pv_headers/pv_headers.h
new file mode 100644
index 0000000..5ee4efc
--- /dev/null
+++ b/src/modules/pv_headers/pv_headers.h
@@ -0,0 +1,57 @@
+/*
+ * pv_headers
+ *
+ * Copyright (C)
+ * 2020 Victor Seva <vseva@sipwise.com>
+ * 2018 Kirill Solomko <ksolomko@sipwise.com>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef PV_HEADERS_H
+#define PV_HEADERS_H
+
+#include "../../core/parser/parse_addr_spec.h"
+#include "../../modules/uac/api.h"
+
+typedef struct _xavp_c_data
+{
+	struct to_body to_b;
+	struct to_param *to_params;
+	str value;
+} xavp_c_data_t;
+
+extern uac_api_t uac;
+
+extern str xavi_name;
+extern str xavi_parsed_xname;
+extern str xavi_helper_xname;
+
+extern unsigned int header_name_size;
+extern unsigned int header_value_size;
+
+extern str _hdr_from;
+extern str _hdr_to;
+extern str _hdr_reply_reason;
+extern int _branch;
+extern int _reply_counter;
+
+extern int FL_PV_HDRS_COLLECTED;
+extern int FL_PV_HDRS_APPLIED;
+
+#endif /* PV_HEADERS_H */
diff --git a/src/modules/pv_headers/pvh_func.c b/src/modules/pv_headers/pvh_func.c
new file mode 100644
index 0000000..749275e
--- /dev/null
+++ b/src/modules/pv_headers/pvh_func.c
@@ -0,0 +1,360 @@
+/*
+ * pv_headers
+ *
+ * Copyright (C)
+ * 2020 Victor Seva <vseva@sipwise.com>
+ * 2018 Kirill Solomko <ksolomko@sipwise.com>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "../../core/strutils.h"
+
+#include "pv_headers.h"
+#include "pvh_func.h"
+#include "pvh_xavp.h"
+#include "pvh_str.h"
+#include "pvh_hash.h"
+#include "pvh_hdr.h"
+
+static str xavi_helper_name = str_init("xavi_name");
+
+int pvh_parse_msg(sip_msg_t *msg)
+{
+	if(msg->first_line.type == SIP_REQUEST) {
+		if(!IS_SIP(msg)) {
+			LM_DBG("non SIP request message\n");
+			return 1;
+		}
+	} else if(msg->first_line.type == SIP_REPLY) {
+		if(!IS_SIP_REPLY(msg)) {
+			LM_DBG("non SIP reply message\n");
+			return 1;
+		}
+	} else {
+		LM_DBG("non SIP message\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+int pvh_collect_headers(struct sip_msg *msg)
+{
+	struct hdr_field *hf = NULL;
+	str name = STR_NULL;
+	str val = STR_NULL;
+	char hvals[header_name_size][header_value_size];
+	int idx = 0, d_size = 0;
+	str val_part = STR_NULL;
+
+	if(pvh_hdrs_collected(msg)) {
+		LM_ERR("headers are already collected\n");
+		return -1;
+	}
+
+	if(parse_headers(msg, HDR_EOH_F, 0) < 0) {
+		LM_ERR("error parsing headers\n");
+		return -1;
+	}
+
+	for(hf = msg->headers; hf; hf = hf->next) {
+		LM_DBG("collect header[%.*s]: %.*s\n", hf->name.len, hf->name.s,
+				hf->body.len, hf->body.s);
+
+		switch(hf->type) {
+			case HDR_FROM_T:
+				name.len = _hdr_from.len;
+				name.s = _hdr_from.s;
+				LM_DBG("force [From] as key\n");
+				break;
+			case HDR_TO_T:
+				name.len = _hdr_to.len;
+				name.s = _hdr_to.s;
+				LM_DBG("force [To] as key\n");
+				break;
+			default:
+				name.len = hf->name.len;
+				name.s = hf->name.s;
+		}
+		val.len = hf->body.len;
+		val.s = hf->body.s;
+
+		if(strchr(val.s, ',') != NULL
+				&& str_hash_case_get(&split_headers, name.s, name.len)) {
+
+			if(pvh_split_values(&val, hvals, &d_size, 1) < 0) {
+				LM_ERR("could not parse %.*s header comma separated "
+					   "value",
+						name.len, name.s);
+				return -1;
+			}
+
+			for(idx = 0; idx < d_size; idx++) {
+				val_part.s = hvals[idx];
+				val_part.len = strlen(hvals[idx]);
+				if(pvh_set_xavi(msg, &xavi_name, &name, &val_part, SR_XTYPE_STR,
+						   0, 1)
+						< 0)
+					return -1;
+			}
+			continue;
+		}
+		if(pvh_set_xavi(msg, &xavi_name, &name, &val, SR_XTYPE_STR, 0, 1) < 0)
+			return -1;
+	}
+
+	if(pvh_set_xavi(msg, &xavi_helper_xname, &xavi_helper_name, &xavi_name,
+			   SR_XTYPE_STR, 0, 0)
+			< 0)
+		return -1;
+
+	pvh_hdrs_set_collected(msg);
+
+	return 1;
+}
+
+int pvh_apply_headers(struct sip_msg *msg)
+{
+	sr_xavp_t *xavi = NULL;
+	sr_xavp_t *sub = NULL;
+	struct str_hash_table rm_hdrs;
+	int from_cnt = 0, to_cnt = 0;
+	char t[header_name_size];
+	char tv[2][header_value_size];
+	str display = {tv[0], header_value_size};
+	str uri = {tv[1], header_value_size};
+	str br_xname = {t, header_name_size};
+	int skip_from_to = 0, keys_count = 0;
+	int res = -1;
+
+	memset(&rm_hdrs, 0, sizeof(struct str_hash_table));
+
+	if(pvh_hdrs_applied(msg)) {
+		LM_ERR("headers are already applied\n");
+		return -1;
+	}
+
+	if(parse_headers(msg, HDR_EOH_F, 0) < 0) {
+		LM_ERR("error parsing headers\n");
+		return -1;
+	}
+
+	pvh_get_branch_xname(msg, &xavi_name, &br_xname);
+
+	if((xavi = xavi_get(&br_xname, NULL)) == NULL
+			&& (xavi = xavi_get(&xavi_name, NULL)) == NULL) {
+		LM_ERR("missing xavi %.*s, run pv_collect_headers() first\n",
+				xavi_name.len, xavi_name.s);
+		return -1;
+	}
+	if(xavi->val.type != SR_XTYPE_XAVP) {
+		LM_ERR("not xavp child type %.*s\n", xavi_name.len, xavi_name.s);
+		return -1;
+	}
+
+	if((sub = xavi->val.v.xavp) == NULL) {
+		LM_ERR("invalid xavp structure: %.*s\n", xavi_name.len, xavi_name.s);
+		return -1;
+	}
+	keys_count = pvh_xavi_keys_count(&sub);
+	if(str_hash_alloc(&rm_hdrs, keys_count) < 0) {
+		PKG_MEM_ERROR;
+		return -1;
+	}
+	LM_DBG("xavi->name:%.*s br_xname:%.*s keys_count: %d\n", xavi->name.len,
+			xavi->name.s, br_xname.len, br_xname.s, keys_count);
+	str_hash_init(&rm_hdrs);
+
+	if(msg->first_line.type == SIP_REPLY
+			|| msg->first_line.u.request.method_value == METHOD_ACK
+			|| msg->first_line.u.request.method_value == METHOD_PRACK
+			|| msg->first_line.u.request.method_value == METHOD_BYE) {
+		skip_from_to = 1;
+		if(msg->to == NULL) {
+			LM_DBG("no To header, can't store To info in parsed\n");
+		} else {
+			if(pvh_set_parsed(msg, &_hdr_to, &msg->to->body, NULL) == NULL)
+				LM_ERR("can't store To info in parsed\n");
+		}
+	}
+
+	do {
+		if(pvh_skip_header(&sub->name))
+			continue;
+
+		if(cmpi_str(&sub->name, &_hdr_from) == 0) {
+			if(skip_from_to) {
+				LM_DBG("skip From header change in reply messages\n");
+				continue;
+			}
+			if(cmp_str(&sub->val.v.s, &msg->from->body) == 0) {
+				LM_DBG("skip unchanged From header\n");
+				continue;
+			}
+			if(from_cnt > 0)
+				continue;
+
+			memset(display.s, 0, header_value_size);
+			memset(uri.s, 0, header_value_size);
+
+			if(pvh_extract_display_uri(sub->val.v.s.s, &display, &uri) < 0) {
+				LM_ERR("error parsing From header\n");
+				goto err;
+			}
+
+			if(uac.replace_from != NULL) {
+				LM_DBG("replace_from[%s]: %s %s\n", sub->name.s, display.s,
+						uri.s);
+				if(display.len == 0)
+					pvh_real_hdr_remove_display(msg, &sub->name);
+				uac.replace_from(msg, &display, &uri);
+			}
+
+			from_cnt++;
+			continue;
+		}
+
+		if(cmpi_str(&sub->name, &_hdr_to) == 0) {
+			if(skip_from_to) {
+				LM_DBG("skip To header change in reply messages\n");
+				continue;
+			}
+			if(cmp_str(&sub->val.v.s, &msg->to->body) == 0) {
+				LM_DBG("skip unchanged To header\n");
+				continue;
+			}
+			if(to_cnt > 0)
+				continue;
+
+			memset(display.s, 0, header_value_size);
+			memset(uri.s, 0, header_value_size);
+
+			if(pvh_extract_display_uri(sub->val.v.s.s, &display, &uri) < 0) {
+				LM_ERR("error parsing To header\n");
+				goto err;
+			}
+
+			if(uac.replace_to != NULL) {
+				LM_DBG("replace_to[%s]: %s %s\n", sub->name.s, display.s,
+						uri.s);
+				if(display.len == 0)
+					pvh_real_hdr_remove_display(msg, &sub->name);
+				uac.replace_to(msg, &display, &uri);
+			}
+
+			to_cnt++;
+			continue;
+		}
+
+		if(cmpi_str(&sub->name, &_hdr_reply_reason) == 0) {
+			if(str_hash_case_get(&rm_hdrs, sub->name.s, sub->name.len))
+				continue;
+			pvh_real_replace_reply_reason(msg, &sub->val.v.s);
+			pvh_str_hash_add_key(&rm_hdrs, &sub->name);
+			continue;
+		}
+
+		if(!str_hash_case_get(&rm_hdrs, sub->name.s, sub->name.len)) {
+			if(!pvh_avp_is_null(sub) && xavi_count(&sub->name, &sub) == 1) {
+				LM_DBG("replace header[%s]: %s\n", sub->name.s, sub->val.v.s.s);
+				pvh_real_hdr_replace(msg, &sub->name, &sub->val.v.s);
+				pvh_str_hash_add_key(&rm_hdrs, &sub->name);
+				continue;
+			}
+			LM_DBG("remove header[%s]: %s\n", sub->name.s, sub->val.v.s.s);
+			pvh_real_hdr_del_by_name(msg, &sub->name);
+			pvh_str_hash_add_key(&rm_hdrs, &sub->name);
+		}
+
+		if(!pvh_avp_is_null(sub) && !pvh_single_header(&sub->name)) {
+			pvh_real_hdr_append(msg, &sub->name, &sub->val.v.s);
+			LM_DBG("append header[%s]: %s\n", sub->name.s, sub->val.v.s.s);
+		}
+	} while((sub = sub->next) != NULL);
+
+	pvh_hdrs_set_applied(msg);
+
+	res = 1;
+
+err:
+	if(rm_hdrs.size)
+		pvh_str_hash_free(&rm_hdrs);
+	return res;
+}
+
+int pvh_reset_headers(struct sip_msg *msg)
+{
+	char t[header_name_size];
+	str br_xname = {t, header_name_size};
+
+	pvh_get_branch_xname(msg, &xavi_name, &br_xname);
+	LM_DBG("clean xavi:%.*s\n", br_xname.len, br_xname.s);
+	xavi_rm_by_name(&br_xname, 1, NULL);
+	pvh_get_branch_xname(msg, &xavi_parsed_xname, &br_xname);
+	LM_DBG("clean xavi:%.*s\n", br_xname.len, br_xname.s);
+	xavi_rm_by_name(&br_xname, 1, NULL);
+
+	pvh_hdrs_reset_flags(msg);
+
+	return 1;
+}
+
+int pvh_check_header(struct sip_msg *msg, str *hname)
+{
+
+	if(pvh_xavi_get_child(msg, &xavi_name, hname) == NULL)
+		return -1;
+
+	return 1;
+}
+
+int pvh_append_header(struct sip_msg *msg, str *hname, str *hvalue)
+{
+	return pvh_set_xavi(msg, &xavi_name, hname, hvalue, SR_XTYPE_STR, 0, 1);
+}
+
+int pvh_modify_header(struct sip_msg *msg, str *hname, str *hvalue, int indx)
+{
+	return pvh_set_xavi(msg, &xavi_name, hname, hvalue, SR_XTYPE_STR, indx, 0);
+}
+
+int pvh_remove_header(struct sip_msg *msg, str *hname, int indx)
+{
+	sr_xavp_t *avp = NULL;
+	int count = 0;
+
+	if((avp = pvh_xavi_get_child(msg, &xavi_name, hname)) == NULL)
+		return 1;
+
+	if(indx < 0) {
+		count = xavi_count(hname, &avp);
+		do {
+			if(pvh_set_xavi(
+					   msg, &xavi_name, hname, NULL, SR_XTYPE_STR, indx++, 0)
+					< 1)
+				return -1;
+		} while(indx < count);
+	} else {
+		if(pvh_set_xavi(msg, &xavi_name, hname, NULL, SR_XTYPE_STR, indx, 0)
+				< 1)
+			return -1;
+	}
+
+	return 1;
+}
diff --git a/src/modules/pv_headers/pvh_func.h b/src/modules/pv_headers/pvh_func.h
new file mode 100644
index 0000000..6aaf5aa
--- /dev/null
+++ b/src/modules/pv_headers/pvh_func.h
@@ -0,0 +1,42 @@
+/*
+ * pv_headers
+ *
+ * Copyright (C)
+ * 2020 Victor Seva <vseva@sipwise.com>
+ * 2018 Kirill Solomko <ksolomko@sipwise.com>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef PV_FUNC_H
+#define PV_FUNC_H
+
+#include "../../core/parser/msg_parser.h"
+
+int pvh_parse_msg(sip_msg_t *msg);
+
+int pvh_collect_headers(struct sip_msg *msg);
+int pvh_apply_headers(struct sip_msg *msg);
+int pvh_reset_headers(struct sip_msg *msg);
+
+int pvh_check_header(struct sip_msg *msg, str *hname);
+int pvh_append_header(struct sip_msg *msg, str *hname, str *hvalue);
+int pvh_modify_header(struct sip_msg *msg, str *hname, str *hvalue, int indx);
+int pvh_remove_header(struct sip_msg *msg, str *hname, int indx);
+
+#endif /* PV_FUNC_H */
diff --git a/src/modules/pv_headers/pvh_hash.c b/src/modules/pv_headers/pvh_hash.c
new file mode 100644
index 0000000..89f93f7
--- /dev/null
+++ b/src/modules/pv_headers/pvh_hash.c
@@ -0,0 +1,134 @@
+/*
+ * pv_headers
+ *
+ * Copyright (C)
+ * 2020 Victor Seva <vseva@sipwise.com>
+ * 2018 Kirill Solomko <ksolomko@sipwise.com>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "pv_headers.h"
+#include "pvh_hash.h"
+#include "pvh_str.h"
+
+struct str_hash_table skip_headers;
+struct str_hash_table split_headers;
+struct str_hash_table single_headers;
+
+int pvh_str_hash_init(struct str_hash_table *ht, str *keys, char *desc)
+{
+	char split[header_name_size][header_value_size];
+	int idx = 0, d_size = 0;
+	str val = STR_NULL;
+
+	if(pvh_split_values(keys, split, &d_size, 0) < 0) {
+		LM_ERR("could not parse %s param\n", desc);
+		return -1;
+	}
+
+	if(str_hash_alloc(ht, d_size + 1) < 0) {
+		PKG_MEM_ERROR;
+		return -1;
+	}
+	str_hash_init(ht);
+
+	for(idx = 0; idx < d_size; idx++) {
+		val.s = split[idx];
+		val.len = strlen(split[idx]);
+		if(pvh_str_hash_add_key(ht, &val) < 0) {
+			LM_ERR("cannot add a hash key=>%s", desc);
+			return -1;
+		}
+	}
+
+	return 1;
+}
+
+int pvh_str_hash_add_key(struct str_hash_table *ht, str *key)
+{
+	struct str_hash_entry *e = NULL;
+	int e_size;
+
+	if(ht->table == NULL || key == NULL || key->len == 0)
+		return -1;
+
+	e_size = sizeof(struct str_hash_entry) + sizeof(char) * key->len;
+	e = pkg_malloc(e_size);
+	if(e == NULL) {
+		PKG_MEM_ERROR;
+		return -1;
+	}
+	memset(e, 0, e_size);
+
+	if(pvh_str_new(&e->key, key->len + 1) < 0)
+		goto err;
+	pvh_str_copy(&e->key, key, key->len + 1);
+
+	str_hash_case_add(ht, e);
+	return 1;
+
+err:
+	pvh_str_free(&e->key);
+	return -1;
+}
+
+int pvh_str_hash_free(struct str_hash_table *ht)
+{
+	struct str_hash_entry *e = NULL;
+	struct str_hash_entry *bak = NULL;
+	int r;
+
+	if(ht == NULL)
+		return -1;
+
+	if(ht->table) {
+		for(r = 0; r < ht->size; r++) {
+			clist_foreach_safe(&ht->table[r], e, bak, next)
+			{
+				pvh_str_free(&e->key);
+				pkg_free(e);
+			}
+		}
+		pkg_free(ht->table);
+	}
+
+	return 1;
+}
+
+int pvh_skip_header(str *hname)
+{
+	if(hname == NULL)
+		return 0;
+
+	if(str_hash_case_get(&skip_headers, hname->s, hname->len))
+		return 1;
+
+	return 0;
+}
+
+int pvh_single_header(str *hname)
+{
+	if(hname == NULL)
+		return 0;
+
+	if(str_hash_case_get(&single_headers, hname->s, hname->len))
+		return 1;
+
+	return 0;
+}
diff --git a/src/modules/pv_headers/pvh_hash.h b/src/modules/pv_headers/pvh_hash.h
new file mode 100644
index 0000000..d7e25f8
--- /dev/null
+++ b/src/modules/pv_headers/pvh_hash.h
@@ -0,0 +1,41 @@
+/*
+ * pv_headers
+ *
+ * Copyright (C)
+ * 2020 Victor Seva <vseva@sipwise.com>
+ * 2018 Kirill Solomko <ksolomko@sipwise.com>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef PVH_HASH_H
+#define PVH_HASH_H
+
+#include "../../core/str_hash.h"
+
+extern struct str_hash_table skip_headers;
+extern struct str_hash_table split_headers;
+extern struct str_hash_table single_headers;
+
+int pvh_str_hash_init(struct str_hash_table *ht, str *keys, char *desc);
+int pvh_str_hash_add_key(struct str_hash_table *ht, str *key);
+int pvh_str_hash_free(struct str_hash_table *ht);
+int pvh_skip_header(str *hname);
+int pvh_single_header(str *hname);
+
+#endif /* PVH_HASH_H */
diff --git a/src/modules/pv_headers/pvh_hdr.c b/src/modules/pv_headers/pvh_hdr.c
new file mode 100644
index 0000000..d83a646
--- /dev/null
+++ b/src/modules/pv_headers/pvh_hdr.c
@@ -0,0 +1,295 @@
+/*
+ * pv_headers
+ *
+ * Copyright (C)
+ * 2020 Victor Seva <vseva@sipwise.com>
+ * 2018 Kirill Solomko <ksolomko@sipwise.com>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "../../core/data_lump.h"
+#include "../../core/dset.h"
+
+#include "pvh_hdr.h"
+
+int pvh_hdrs_collected(struct sip_msg *msg)
+{
+	if(msg->first_line.type == SIP_REPLY) {
+		if(isflagset(msg, FL_PV_HDRS_COLLECTED) == 1) {
+			return 1;
+		}
+	} else {
+		if(isbflagset(_branch, FL_PV_HDRS_COLLECTED) == 1) {
+			return 1;
+		}
+	}
+	return 0;
+}
+
+int pvh_hdrs_applied(struct sip_msg *msg)
+{
+	if(msg->first_line.type == SIP_REPLY) {
+		if(isflagset(msg, FL_PV_HDRS_APPLIED) == 1) {
+			return 1;
+		}
+	} else {
+		if(isbflagset(_branch, FL_PV_HDRS_APPLIED) == 1) {
+			return 1;
+		}
+	}
+	return 0;
+}
+
+void pvh_hdrs_set_applied(struct sip_msg *msg)
+{
+	if(msg->first_line.type == SIP_REPLY) {
+		setflag(msg, FL_PV_HDRS_APPLIED);
+	} else {
+		setbflag(_branch, FL_PV_HDRS_APPLIED);
+	}
+}
+
+void pvh_hdrs_set_collected(struct sip_msg *msg)
+{
+	if(msg->first_line.type == SIP_REPLY) {
+		setflag(msg, FL_PV_HDRS_COLLECTED);
+	} else {
+		setbflag(_branch, FL_PV_HDRS_COLLECTED);
+	}
+}
+
+void pvh_hdrs_reset_flags(struct sip_msg *msg)
+{
+	if(msg->first_line.type == SIP_REPLY) {
+		resetflag(msg, FL_PV_HDRS_COLLECTED);
+		resetflag(msg, FL_PV_HDRS_APPLIED);
+	} else {
+		resetbflag(_branch, FL_PV_HDRS_COLLECTED);
+		resetbflag(_branch, FL_PV_HDRS_APPLIED);
+	}
+}
+
+int pvh_real_hdr_append(struct sip_msg *msg, str *hname, str *hvalue)
+{
+	struct lump *anchor = NULL;
+	hdr_field_t *hf = NULL;
+	hdr_field_t *m_hf = NULL;
+	str new_h = STR_NULL;
+
+	if(hname->s == NULL || hvalue->s == NULL) {
+		LM_ERR("header name/value cannot be empty");
+		return -1;
+	}
+
+	// find last header matching the name
+	for(hf = msg->headers; hf; hf = hf->next) {
+		if(hf->name.len == hname->len
+				&& strncasecmp(hf->name.s, hname->s, hname->len) == 0) {
+			m_hf = hf;
+		}
+		if(!hf->next)
+			break;
+	}
+
+	if(m_hf == NULL)
+		anchor = anchor_lump(msg, msg->unparsed - msg->buf, 0, 0);
+	else
+		anchor = anchor_lump(msg, m_hf->name.s + m_hf->len - msg->buf, 0, 0);
+
+	if(anchor == 0) {
+		LM_ERR("unable to find header lump\n");
+		return -1;
+	}
+
+	if(pvh_create_hdr_str(hname, hvalue, &new_h) <= 0)
+		return -1;
+
+	if(insert_new_lump_after(anchor, new_h.s, new_h.len, 0) == 0) {
+		LM_ERR("cannot insert header lump\n");
+		pkg_free(new_h.s);
+		return -1;
+	}
+
+	LM_DBG("append header: %.*s\n", new_h.len, new_h.s);
+
+	return 1;
+}
+
+int pvh_real_hdr_replace(struct sip_msg *msg, str *hname, str *hvalue)
+{
+	struct lump *anchor = NULL;
+	hdr_field_t *hf = NULL;
+	str new_h = STR_NULL;
+	int new = 1;
+
+	if(hname->s == NULL || hvalue->s == NULL) {
+		LM_ERR("header name/value cannot be empty");
+		return -1;
+	}
+
+	for(hf = msg->headers; hf; hf = hf->next) {
+		if(hf->name.len == hname->len
+				&& strncasecmp(hf->name.s, hname->s, hname->len) == 0) {
+			if(hf->body.len == hvalue->len
+					&& strncasecmp(hf->body.s, hvalue->s, hvalue->len) == 0) {
+				return 1;
+			}
+			new = 0;
+			break;
+		}
+		if(!hf->next)
+			break;
+	}
+
+	if(hf == NULL) {
+		LM_ERR("unable to find header lump\n");
+		return -1;
+	}
+
+	if(new == 0) {
+		if((anchor = del_lump(msg, hf->name.s - msg->buf, hf->len, 0)) == 0) {
+			LM_ERR("unable to delete header lump\n");
+			return -1;
+		}
+	} else {
+		anchor = anchor_lump(msg, hf->name.s + hf->len - msg->buf, 0, 0);
+	}
+
+	if(anchor == 0) {
+		LM_ERR("unable to find header lump\n");
+		return -1;
+	}
+
+	if(pvh_create_hdr_str(hname, hvalue, &new_h) <= 0)
+		return -1;
+
+	if(insert_new_lump_after(anchor, new_h.s, new_h.len, 0) == 0) {
+		LM_ERR("cannot insert header lump\n");
+		pkg_free(new_h.s);
+		return -1;
+	}
+
+	LM_DBG("%s header: %.*s\n", new ? "append" : "replace", new_h.len, new_h.s);
+
+	return 1;
+}
+
+int pvh_real_hdr_del_by_name(struct sip_msg *msg, str *hname)
+{
+	hdr_field_t *hf = NULL;
+
+	for(hf = msg->headers; hf; hf = hf->next) {
+		if(hf->name.len == hname->len
+				&& strncasecmp(hf->name.s, hname->s, hname->len) == 0) {
+			LM_DBG("remove header[%.*s]: %.*s\n", hf->name.len, hf->name.s,
+					hf->body.len, hf->body.s);
+			del_lump(msg, hf->name.s - msg->buf, hf->len, 0);
+		}
+	}
+	return 1;
+}
+
+int pvh_real_hdr_remove_display(struct sip_msg *msg, str *hname)
+{
+	hdr_field_t *hf = NULL;
+	struct to_body *d_hf = NULL;
+	int disp_len = 0;
+
+	for(hf = msg->headers; hf; hf = hf->next) {
+		if(hf->name.len == hname->len
+				&& strncasecmp(hf->name.s, hname->s, hname->len) == 0) {
+			d_hf = (struct to_body *)hf->parsed;
+			if((disp_len = d_hf->display.len) > 0) {
+				LM_DBG("remove display[%.*s]: %.*s\n", hf->name.len, hf->name.s,
+						disp_len, d_hf->display.s);
+				if(strncmp(d_hf->display.s + disp_len, " ", 1) == 0)
+					disp_len++;
+				del_lump(msg, d_hf->display.s - msg->buf, disp_len, 0);
+			}
+		}
+	}
+	return 1;
+}
+
+int pvh_real_replace_reply_reason(struct sip_msg *msg, str *value)
+{
+	struct lump *anchor = NULL;
+	char *reason = NULL;
+
+	anchor = del_lump(msg, msg->first_line.u.reply.reason.s - msg->buf,
+			msg->first_line.u.reply.reason.len, 0);
+	if(!anchor) {
+		LM_ERR("set reply: failed to del lump\n");
+		goto err;
+	}
+
+	reason = (char *)pkg_malloc(value->len);
+	if(reason == NULL) {
+		PKG_MEM_ERROR;
+		goto err;
+	}
+	memcpy(reason, value->s, value->len);
+
+	if(insert_new_lump_after(anchor, reason, value->len, 0) == 0) {
+		LM_ERR("set reply: failed to add lump: %.*s\n", value->len, value->s);
+		goto err;
+	}
+
+	return 1;
+
+err:
+	if(reason)
+		pkg_free(reason);
+	return -1;
+}
+
+int pvh_create_hdr_str(str *hname, str *hvalue, str *dst)
+{
+	int os;
+	if(hname->s == NULL || hvalue->s == NULL) {
+		LM_ERR("header name/value cannot be empty");
+		return -1;
+	}
+
+	if(dst == NULL) {
+		LM_ERR("new header str cannot be null");
+		return -1;
+	}
+
+	dst->len = hname->len + 2 + hvalue->len + CRLF_LEN;
+	dst->s = (char *)pkg_malloc(dst->len + 1);
+	if(dst->s == NULL) {
+		PKG_MEM_ERROR;
+		return -1;
+	}
+	memset(dst->s, 0, dst->len + 1);
+
+	os = 0;
+	memcpy(dst->s, hname->s, hname->len);
+	os += hname->len;
+	memcpy(dst->s + os, ": ", 2);
+	os += 2;
+	memcpy(dst->s + os, hvalue->s, hvalue->len);
+	os += hvalue->len;
+	memcpy(dst->s + os, CRLF, CRLF_LEN);
+	os += CRLF_LEN;
+	dst->s[dst->len] = '\0';
+
+	return 1;
+}
diff --git a/src/modules/pv_headers/pvh_hdr.h b/src/modules/pv_headers/pvh_hdr.h
new file mode 100644
index 0000000..4f86953
--- /dev/null
+++ b/src/modules/pv_headers/pvh_hdr.h
@@ -0,0 +1,47 @@
+/*
+ * pv_headers
+ *
+ * Copyright (C)
+ * 2020 Victor Seva <vseva@sipwise.com>
+ * 2018 Kirill Solomko <ksolomko@sipwise.com>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef PV_HDR_H
+#define PV_HDR_H
+
+#include "../../core/parser/msg_parser.h"
+#include "../../core/xavp.h"
+
+#include "pv_headers.h"
+
+int pvh_hdrs_collected(struct sip_msg *msg);
+int pvh_hdrs_applied(struct sip_msg *msg);
+void pvh_hdrs_set_collected(struct sip_msg *msg);
+void pvh_hdrs_set_applied(struct sip_msg *msg);
+void pvh_hdrs_reset_flags(struct sip_msg *msg);
+
+int pvh_real_hdr_append(struct sip_msg *msg, str *hname, str *hvalue);
+int pvh_real_hdr_replace(struct sip_msg *msg, str *hname, str *hvalue);
+int pvh_real_hdr_del_by_name(struct sip_msg *msg, str *hname);
+int pvh_real_hdr_remove_display(struct sip_msg *msg, str *hname);
+int pvh_real_replace_reply_reason(struct sip_msg *msg, str *value);
+int pvh_create_hdr_str(str *hname, str *hvalue, str *dst);
+
+#endif /* PV_HDR_H */
\ No newline at end of file
diff --git a/src/modules/pv_headers/pvh_str.c b/src/modules/pv_headers/pvh_str.c
new file mode 100644
index 0000000..6ede032
--- /dev/null
+++ b/src/modules/pv_headers/pvh_str.c
@@ -0,0 +1,146 @@
+/*
+ * pv_headers
+ *
+ * Copyright (C)
+ * 2020 Victor Seva <vseva@sipwise.com>
+ * 2018 Kirill Solomko <ksolomko@sipwise.com>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "pvh_str.h"
+
+int pvh_str_new(str *s, int size)
+{
+	s->s = (char *)pkg_malloc(size);
+	if(s->s == NULL) {
+		PKG_MEM_ERROR;
+		return -1;
+	}
+	memset(s->s, 0, size);
+	s->len = 0;
+
+	return 1;
+}
+
+int pvh_str_free(str *s)
+{
+	if(s->s)
+		pkg_free(s->s);
+	s->s = NULL;
+	return 1;
+}
+
+int pvh_str_copy(str *dst, str *src, unsigned int max_size)
+{
+	unsigned int src_len = src->len + 1 >= max_size ? max_size - 1 : src->len;
+
+	if(src == NULL || dst == NULL || src->len <= 0)
+		return -1;
+
+	memset(dst->s, 0, dst->len);
+	memcpy(dst->s, src->s, src_len);
+	dst->s[src_len] = '\0';
+	dst->len = src_len;
+
+	return 1;
+}
+
+int pvh_extract_display_uri(char *suri, str *display, str *duri)
+{
+	char *ptr_a = NULL;
+	char *ptr_b = NULL;
+	int display_len = 0;
+	int uri_len = 0;
+
+	if(suri == NULL || strlen(suri) == 0)
+		return -1;
+
+	ptr_a = strchr(suri, '<');
+	ptr_b = strchr(suri, '>');
+
+	if(ptr_a == NULL && ptr_b == NULL) {
+		ptr_a = suri;
+		uri_len = strlen(suri);
+	} else if(ptr_a == NULL || ptr_b == NULL) {
+		return -1;
+	} else {
+		display_len = ptr_a - suri;
+		ptr_a++;
+		uri_len = ptr_b - ptr_a;
+	}
+
+	if(uri_len <= 0)
+		return -1;
+
+	if(display_len > 0) {
+		memcpy(display->s, suri, display_len);
+		display->len = strlen(display->s);
+		display->s[display->len] = '\0';
+	} else {
+		display->len = 0;
+	}
+
+	memcpy(duri->s, ptr_a, uri_len);
+	duri->len = strlen(duri->s);
+	duri->s[duri->len] = '\0';
+
+	return 1;
+}
+
+int pvh_split_values(
+		str *s, char d[][header_value_size], int *d_size, int keep_spaces)
+{
+	char p;
+	int idx = 0, c_idx = 0;
+
+	*d_size = -1;
+
+	if(s == NULL || s->len == 0 || d == NULL) {
+		*d_size = 0;
+		return 1;
+	}
+
+	while(idx < s->len) {
+		strncpy(&p, s->s + idx++, 1);
+		if(keep_spaces == 0 && strncmp(&p, " ", 1) == 0)
+			continue;
+		if(strncmp(&p, ",", 1) == 0) {
+			if(c_idx == 0)
+				continue;
+			if(c_idx + 1 < header_value_size)
+				c_idx++;
+			d[*d_size][c_idx] = '\0';
+			c_idx = 0;
+			continue;
+		}
+		if(c_idx == 0)
+			(*d_size)++;
+		strncpy(&d[*d_size][c_idx++], &p, 1);
+	}
+
+	if(c_idx > 0) {
+		if(c_idx >= header_value_size)
+			c_idx--;
+		d[*d_size][c_idx] = '\0';
+	}
+
+	(*d_size)++;
+
+	return 1;
+}
\ No newline at end of file
diff --git a/src/modules/pv_headers/pvh_str.h b/src/modules/pv_headers/pvh_str.h
new file mode 100644
index 0000000..10aa216
--- /dev/null
+++ b/src/modules/pv_headers/pvh_str.h
@@ -0,0 +1,40 @@
+/*
+ * pv_headers
+ *
+ * Copyright (C)
+ * 2020 Victor Seva <vseva@sipwise.com>
+ * 2018 Kirill Solomko <ksolomko@sipwise.com>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef PV_STR_H
+#define PV_STR_H
+
+#include "../../core/str.h"
+
+#include "pv_headers.h"
+
+int pvh_str_new(str *s, int size);
+int pvh_str_free(str *s);
+int pvh_str_copy(str *dst, str *src, unsigned int max_size);
+int pvh_extract_display_uri(char *suri, str *display, str *duri);
+int pvh_split_values(
+		str *s, char d[][header_value_size], int *d_size, int keep_spaces);
+
+#endif /* PV_STR_H */
\ No newline at end of file
diff --git a/src/modules/pv_headers/pvh_xavp.c b/src/modules/pv_headers/pvh_xavp.c
new file mode 100644
index 0000000..6aaa8ca
--- /dev/null
+++ b/src/modules/pv_headers/pvh_xavp.c
@@ -0,0 +1,1158 @@
+/*
+ * pv_headers
+ *
+ * Copyright (C)
+ * 2020 Victor Seva <vseva@sipwise.com>
+ * 2018 Kirill Solomko <ksolomko@sipwise.com>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "../../core/parser/parse_uri.h"
+#include "../../core/strutils.h"
+
+#include "pvh_xavp.h"
+#include "pvh_str.h"
+#include "pvh_hash.h"
+#include "pvh_hdr.h"
+
+static str reply_counter = str_init("reply_counter");
+
+/**
+ *
+ */
+static sr_xavp_t *pvh_xavi_get_child_with_ival(
+		str *rname, str *cname, sr_xavp_t *start)
+{
+	sr_xavp_t *ravi = NULL;
+	sr_xavp_t *vavi = NULL;
+
+	ravi = xavi_get(rname, start);
+	if(ravi == NULL || ravi->val.type != SR_XTYPE_XAVP)
+		return NULL;
+
+	vavi = xavi_get(cname, ravi->val.v.xavp);
+	if(vavi == NULL || vavi->val.type != SR_XTYPE_INT)
+		return NULL;
+
+	return vavi;
+}
+
+/**
+ * We keep a $xavi(xavi_helper_xname=>reply_counter) with the number of replies
+ * so we will use $xavi(xavi_name.r.<id>) on reply_route
+ */
+int pvh_reply_append(sr_xavp_t **start)
+{
+	sr_xavp_t *xavi = NULL;
+	sr_xval_t xval;
+
+	xavi = pvh_xavi_get_child_with_ival(
+			&xavi_helper_xname, &reply_counter, start ? *start : NULL);
+	if(xavi) {
+		xavi->val.v.i++;
+		LM_DBG("reply message: %d\n", xavi->val.v.i);
+		return xavi->val.v.i;
+	}
+
+	memset(&xval, 0, sizeof(sr_xval_t));
+	xval.type = SR_XTYPE_INT;
+	xval.v.i = 0;
+
+	xavi = xavi_get(&xavi_helper_xname, start ? *start : NULL);
+	if(xavi == NULL) {
+		if(xavi_add_xavi_value(&xavi_helper_xname, &reply_counter, &xval,
+				   start ? start : NULL)
+				== NULL) {
+			LM_ERR("can't create xavi:%.*s\n", xavi_helper_xname.len,
+					xavi_helper_xname.s);
+			return -1;
+		}
+		LM_DBG("xavi_name:%.*s created\n", xavi_helper_xname.len,
+				xavi_helper_xname.s);
+	} else {
+		if(xavi_add_value(&reply_counter, &xval, &xavi->val.v.xavp) == NULL) {
+			LM_ERR("can't add reply_counter value\n");
+			return -1;
+		}
+		LM_DBG("added value\n");
+	}
+	LM_DBG("reply message: %d\n", xval.v.i);
+	return xval.v.i;
+}
+
+static sr_xavp_t *pvh_xavi_new_value(str *name, sr_xval_t *val)
+{
+	sr_xavp_t *avp = NULL;
+	int size;
+	unsigned int id;
+
+	if(name == NULL || name->s == NULL || val == NULL)
+		return NULL;
+	id = get_hash1_case_raw(name->s, name->len);
+
+	size = sizeof(sr_xavp_t) + name->len + 1;
+	if(val->type == SR_XTYPE_STR)
+		size += val->v.s.len + 1;
+	avp = (sr_xavp_t *)shm_malloc(size);
+	if(avp == NULL) {
+		SHM_MEM_ERROR;
+		return NULL;
+	}
+	memset(avp, 0, size);
+	avp->id = id;
+	avp->name.s = (char *)avp + sizeof(sr_xavp_t);
+	memcpy(avp->name.s, name->s, name->len);
+	avp->name.s[name->len] = '\0';
+	avp->name.len = name->len;
+	memcpy(&avp->val, val, sizeof(sr_xval_t));
+	if(val->type == SR_XTYPE_STR) {
+		avp->val.v.s.s = avp->name.s + avp->name.len + 1;
+		memcpy(avp->val.v.s.s, val->v.s.s, val->v.s.len);
+		avp->val.v.s.s[val->v.s.len] = '\0';
+		avp->val.v.s.len = val->v.s.len;
+	}
+
+	return avp;
+}
+
+int pvh_xavi_append_value(str *name, sr_xval_t *val, sr_xavp_t **start)
+{
+	sr_xavp_t *last = NULL;
+	sr_xavp_t *xavi = NULL;
+
+	if((xavi = pvh_xavi_new_value(name, val)) == NULL)
+		return -1;
+
+	if(*start == NULL) {
+		xavi->next = *start;
+		*start = xavi;
+		return 1;
+	}
+
+	last = *start;
+	while(last->next)
+		last = last->next;
+	last->next = xavi;
+
+	return 1;
+}
+
+/**
+ *
+ */
+static int pvh_xavi_set_value(str *name, sr_xval_t *val, int idx, sr_xavp_t **start)
+{
+	int cnt = 0;
+
+	if(idx < 0) {
+		cnt = xavi_count(name, start);
+		idx = idx + cnt;
+		if(idx < 0) {
+			LM_ERR("wrong calculated idx:%d\n", idx);
+			return -1;
+		}
+	}
+	LM_DBG("xavi name: %.*s\n", name->len, name->s);
+	if(xavi_set_value(name, idx, val, start) == NULL)
+		return -1;
+
+	return 1;
+}
+
+/**
+ *
+ */
+static sr_xavp_t *pvh_get_xavi(struct sip_msg *msg, str *xname)
+{
+	sr_xavp_t *xavi = NULL;
+	char t[header_name_size];
+	str br_xname = {t, header_name_size};
+
+	pvh_get_branch_xname(msg, xname, &br_xname);
+	if((xavi = xavi_get(&br_xname, NULL)) == NULL) {
+		if(cmp_str(xname, &br_xname) == 0)
+			goto end;
+		if((xavi = xavi_get(xname, NULL)) == NULL)
+			goto end;
+	}
+
+	if(xavi->val.type != SR_XTYPE_XAVP) {
+		LM_ERR("not xavp child type %s\n", br_xname.s);
+		xavi = NULL;
+		goto end;
+	}
+
+end:
+	return xavi;
+}
+
+/**
+ *
+ */
+static void pvh_free_to_params(struct to_param *param, sr_xavp_sfree_f sfree)
+{
+	struct to_param *n = NULL;
+
+	while(param) {
+		n = param->next;
+		sfree(param);
+		param = n;
+	}
+	param = NULL;
+}
+
+/**
+ *
+ */
+int pvh_parse_header_name(pv_spec_p sp, str *hname)
+{
+	pv_spec_p psp = NULL;
+
+	if(hname->s == NULL || hname->len == 0) {
+		LM_ERR("empty header name\n");
+		return -1;
+	}
+
+	if(hname->len >= header_name_size) {
+		LM_ERR("header name is too long\n");
+		return -1;
+	}
+
+	if(*hname->s == PV_MARKER) {
+		psp = (pv_spec_p)pkg_malloc(sizeof(pv_spec_t));
+		if(psp == NULL) {
+			PKG_MEM_ERROR;
+			return -1;
+		}
+		if(pv_parse_spec(hname, psp) == NULL) {
+			LM_ERR("invalid name [%.*s]\n", hname->len, hname->s);
+			pv_spec_free(psp);
+			return -1;
+		}
+		sp->pvp.pvn.type = PV_NAME_PVAR;
+		sp->pvp.pvn.u.dname = (void *)psp;
+		return 0;
+	}
+
+	sp->pvp.pvn.type = PV_NAME_INTSTR;
+	sp->pvp.pvn.u.isname.type = AVP_NAME_STR;
+	sp->pvp.pvn.u.isname.name.s = *hname;
+
+	return 0;
+}
+
+/**
+ *
+ */
+static sr_xval_t *pvh_xavi_get_value(
+		struct sip_msg *msg, str *xname, str *name, int idx)
+{
+	sr_xavp_t *xavi = NULL;
+	sr_xavp_t *sub = NULL;
+
+	if((xavi = pvh_get_xavi(msg, xname)) != NULL) {
+		/*	LM_DBG("xavi:%.*s name:%.*s idx:%d\n", xavi->name.len, xavi->name.s,
+				name->len, name->s, idx); */
+		sub = xavi_get_by_index(name, idx, &xavi->val.v.xavp);
+	}
+
+	return sub ? &sub->val : NULL;
+}
+
+/**
+ *
+ */
+sr_xavp_t *pvh_xavi_get_child(struct sip_msg *msg, str *xname, str *name)
+{
+	sr_xavp_t *xavi = NULL;
+	char t[header_name_size];
+	str br_xname = {t, header_name_size};
+
+	pvh_get_branch_xname(msg, xname, &br_xname);
+	xavi = xavi_get_child(&br_xname, name);
+	if(xavi == NULL) {
+		if(cmp_str(xname, &br_xname) != 0) {
+			xavi = xavi_get_child(xname, name);
+			if(xavi) {
+				LM_DBG("br_xname:%.*s is not here, using xname:%.*s\n",
+						br_xname.len, br_xname.s, xname->len, xname->s);
+			}
+		}
+	}
+	return xavi;
+}
+
+/**
+ *
+ */
+int pvh_avp_is_null(sr_xavp_t *avp)
+{
+	if(avp == NULL)
+		return 1;
+
+	if(avp->val.type == SR_XTYPE_NULL
+			|| (avp->val.type == SR_XTYPE_STR
+					   && (strncasecmp(avp->val.v.s.s, "NULL", 4) == 0))) {
+		return 1;
+	}
+
+	return 0;
+}
+
+/**
+ *
+ */
+static void pvh_xavi_free_data(void *p, sr_xavp_sfree_f sfree)
+{
+	xavp_c_data_t *c_data = NULL;
+
+	if((c_data = (xavp_c_data_t *)p) != NULL) {
+		pvh_free_to_params(c_data->to_params, sfree);
+		sfree(c_data->value.s);
+		c_data->value.s = NULL;
+		sfree(c_data);
+		c_data = NULL;
+	}
+}
+
+/**
+ *
+ */
+int pvh_xavi_keys_count(sr_xavp_t **start)
+{
+	sr_xavp_t *xavi = NULL;
+	int cnt = 0;
+
+	if(*start == NULL)
+		return 0;
+
+	xavi = *start;
+
+	while(xavi) {
+		cnt++;
+		xavi = xavi->next;
+	}
+
+	return cnt;
+}
+
+/**
+ *
+ */
+int pvh_set_xavi(struct sip_msg *msg, str *xname, str *name, void *data,
+		sr_xtype_t type, int idx, int append)
+{
+	sr_xavp_t **xavi = NULL;
+	sr_xavp_t *root = NULL;
+	sr_xval_t root_xval;
+	sr_xval_t xval;
+	char t[header_name_size];
+	str br_xname = {t, header_name_size};
+
+	if(xname == NULL || name == NULL) {
+		LM_ERR("missing xavi/pv name\n");
+		return -1;
+	}
+
+	pvh_get_branch_xname(msg, xname, &br_xname);
+	LM_DBG("br_xname: %.*s name: %.*s append:%d\n",
+		br_xname.len, br_xname.s, name->len, name->s, append);
+	memset(&xval, 0, sizeof(sr_xval_t));
+	if(data == NULL || SR_XTYPE_NULL) {
+		xval.type = SR_XTYPE_NULL;
+	} else if(type == SR_XTYPE_STR) {
+		xval.type = SR_XTYPE_STR;
+		xval.v.s = *(str *)data;
+	} else if(type == SR_XTYPE_DATA) {
+		xval.type = SR_XTYPE_DATA;
+		xval.v.data = (sr_data_t *)shm_malloc(sizeof(sr_data_t));
+		if(xval.v.data == NULL) {
+			SHM_MEM_ERROR;
+			return -1;
+		}
+		memset(xval.v.data, 0, sizeof(sr_data_t));
+		xval.v.data->p = data;
+		xval.v.data->pfree = pvh_xavi_free_data;
+	}
+
+	root = xavi_get(&br_xname, NULL);
+
+	if(root == NULL && _branch > 0) {
+		pvh_clone_branch_xavi(msg, xname);
+		root = xavi_get(&br_xname, NULL);
+	}
+
+	xavi = root ? &root->val.v.xavp : &root;
+
+	if(root == NULL) {
+		append = 1;
+		memset(&root_xval, 0, sizeof(sr_xval_t));
+		root_xval.type = SR_XTYPE_XAVP;
+		root_xval.v.xavp = NULL;
+
+		if((root = xavi_add_value(&br_xname, &root_xval, NULL)) == NULL) {
+			LM_ERR("error create xavi %.*s\n", br_xname.len, br_xname.s);
+			return -1;
+		}
+		xavi = &root->val.v.xavp;
+	} else if(xavi_get_child(&br_xname, name) == NULL) {
+		append = 1;
+	}
+
+	if(append) {
+		if(pvh_xavi_append_value(name, &xval, xavi) < 0) {
+			LM_ERR("error append xavi=>name %.*s=>%.*s\n",
+				br_xname.len, br_xname.s, name->len, name->s);
+			return -1;
+		}
+	} else {
+		if(pvh_xavi_set_value(name, &xval, idx, xavi) < 0) {
+			LM_ERR("error modify xavi=>name %.*s=>%.*s idx=%d\n",
+				br_xname.len, br_xname.s, name->len, name->s, idx);
+			return -1;
+		}
+	}
+
+	return 1;
+}
+
+
+/**
+ *
+ */
+int pvh_get_branch_xname(struct sip_msg *msg, str *xname, str *dst)
+{
+	int os = 0;
+	char br_idx_s[32];
+	char br_idx_len = 0;
+
+	if(dst == NULL)
+		return -1;
+
+	memset(dst->s, 0, dst->len);
+	memcpy(dst->s, xname->s, xname->len);
+	os += xname->len;
+
+	if(_branch > 0) {
+		snprintf(br_idx_s, 32, "%d", _branch - 1);
+		br_idx_len = strlen(br_idx_s);
+		memcpy(dst->s + os, ".", 1);
+		os += 1;
+		memcpy(dst->s + os, br_idx_s, br_idx_len);
+		os += br_idx_len;
+	}
+	if(msg->first_line.type == SIP_REPLY) {
+		snprintf(br_idx_s, 32, ".r.%d", _reply_counter);
+		br_idx_len = strlen(br_idx_s);
+		memcpy(dst->s + os, br_idx_s, br_idx_len);
+		os += br_idx_len;
+	}
+	dst->len = os;
+	dst->s[dst->len] = '\0';
+
+	return 1;
+}
+
+/**
+ *
+ */
+int pvh_clone_branch_xavi(struct sip_msg *msg, str *xname)
+{
+	sr_xavp_t *xavi = NULL;
+	sr_xavp_t *br_xavi = NULL;
+	sr_xavp_t *sub = NULL;
+	sr_xval_t root_xval;
+	char t[header_name_size];
+	str br_xname = {t, header_name_size};
+	int i = 0;
+
+	if((xavi = xavi_get(xname, NULL)) == NULL) {
+		LM_ERR("cannot clone xavi from non existing %.*s\n",
+			xname->len, xname->s);
+		return -1;
+	}
+
+	if(xavi->val.type != SR_XTYPE_XAVP) {
+		LM_ERR("not xavp child type %.*s\n", xavi_name.len, xavi_name.s);
+		return -1;
+	}
+
+	if((sub = xavi->val.v.xavp) == NULL) {
+		LM_ERR("invalid xavi structure: %.*s\n", xavi_name.len, xavi_name.s);
+		return -1;
+	}
+
+	pvh_get_branch_xname(msg, xname, &br_xname);
+
+	memset(&root_xval, 0, sizeof(sr_xval_t));
+	root_xval.type = SR_XTYPE_XAVP;
+	root_xval.v.xavp = NULL;
+
+	if((br_xavi = xavi_add_value(&br_xname, &root_xval, NULL)) == NULL) {
+		LM_ERR("error create xavi %.*s\n", br_xname.len, br_xname.s);
+		return -1;
+	}
+
+	if(cmp_str(xname, &xavi_parsed_xname) == 0) {
+		return 1;
+	}
+
+	do {
+		if(pvh_skip_header(&sub->name))
+			continue;
+		if(sub->val.type == SR_XTYPE_DATA)
+			continue;
+		if(pvh_xavi_append_value(&sub->name, &sub->val, &br_xavi->val.v.xavp)
+				< 0) {
+			LM_ERR("cannot clone xavi %.*s\n", sub->name.len, sub->name.s);
+			return -1;
+		}
+		++i;
+	} while((sub = sub->next) != NULL);
+	LM_DBG("cloned %.*s[%d] => %.*s\n", xname->len, xname->s, i, br_xname.len,
+			br_xname.s);
+	return 1;
+}
+
+/**
+ *
+ */
+int pvh_get_header(struct sip_msg *msg, pv_param_t *param, pv_value_t *res)
+{
+	sr_xavp_t *xavi = NULL;
+	sr_xval_t *xval = NULL;
+	pv_value_t tv;
+	str hname = STR_NULL;
+	int idx = 0;
+	int cnt = 0;
+
+	idx = param->pvi.u.ival;
+
+	if(param->pvn.type == PV_NAME_PVAR) {
+		if(pv_get_spec_value(msg, (pv_spec_p)(param->pvn.u.dname), &tv) != 0) {
+			LM_ERR("cannot get avp value\n");
+			return -1;
+		}
+		if(!(tv.flags & PV_VAL_STR)) {
+			return pv_get_null(msg, param, res);
+		}
+		hname = tv.rs;
+	} else if(param->pvn.u.isname.type == AVP_NAME_STR) {
+		hname = param->pvn.u.isname.name.s;
+	} else {
+		return pv_get_null(msg, param, res);
+	}
+
+	if(idx < 0) {
+		if((xavi = pvh_xavi_get_child(msg, &xavi_name, &hname)) == NULL)
+			cnt = 0;
+		else
+			cnt = xavi_count(&hname, &xavi);
+		idx = idx + cnt;
+		if(idx < 0)
+			pv_get_null(msg, param, res);
+	}
+
+	xval = pvh_xavi_get_value(msg, &xavi_name, &hname, idx);
+
+	if(xval == NULL || !xval->v.s.s)
+		return pv_get_null(msg, param, res);
+
+	return pv_get_strval(msg, param, res, &xval->v.s);
+}
+
+/**
+ *
+ */
+int pvh_set_header(
+		struct sip_msg *msg, pv_param_t *param, int op, pv_value_t *val)
+{
+	sr_xavp_t *xavi = NULL, *avi = NULL;
+	pv_elem_p pv_format = NULL;
+	pv_value_t tv;
+	str *hname = NULL;
+	str fval = STR_NULL;
+	int idxf, idx, hname_cnt, cnt;
+
+	if(pv_get_spec_name(msg, param, &tv) != 0 || (!(tv.flags & PV_VAL_STR))) {
+		LM_ERR("invalid header name, must be a string\n");
+		return -1;
+	}
+	hname = &tv.rs;
+
+	/* get the index */
+	if(pv_get_spec_index(msg, param, &idx, &idxf)!=0)
+	{
+		LM_ERR("invalid index\n");
+		return -1;
+	}
+
+	if((xavi = pvh_get_xavi(msg, &xavi_name)) == NULL) {
+		LM_ERR("xavi %.*s not found\n", xavi_name.len, xavi_name.s);
+		return -1;
+	}
+	avi = xavi->val.v.xavp;
+	hname_cnt = xavi_count(hname, &avi);
+	if(hname_cnt == 0) {
+		idx = 0;
+	} else if(idx < 0) {
+		idx = idx + hname_cnt;
+	}
+	if(idx < 0) {
+		LM_ERR("invalid index\n");
+		return -1;
+	}
+	LM_DBG("xavi:%.*s hname:%.*s hname_cnt:%d idx:%d idxf:%d\n",
+		xavi->name.len, xavi->name.s, hname->len, hname->s,
+		hname_cnt, idx, idxf);
+
+	if(val == NULL || (val->flags & PV_VAL_NULL)) {
+		if(idxf == PV_IDX_ALL) {
+			cnt = xavi_rm_by_name(hname, 1, &xavi);
+			LM_DBG("removed %d values of %.*s=>%.*s, set $null\n",
+				cnt, xavi->name.len, xavi->name.s, hname->len, hname->s);
+			if(pvh_set_xavi(msg, &xavi_name, hname, NULL, SR_XTYPE_NULL, 0, 0)
+					< 0)
+				goto err;
+		} else {
+			if(pvh_set_xavi(msg, &xavi_name, hname, NULL, SR_XTYPE_NULL, idx, 0)
+					< 0)
+				goto err;
+		}
+	} else if(val->flags & (PV_VAL_STR | PV_TYPE_INT | PV_VAL_INT)) {
+		if(val->flags & (PV_TYPE_INT | PV_VAL_INT)) {
+			if(pv_get_sintval(msg, param, val, val->ri) < 0)
+				goto err;
+		}
+		if(pv_parse_format(&val->rs, &pv_format) < 0) {
+			LM_ERR("cannot parse format: %.*s\n", val->rs.len, val->rs.s);
+			goto err;
+		}
+
+		if(pv_printf_s(msg, pv_format, &fval) < 0) {
+			LM_ERR("cannot parse format: %.*s\n", val->rs.len, val->rs.s);
+			goto err;
+		}
+		if( idx == 0 && idxf == PV_IDX_NONE) {
+			if(pvh_set_xavi(msg, &xavi_name, hname, &fval, SR_XTYPE_STR, 0, 1)
+					< 0)
+				goto err;
+		} else if(idxf == PV_IDX_ALL) {
+			if(hname_cnt > 1) {
+				cnt = xavi_rm_by_name(hname, 1, &xavi);
+				LM_DBG("removed %d values of %.*s=>%.*s\n",
+					cnt, xavi->name.len, xavi->name.s, hname->len, hname->s);
+			}
+			if(pvh_set_xavi(msg, &xavi_name, hname, &fval, SR_XTYPE_STR, 0,
+					   hname_cnt ? 0 : 1)
+					< 0)
+				goto err;
+		} else {
+			if(pvh_set_xavi(
+					   msg, &xavi_name, hname, &fval, SR_XTYPE_STR, idx, 0)
+					< 0)
+				goto err;
+		}
+		if(pv_format)
+			pv_elem_free_all(pv_format);
+	} else {
+		LM_ERR("x_hdr %.*s value can be either string, integer or null\n",
+				hname->len, hname->s);
+		goto err;
+	}
+	return 1;
+
+err:
+	if(pv_format)
+		pv_elem_free_all(pv_format);
+	return -1;
+}
+
+/**
+ *
+ */
+xavp_c_data_t *pvh_set_parsed(
+		struct sip_msg *msg, str *hname, str *cur, str *new)
+{
+	xavp_c_data_t *c_data = NULL;
+	str *val = new;
+
+	c_data = (xavp_c_data_t *)shm_malloc(sizeof(xavp_c_data_t));
+	if(c_data == NULL) {
+		SHM_MEM_ERROR;
+		return NULL;
+	}
+	memset(c_data, 0, sizeof(xavp_c_data_t));
+	if(val == NULL)
+		val = cur;
+	if(pvh_merge_uri(msg, SET_URI_T, cur, val, c_data) < 0)
+		goto err;
+	if(pvh_set_xavi(msg, &xavi_parsed_xname, hname, c_data, SR_XTYPE_DATA, 0, 0)
+			< 0)
+		goto err;
+	LM_DBG("c_data from pvh_merge_uri hname:%.*s\n", hname->len, hname->s);
+
+	return c_data;
+
+err:
+	// how can I call?? pvh_xavi_free_data(c_data, shm_free);
+	return NULL;
+}
+
+/**
+ *
+ */
+int pvh_get_uri(struct sip_msg *msg, pv_param_t *param, pv_value_t *res)
+{
+	sr_xval_t *xval = NULL;
+	sr_xval_t *xval_pd = NULL;
+	xavp_c_data_t *c_data = NULL;
+	int p_no = 0;
+	str sval = STR_NULL;
+	int ival = 0;
+	int is_strint = 0;
+	char t[header_name_size];
+	str hname = {t, header_name_size - 1};
+
+	p_no = param->pvn.u.isname.name.n;
+	if(p_no >= 1 && p_no <= 5)
+		pvh_str_copy(&hname, &_hdr_from, header_name_size);
+	else if(p_no >= 6 && p_no <= 10)
+		pvh_str_copy(&hname, &_hdr_to, header_name_size);
+
+	xval = pvh_xavi_get_value(msg, &xavi_name, &hname, 0);
+	if(xval == NULL || !xval->v.s.s) {
+		/*	LM_DBG("xavi:%.*s hname:%.*s is null\n", xavi_name.len, xavi_name.s,
+				hname.len, hname.s); */
+		goto err;
+	}
+
+	xval_pd = pvh_xavi_get_value(msg, &xavi_parsed_xname, &hname, 0);
+
+	if(xval_pd) {
+		/*	LM_DBG("p_no:%d c_data from xavi_parsed_xname hname:%.*s\n", p_no,
+				hname.len, hname.s); */
+		c_data = (xavp_c_data_t *)xval_pd->v.data->p;
+	}
+
+	if(c_data != NULL && cmp_str(&xval->v.s, &c_data->value) != 0) {
+		/*	LM_DBG("xval:%.*s != c_data->value:%.*s\n", xval->v.s.len, xval->v.s.s,
+				c_data->value.len, c_data->value.s); */
+		c_data = NULL;
+	}
+
+	if(c_data == NULL) {
+		if((c_data = pvh_set_parsed(msg, &hname, &xval->v.s, NULL)) == NULL)
+			goto err;
+	}
+
+	switch(p_no) {
+		case 1: // full from
+		case 6: // full to
+			sval = c_data->to_b.uri;
+			break;
+		case 2: // username from
+		case 7: // username to
+			sval = c_data->to_b.parsed_uri.user;
+			break;
+		case 3: // domain from
+		case 8: // domain to
+			sval = c_data->to_b.parsed_uri.host;
+			break;
+		case 4: // displayname from
+		case 9: // displayname to
+			sval = c_data->to_b.display;
+			break;
+		case 5:  // from tag
+		case 10: // to tag
+			sval = c_data->to_b.tag_value;
+			break;
+		default:
+			LM_ERR("unknown get uri op\n");
+	}
+	/*	LM_DBG("p_no:%d sval:%.*s\n", p_no, sval.len, sval.s); */
+	return sval.s ? is_strint ? pv_get_strintval(msg, param, res, &sval, ival)
+							  : pv_get_strval(msg, param, res, &sval)
+				  : pv_get_null(msg, param, res);
+
+err:
+	return pv_get_null(msg, param, res);
+}
+
+/**
+ *
+ */
+int pvh_set_uri(struct sip_msg *msg, pv_param_t *param, int op, pv_value_t *val)
+{
+	sr_xval_t *xval = NULL;
+	xavp_c_data_t *c_data = NULL;
+	pv_elem_p pv_format = NULL;
+	int p_no = 0;
+	enum action_type a_type;
+	char t[header_name_size];
+	str hname = {t, header_name_size - 1};
+	str fval;
+
+	p_no = param->pvn.u.isname.name.n;
+	if(p_no >= 1 && p_no <= 5)
+		pvh_str_copy(&hname, &_hdr_from, header_name_size);
+	else if(p_no >= 6 && p_no <= 10)
+		pvh_str_copy(&hname, &_hdr_to, header_name_size);
+
+	switch(p_no) {
+		case 1: // uri from
+		case 6: // uri to
+			a_type = SET_URI_T;
+			break;
+		case 2: // username from
+		case 7: // username to
+			a_type = SET_USER_T;
+			break;
+		case 3: // domain from
+		case 8: // domain to
+			a_type = SET_HOST_T;
+			break;
+		case 4: // displayname from
+		case 9: // displayname to
+			a_type = SET_USERPHONE_T;
+			break;
+		default:
+			LM_ERR("unknown set uri op\n");
+			goto err;
+	}
+
+	if(val->flags & (PV_TYPE_INT | PV_VAL_INT)) {
+		if(pv_get_sintval(msg, param, val, val->ri) < 0)
+			goto err;
+	}
+
+	if(pv_parse_format(&val->rs, &pv_format) < 0) {
+		LM_ERR("cannot parse format: %.*s\n", val->rs.len, val->rs.s);
+		goto err;
+	}
+
+	if(pv_printf_s(msg, pv_format, &fval) < 0) {
+		LM_ERR("cannot parse format: %.*s\n", val->rs.len, val->rs.s);
+		goto err;
+	}
+
+	xval = pvh_xavi_get_value(msg, &xavi_name, &hname, 0);
+	if(xval == NULL || !xval->v.s.s)
+		goto err;
+
+	c_data = (xavp_c_data_t *)shm_malloc(sizeof(xavp_c_data_t));
+	if(c_data == NULL) {
+		SHM_MEM_ERROR;
+		goto err;
+	}
+	memset(c_data, 0, sizeof(xavp_c_data_t));
+	if(pvh_merge_uri(msg, a_type, &xval->v.s, &fval, c_data) < 0)
+		goto err;
+	/*	LM_DBG("xavi:%.*s hname:%.*s value:%.*s\n", xavi_name.len, xavi_name.s,
+			hname.len, hname.s, c_data->value.len, c_data->value.s); */
+	if(pvh_set_xavi(msg, &xavi_name, &hname, &c_data->value, SR_XTYPE_STR, 0, 0)
+			< 0)
+		goto err;
+
+	if(pvh_set_xavi(
+			   msg, &xavi_parsed_xname, &hname, c_data, SR_XTYPE_DATA, 0, 0)
+			< 0)
+		goto err;
+
+	if(pv_format)
+		pv_elem_free_all(pv_format);
+	return 1;
+
+err:
+	if(pv_format)
+		pv_elem_free_all(pv_format);
+	return -1;
+}
+
+/**
+ *
+ */
+int pvh_merge_uri(struct sip_msg *msg, enum action_type type, str *cur,
+		str *new, xavp_c_data_t *c_data)
+{
+	struct sip_uri puri;
+	struct to_body tb;
+	struct to_param *param = NULL;
+	struct to_param *sparam_start = NULL;
+	struct to_param **sparam = NULL;
+	str *merged = NULL;
+	char *c_ptr = NULL;
+	str uri_t;
+	int os = 0;
+	int t_len = 0;
+
+	parse_addr_spec(cur->s, cur->s + cur->len, &tb, 0);
+	if(!tb.uri.s) {
+		LM_ERR("cannot parse addr spec\n");
+		goto err;
+	}
+
+	if(parse_uri(tb.uri.s, tb.uri.len, &tb.parsed_uri) < 0) {
+		LM_ERR("cannot parse uri %.*s\n", tb.uri.len, tb.uri.s);
+		goto err;
+	}
+	puri = tb.parsed_uri;
+
+	c_data->value.s = (char *)shm_malloc(header_value_size);
+	if(c_data->value.s == NULL) {
+		SHM_MEM_ERROR;
+		goto err;
+	}
+	merged = &c_data->value;
+
+	if(type == SET_URI_T && strchr(new->s, '<')) {
+		pvh_str_copy(merged, new, header_value_size);
+		goto reparse;
+	}
+
+	os = 0;
+	if(type == SET_USERPHONE_T) {
+		memcpy(merged->s + os, new->s, new->len);
+		os += new->len;
+		memcpy(merged->s + os, " ", 1);
+		os += 1;
+	} else if(tb.display.len > 0) {
+		memcpy(merged->s + os, tb.display.s, tb.display.len);
+		os += tb.display.len;
+		memcpy(merged->s + os, " ", 1);
+		os += 1;
+	}
+	memcpy(merged->s + os, "<", 1);
+	os += 1;
+	if(type != SET_URI_T) {
+		uri_type_to_str(puri.type, &uri_t);
+		t_len = uri_t.len + 1;
+		memcpy(merged->s + os, uri_t.s, uri_t.len);
+		os += uri_t.len;
+		memcpy(merged->s + os, ":", 1);
+		os += 1;
+	}
+	switch(type) {
+		case SET_USERPHONE_T:
+			memcpy(merged->s + os, tb.uri.s + t_len, tb.uri.len - t_len);
+			os += tb.uri.len - t_len;
+			break;
+		case SET_URI_T:
+			memcpy(merged->s + os, new->s, new->len);
+			os += new->len;
+			break;
+		case SET_USER_T:
+			memcpy(merged->s + os, new->s, new->len);
+			os += new->len;
+			memcpy(merged->s + os, tb.uri.s + t_len + puri.user.len,
+					tb.uri.len - t_len - puri.user.len);
+			os += tb.uri.len - t_len - puri.user.len;
+			break;
+		case SET_HOST_T:
+			if((c_ptr = strchr(tb.uri.s, '@')) == NULL) {
+				LM_ERR("invalid uri: %.*s\n", tb.uri.len, tb.uri.s);
+				goto err;
+			}
+			memcpy(merged->s + os, tb.uri.s + t_len,
+					c_ptr - tb.uri.s - t_len + 1);
+			os += c_ptr - tb.uri.s - t_len + 1;
+			memcpy(merged->s + os, new->s, new->len);
+			os += new->len;
+			memcpy(merged->s + os, c_ptr + puri.host.len + 1,
+					tb.uri.s + tb.uri.len - c_ptr - puri.host.len - 1);
+			os += tb.uri.s + tb.uri.len - c_ptr - puri.host.len - 1;
+			break;
+		default:
+			LM_ERR("unknown set uri op\n");
+			goto err;
+	}
+	memcpy(merged->s + os, ">", 1);
+	os += 1;
+	if((param = tb.param_lst) != NULL) {
+		while(param) {
+			memcpy(merged->s + os, ";", 1);
+			os += 1;
+			memcpy(merged->s + os, param->name.s, param->name.len);
+			os += param->name.len;
+			memcpy(merged->s + os, "=", 1);
+			os += 1;
+			memcpy(merged->s + os, param->value.s, param->value.len);
+			os += param->value.len;
+			param = param->next;
+		}
+	}
+	merged->len = os;
+	merged->s[merged->len] = '\0';
+
+reparse:
+
+	parse_addr_spec(merged->s, merged->s + merged->len, &c_data->to_b, 0);
+	if(!c_data->to_b.uri.s) {
+		LM_ERR("cannot parse addr spec\n");
+		goto err;
+	}
+
+	if((param = tb.param_lst) != NULL) {
+		while(param) {
+			if(sparam == NULL)
+				sparam = &sparam_start;
+			*sparam = (struct to_param *)shm_malloc(sizeof(struct to_param));
+			if(*sparam == NULL) {
+				SHM_MEM_ERROR;
+				goto err;
+			}
+			memset(*sparam, 0, sizeof(struct to_param));
+			memcpy(*sparam, param, sizeof(struct to_param));
+			(*sparam)->next = NULL;
+			sparam = &(*sparam)->next;
+			param = param->next;
+		}
+		c_data->to_params = sparam_start;
+	}
+
+	if(parse_uri(c_data->to_b.uri.s, c_data->to_b.uri.len,
+			   &c_data->to_b.parsed_uri)
+			< 0) {
+		LM_ERR("cannot parse uri %.*s\n", c_data->to_b.uri.len,
+				c_data->to_b.uri.s);
+		goto err;
+	}
+
+	free_to_params(&tb);
+	free_to_params(&c_data->to_b);
+	return 1;
+
+err:
+	free_to_params(&tb);
+	free_to_params(&c_data->to_b);
+	return -1;
+}
+
+/**
+ *
+ */
+int pvh_get_reply_sr(struct sip_msg *msg, pv_param_t *param, pv_value_t *res)
+{
+	sr_xval_t *xval = NULL;
+	int p_no = 0;
+
+	p_no = param->pvn.u.isname.name.n;
+
+	if(msg->first_line.type != SIP_REPLY)
+		return pv_get_null(msg, param, res);
+
+	switch(p_no) {
+		case 1: // status
+			return pv_get_intstrval(msg, param, res,
+					(int)msg->first_line.u.reply.statuscode,
+					&msg->first_line.u.reply.status);
+			break;
+		case 2: // reason
+			xval = pvh_xavi_get_value(msg, &xavi_name, &_hdr_reply_reason, 0);
+			return pv_get_strval(msg, param, res,
+					xval && xval->v.s.s ? &xval->v.s
+										: &msg->first_line.u.reply.reason);
+			break;
+		default:
+			LM_ERR("unknown get reply op\n");
+	}
+
+	return pv_get_null(msg, param, res);
+}
+
+/**
+ *
+ */
+int pvh_set_reply_sr(
+		struct sip_msg *msg, pv_param_t *param, int op, pv_value_t *val)
+{
+	pv_elem_p pv_format = NULL;
+	int p_no = 0;
+	unsigned int code = 0;
+	str fval;
+
+	p_no = param->pvn.u.isname.name.n;
+
+	if(msg->first_line.type != SIP_REPLY) {
+		LM_ERR("set reply: not a reply message\n");
+		goto err;
+	}
+
+	if(val->flags & (PV_VAL_NULL)) {
+		LM_ERR("set reply: value cannot be null\n");
+		goto err;
+	}
+
+	if(val->flags & (PV_TYPE_INT | PV_VAL_INT)) {
+		if(pv_get_sintval(msg, param, val, val->ri) < 0)
+			goto err;
+	}
+
+	if(pv_parse_format(&val->rs, &pv_format) < 0) {
+		LM_ERR("cannot parse format: %.*s\n", val->rs.len, val->rs.s);
+		goto err;
+	}
+
+	if(pv_printf_s(msg, pv_format, &fval) < 0) {
+		LM_ERR("cannot parse format: %.*s\n", val->rs.len, val->rs.s);
+		goto err;
+	}
+
+	switch(p_no) {
+		case 1: // status
+			code = atoi(fval.s);
+			if(code < 100 || code > 699) {
+				LM_ERR("set reply: wrong status code: %d\n", code);
+				goto err;
+			}
+			if((code < 300 || msg->REPLY_STATUS < 300)
+					&& (code / 100 != msg->REPLY_STATUS / 100)) {
+				LM_ERR("set reply: 1xx or 2xx replies cannot be changed or set "
+					   "to\n");
+				goto err;
+			}
+			msg->first_line.u.reply.statuscode = code;
+			msg->first_line.u.reply.status.s[2] = code % 10 + '0';
+			code /= 10;
+			msg->first_line.u.reply.status.s[1] = code % 10 + '0';
+			code /= 10;
+			msg->first_line.u.reply.status.s[0] = code + '0';
+			break;
+		case 2: // reason
+			if(pvh_set_xavi(msg, &xavi_name, &_hdr_reply_reason, &fval,
+					   SR_XTYPE_STR, 0, 0)
+					< 0) {
+				LM_ERR("set reply: cannot set reply reason\n");
+				goto err;
+			}
+			break;
+		default:
+			LM_ERR("unknown set reply op\n");
+			goto err;
+	}
+
+	if(pv_format)
+		pv_elem_free_all(pv_format);
+	return 1;
+
+err:
+	if(pv_format)
+		pv_elem_free_all(pv_format);
+	return -1;
+}
diff --git a/src/modules/pv_headers/pvh_xavp.h b/src/modules/pv_headers/pvh_xavp.h
new file mode 100644
index 0000000..646ebd7
--- /dev/null
+++ b/src/modules/pv_headers/pvh_xavp.h
@@ -0,0 +1,62 @@
+/*
+ * pv_headers
+ *
+ * Copyright (C)
+ * 2020 Victor Seva <vseva@sipwise.com>
+ * 2018 Kirill Solomko <ksolomko@sipwise.com>
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef PV_XAVP_H
+#define PV_XAVP_H
+
+#include "../../core/str.h"
+#include "../../core/xavp.h"
+
+#include "pv_headers.h"
+
+int pvh_reply_append(sr_xavp_t **start);
+
+int pvh_set_xavi(struct sip_msg *msg, str *xname, str *name, void *data,
+		sr_xtype_t type, int idx, int append);
+int pvh_xavi_keys_count(sr_xavp_t **start);
+sr_xavp_t *pvh_xavi_get_child(struct sip_msg *msg, str *xname, str *name);
+int pvh_avp_is_null(sr_xavp_t *avp);
+
+int pvh_get_branch_xname(struct sip_msg *msg, str *xname, str *dst);
+int pvh_clone_branch_xavi(struct sip_msg *msg, str *xname);
+
+int pvh_parse_header_name(pv_spec_p sp, str *hname);
+int pvh_get_header(struct sip_msg *msg, pv_param_t *param, pv_value_t *res);
+int pvh_set_header(
+		struct sip_msg *msg, pv_param_t *param, int op, pv_value_t *val);
+xavp_c_data_t *pvh_set_parsed(
+		struct sip_msg *msg, str *hname, str *cur, str *new);
+
+int pvh_set_uri(
+		struct sip_msg *msg, pv_param_t *param, int op, pv_value_t *val);
+int pvh_get_uri(struct sip_msg *msg, pv_param_t *param, pv_value_t *res);
+int pvh_merge_uri(struct sip_msg *msg, enum action_type type, str *cur,
+		str *new, xavp_c_data_t *c_data);
+
+int pvh_get_reply_sr(struct sip_msg *msg, pv_param_t *param, pv_value_t *res);
+int pvh_set_reply_sr(
+		struct sip_msg *msg, pv_param_t *param, int op, pv_value_t *val);
+
+#endif /* PV_XAVP_H */
