Browse Source

lost: added Geolocation header value list support

- the Geolocation header value parsing now supports
  a list of location URIs of type (cid, http and https).
- types may be filtered and the list sequence may be
  changed via new module parameters.
- besides: README update and code refactoring.
Wolfgang Kampichler 4 years ago
parent
commit
f9f26adc11

+ 66 - 5
src/modules/lost/doc/lost_admin.xml

@@ -169,6 +169,62 @@
                 <programlisting format="linespecific">
     ...
     modparam("lost", "location_type, "civic geodetic locationURI")
+    ...
+                </programlisting>
+            </example>
+        </section>
+        <section id="lost.p.geoheader_type">
+            <title><varname>geoheader_type</varname> (int)</title>
+            <para>
+            A Geolocation header may include a list of locationValues pointing
+            to either a Presence Information Data Format Location Object
+            (PIDF-LO) in the SIP body using a content-indirection (cid:) URI
+            per RFC4483 (<ulink url="https://tools.ietf.org/html/rfc4483"/>),
+            or an http(s) URI pointing to an external source. This parameter
+            supports filtering of the following types:
+            </para>
+			<itemizedlist>
+                <listitem><para>
+                    <emphasis>0 (any)</emphasis> - any URI (first or last)
+                </para></listitem>
+                <listitem><para>
+                    <emphasis>1 (cid)</emphasis> - cid URI (aka Location-by-Value)
+                </para></listitem>
+                <listitem><para>
+                    <emphasis>2 (http)</emphasis> - http URI (aka Location-by-Reference)
+                </para></listitem>
+                <listitem><para>
+                    <emphasis>3 (https)</emphasis> - https URI (aka Location-by-Reference)
+                </para></listitem>
+            </itemizedlist>
+            <para>
+            Default: 0 (any)
+            </para>
+            <example>
+            <title>Set <varname>geoheader_type</varname> parameter</title>
+                <programlisting format="linespecific">
+    ...
+    modparam("lost", "geoheader_type", 1)
+    ...
+                </programlisting>
+            </example>
+        </section>
+        <section id="lost.p.geoheader_order">
+            <title><varname>geoheader_order</varname> (int)</title>
+            <para>
+            A Geolocation header may include a list of locationValues. This
+            parameter sets the order of the URI used to retrieve location
+            information, either the first element of a certain type or the
+            last. Values are 0 (first) or 1 (last).
+            </para>
+            <para>
+            Default: 0 (first)
+            </para>
+            <example>
+            <title>Set <varname>geoheader_order</varname> parameter</title>
+                <programlisting format="linespecific">
+    ...
+    modparam("lost", "geoheader_order", 0)
     ...
                 </programlisting>
             </example>
@@ -328,11 +384,12 @@ xlog("L_INFO", "LOST findService: Result code $var(res)\nUri: $var(uri)\nName: $
             <ulink url="https://www.digitalocean.com/community/questions/how-to-upgrade-curl-in-centos6"></ulink>.
         </para>
         <para>
-            Note: http_client_query exported by the http_client API returns by default the first line of
-            the HTTP response, but the lost module requires the complete response message, otherwise
-            dereferencing location via the HTTP URL provided with the Geolocation header causes an error.
-            Therefore, to work properly, it is required to set the http_client module parameter query_result
-            to 0. More details at:
+            Note: http_client_query exported by the http_client API returns by
+            default the first line of the HTTP response, but the lost module requires
+            the complete response message, otherwise dereferencing location via the
+            HTTP URL provided with the Geolocation header causes an error. Therefore,
+            to work properly, it is required to set the http_client module parameter
+            query_result to 0. More details at:
             <ulink url="https://www.kamailio.org/docs/modules/devel/modules/http_client.html#http_client.p.query_result"></ulink>.
         </para>
         <para>
@@ -340,5 +397,9 @@ xlog("L_INFO", "LOST findService: Result code $var(res)\nUri: $var(uri)\nName: $
             following link (search for "GET ACCESS"):
             <ulink url="https://gridgears.at/"></ulink>.
         </para>
+        <para>
+            Note: in case modparam "geoheader_type" is set to 2 (http), the module may
+            use 3 (https) as fallback and vice versa.
+        </para>
     </section>
 </chapter>

+ 186 - 157
src/modules/lost/functions.c

@@ -54,6 +54,8 @@
 
 extern httpc_api_t httpapi;
 
+extern int lost_geoloc_type;
+extern int lost_geoloc_order;
 extern int held_resp_time;
 extern int held_exact_type;
 extern str held_loc_type;
@@ -83,7 +85,8 @@ char *lost_held_type(char *type, int *exact, int *lgth)
 	if(strstr(type, HELD_TYPE_ANY)) {
 		len = strlen(ret) + strlen(HELD_TYPE_ANY) + 1;
 		tmp = pkg_realloc(ret, len);
-		if(tmp == NULL) goto err;
+		if(tmp == NULL)
+			goto err;
 		ret = tmp;
 		strcat(ret, HELD_TYPE_ANY);
 		*exact = 0;
@@ -91,7 +94,8 @@ char *lost_held_type(char *type, int *exact, int *lgth)
 		if(strstr(type, HELD_TYPE_CIV)) {
 			len = strlen(ret) + strlen(HELD_TYPE_CIV) + 1;
 			tmp = pkg_realloc(ret, len);
-			if(tmp == NULL) goto err;
+			if(tmp == NULL)
+				goto err;
 			ret = tmp;
 			strcat(ret, HELD_TYPE_CIV);
 		}
@@ -99,13 +103,15 @@ char *lost_held_type(char *type, int *exact, int *lgth)
 			if(strlen(ret) > 1) {
 				len = strlen(ret) + strlen(HELD_TYPE_SEP) + 1;
 				tmp = pkg_realloc(ret, len);
-				if(tmp == NULL) goto err;
+				if(tmp == NULL)
+					goto err;
 				ret = tmp;
 				strcat(ret, HELD_TYPE_SEP);
 			}
 			len = strlen(ret) + strlen(HELD_TYPE_GEO) + 1;
 			tmp = pkg_realloc(ret, len);
-			if(tmp == NULL) goto err;
+			if(tmp == NULL)
+				goto err;
 			ret = tmp;
 			strcat(ret, HELD_TYPE_GEO);
 		}
@@ -113,13 +119,15 @@ char *lost_held_type(char *type, int *exact, int *lgth)
 			if(strlen(ret) > 1) {
 				len = strlen(ret) + strlen(HELD_TYPE_SEP) + 1;
 				tmp = pkg_realloc(ret, len);
-				if(tmp == NULL) goto err;
+				if(tmp == NULL)
+					goto err;
 				ret = tmp;
 				strcat(ret, HELD_TYPE_SEP);
 			}
 			len = strlen(ret) + strlen(HELD_TYPE_URI) + 1;
 			tmp = pkg_realloc(ret, len);
-			if(tmp == NULL) goto err;
+			if(tmp == NULL)
+				goto err;
 			ret = tmp;
 			strcat(ret, HELD_TYPE_URI);
 		}
@@ -130,7 +138,7 @@ char *lost_held_type(char *type, int *exact, int *lgth)
 
 err:
 	LM_ERR("no more private memory\n");
-	if (ret != NULL) {
+	if(ret != NULL) {
 		pkg_free(ret);
 	}
 	*lgth = 0;
@@ -287,8 +295,8 @@ int lost_held_function(struct sip_msg *_m, char *_con, char *_pidf, char *_url,
 
 		for(cur_node = root->children; cur_node; cur_node = cur_node->next) {
 			if(cur_node->type == XML_ELEMENT_NODE) {
-				if(xmlStrcmp(cur_node->name,
-							(const xmlChar *)"locationUriSet") == 0) {
+				if(xmlStrcmp(cur_node->name, (const xmlChar *)"locationUriSet")
+						== 0) {
 
 					LM_DBG("*** node '%s' found\n", cur_node->name);
 
@@ -302,8 +310,8 @@ int lost_held_function(struct sip_msg *_m, char *_con, char *_pidf, char *_url,
 						geo.s = lost_trim_content(geo.s, &geo.len);
 					}
 				}
-				if(xmlStrcmp(cur_node->name,
-							(const xmlChar *)"presence") == 0) {
+				if(xmlStrcmp(cur_node->name, (const xmlChar *)"presence")
+						== 0) {
 
 					LM_DBG("*** node '%s' found\n", cur_node->name);
 
@@ -413,25 +421,29 @@ int lost_function(struct sip_msg *_m, char *_con, char *_uri, char *_name,
 	pv_value_t pverr;
 
 	p_loc_t loc = NULL;
+	p_geolist_t geolist = NULL;
+	int geotype;
 
-	xmlDocPtr doc = NULL;
-	xmlNodePtr root = NULL;
-
+	str url = {NULL, 0};
 	str uri = {NULL, 0};
 	str urn = {NULL, 0};
 	str err = {NULL, 0};
-	str res = {NULL, 0};
+	str req = {NULL, 0};
 	str con = {NULL, 0};
 	str ret = {NULL, 0};
-	str geo = {NULL, 0};
-	str geohdr = {NULL, 0};
 	str name = {NULL, 0};
 	str pidf = {NULL, 0};
+	str geohdr = {NULL, 0};
 	str pidfhdr = {NULL, 0};
 
 	struct msg_start *fl;
 	char *search = NULL;
+	char *geoval = NULL;
 	int curlres = 0;
+	int geoitems = 0;
+
+	xmlDocPtr doc = NULL;
+	xmlNodePtr root = NULL;
 
 	if(_con == NULL || _uri == NULL || _name == NULL || _err == NULL) {
 		LM_ERR("invalid parameter\n");
@@ -462,7 +474,7 @@ int lost_function(struct sip_msg *_m, char *_con, char *_uri, char *_name,
 				&& ((*(search + 1) == 'r') || (*(search + 1) == 'R'))
 				&& ((*(search + 2) == 'n') || (*(search + 2) == 'N'))
 				&& (*(search + 3) == ':')) {
-			LM_INFO("### LOST urn [%.*s]\n", urn.len, urn.s);
+			LM_INFO("### LOST urn\t[%.*s]\n", urn.len, urn.s);
 		} else {
 			LM_ERR("service urn not found\n");
 			goto err;
@@ -474,159 +486,178 @@ int lost_function(struct sip_msg *_m, char *_con, char *_uri, char *_name,
 	/* pidf from parameter */
 	if(_pidf) {
 		if(fixup_get_svalue(_m, (gparam_p)_pidf, &pidf) != 0) {
-			LM_ERR("cannot get pidf-lo\n");
-			goto err;
+			LM_WARN("cannot get pidf-lo parameter\n");
+		} else {
+
+			LM_DBG("parsing pidf-lo from paramenter\n");
+
+			if(pidf.len > 0) {
+
+				LM_DBG("pidf-lo: [%.*s]\n", pidf.len, pidf.s);
+
+				/* parse the pidf-lo */
+				loc = lost_parse_pidf(pidf, urn);
+				/* free memory */
+				pidf.s = NULL;
+				pidf.len = 0;
+			} else {
+				LM_WARN("no valid pidf parameter ...\n");
+			}
 		}
 	}
-	/* pidf from geolocation header */
-	if(pidf.len == 0) {
-		LM_WARN("no pidf parameter, trying geolocation header ...\n");
+
+	/* no pidf-lo so far ... check geolocation header */
+	if(loc == NULL) {
+
+		LM_DBG("looking for geolocation header ...\n");
+
 		geohdr.s = lost_get_geolocation_header(_m, &geohdr.len);
 		if(geohdr.len == 0) {
 			LM_ERR("geolocation header not found\n");
 			goto err;
-		} else {
+		}
 
-			LM_DBG("geolocation header found\n");
-
-			/* pidf from multipart body, check cid scheme */
-			if(geohdr.len > 6) {
-				search = geohdr.s;
-				if((*(search + 0) == '<')
-						&& ((*(search + 1) == 'c') || (*(search + 1) == 'C'))
-						&& ((*(search + 2) == 'i') || (*(search + 2) == 'I'))
-						&& ((*(search + 3) == 'd') || (*(search + 3) == 'D'))
-						&& (*(search + 4) == ':')) {
-					search += 4;
-					*search = '<';
-					geo.s = search;
-					geo.len = geo.len - 4;
-
-					LM_DBG("cid: [%.*s]\n", geo.len, geo.s);
-
-					/* get body part - filter=>content id */
-					pidf.s = get_body_part_by_filter(
-							_m, 0, 0, geo.s, NULL, &pidf.len);
-					if(pidf.len == 0) {
-						LM_ERR("no multipart body found\n");
-						/* free memory */
-						geo.s = NULL;
-						geo.len = 0;
-						lost_free_string(&geohdr);
-						goto err;
-					}
-				}
-				/* no pidf-lo so far ... check http(s) scheme */
-				if(((*(search + 0) == 'h') || (*(search + 0) == 'H'))
-						&& ((*(search + 1) == 't') || (*(search + 1) == 'T'))
-						&& ((*(search + 2) == 't') || (*(search + 2) == 'T'))
-						&& ((*(search + 3) == 'p') || (*(search + 3) == 'P'))) {
-					geo.s = geohdr.s;
-					geo.len = geohdr.len;
+		LM_DBG("geolocation header found\n");
 
-					if(*(search + 4) == ':') {
+		/* parse Geolocation header */
+		geolist = lost_new_geoheader_list(geohdr, &geoitems);
+		if(geoitems == 0) {
+			LM_ERR("invalid geolocation header\n");
+			lost_free_string(&geohdr);
+			goto err;
+		}
 
-						LM_DBG("http url: [%.*s]\n", geo.len, geo.s);
+		LM_DBG("number of location URIs: %d\n", geoitems);
 
-					} else if(((*(search + 4) == 's') || (*(search + 4) == 'S'))
-							  && (*(search + 5) == ':')) {
+		if(lost_geoloc_order == 0) {
 
-						LM_DBG("https url: [%.*s]\n", geo.len, geo.s);
+			LM_DBG("reversing location URI sequence\n");
 
-					} else {
-						LM_ERR("invalid url: [%.*s]\n", geo.len, geo.s);
-						/* free memory */
-						geo.s = NULL;
-						geo.len = 0;
-						lost_free_string(&geohdr);
-						goto err;
-					}
+			lost_reverse_geoheader_list(&geolist);
+		}
 
-					/* ! dereference pidf.lo at location server - HTTP GET */
-					/* ! requires hack in http_client module */
-					/* ! functions.c => http_client_query => query_params.oneline = 0; */
-					curlres = httpapi.http_client_query(
-							_m, geo.s, &pidfhdr, NULL, NULL);
-					/* free memory */
-					geo.s = NULL;
-					geo.len = 0;
-					lost_free_string(&geohdr);
-					/* only HTTP 2xx responses are accepted */
-					if(curlres >= 300 || curlres < 100) {
-						LM_ERR("http GET failed with error: %d\n", curlres);
-						/* free memory */
-						lost_free_string(&pidfhdr);
-						goto err;
-					}
+		switch(lost_geoloc_type) {
+			case ANY: /* type: 0 */
+				geoval = lost_get_geoheader_value(geolist, ANY, &geotype);
 
-					LM_DBG("http GET returned: %d\n", curlres);
+				LM_DBG("geolocation header field (any): %s\n", geoval);
 
-					if(pidfhdr.len == 0) {
-						LM_ERR("dereferencing location failed\n");
-						goto err;
-					}
-					pidf.s = pidfhdr.s;
-					pidf.len = pidfhdr.len;
+				break;
+			case CID: /* type: 1 */
+				geoval = lost_get_geoheader_value(geolist, CID, &geotype);
+
+				LM_DBG("geolocation header field (LbV): %s\n", geoval);
+
+				break;
+			case HTTP: /* type: 2 */
+				geoval = lost_get_geoheader_value(geolist, HTTP, &geotype);
+				/* fallback to https */
+				if(geoval == NULL) {
+					LM_WARN("no valid http URL ... trying https\n");
+					geoval = lost_get_geoheader_value(geolist, HTTPS, &geotype);
 				}
-			} else {
-				LM_ERR("invalid geolocation header\n");
-				goto err;
-			}
-		}
-	}
 
-	/* no pidf-lo return error */
-	if(pidf.len == 0) {
-		LM_ERR("pidf-lo not found\n");
-		goto err;
-	}
+				LM_DBG("geolocation header field (LbR): %s\n", geoval);
 
-	LM_DBG("pidf-lo: [%.*s]\n", pidf.len, pidf.s);
+				break;
+			case HTTPS: /* type: 3 */
+				/* prefer https */
+				geoval = lost_get_geoheader_value(geolist, HTTPS, &geotype);
+				/* fallback to http */
+				if(geoval == NULL) {
+					LM_WARN("no valid https URL ... trying http\n");
+					geoval = lost_get_geoheader_value(geolist, HTTP, &geotype);
+				}
 
-	/* read and parse pidf-lo */
-	doc = xmlReadMemory(pidf.s, pidf.len, 0, NULL,
-			XML_PARSE_NOBLANKS | XML_PARSE_NONET | XML_PARSE_NOCDATA);
+				LM_DBG("geolocation header field (LbR): %s\n", geoval);
 
-	if(doc == NULL) {
-		LM_WARN("invalid xml (pidf-lo): [%.*s]\n", pidf.len, pidf.s);
-		doc = xmlRecoverMemory(pidf.s, pidf.len);
-		if(doc == NULL) {
-			LM_ERR("xml (pidf-lo) recovery failed on: [%.*s]\n", pidf.len,
-					pidf.s);
+				break;
+			default:
+				LM_WARN("unknown module parameter value\n");
+				geoval = lost_get_geoheader_value(geolist, UNKNOWN, &geotype);
+
+				LM_DBG("geolocation header field (any): %s\n", geoval);
+
+				break;
+		}
+
+		if(geoval == NULL) {
+			LM_ERR("invalid geolocation header\n");
+			/* free memory */
+			lost_delete_geoheader_list(geolist);
+			lost_free_string(&geohdr);
 			goto err;
 		}
 
-		LM_DBG("xml (pidf-lo) recovered\n");
-	}
+		LM_INFO("### LOST loc\t[%s]\n", geoval);
 
-	root = xmlDocGetRootElement(doc);
-	if(root == NULL) {
-		LM_ERR("empty pidf-lo document\n");
-		goto err;
-	}
-	if((!xmlStrcmp(root->name, (const xmlChar *)"presence"))
-			|| (!xmlStrcmp(root->name, (const xmlChar *)"locationResponse"))) {
-		/* get the geolocation: point or circle, urn, ... */
-		loc = lost_new_loc(urn);
-		if(loc == NULL) {
-			LM_ERR("location object allocation failed\n");
-			goto err;
+		/* use location by value */
+		if(geotype == CID) {
+
+			/* get body part - filter=>content-indirection */
+			pidf.s = get_body_part_by_filter(_m, 0, 0, geoval, NULL, &pidf.len);
+			if(pidf.len > 0) {
+
+				LM_DBG("LbV pidf-lo: [%.*s]\n", pidf.len, pidf.s);
+
+				/* parse the pidf-lo */
+				loc = lost_parse_pidf(pidf, urn);
+				/* free memory */
+				pidf.s = NULL;
+				pidf.len = 0;
+			} else {
+				LM_WARN("no multipart body found\n");
+			}
 		}
-		if(lost_parse_location_info(root, loc) < 0) {
-			LM_ERR("location element not found\n");
-			goto err;
+
+		/* use location by reference */
+		if((geotype == HTTPS) || (geotype == HTTP)) {
+			url.s = geoval;
+			url.len = strlen(geoval);
+			/* ! dereference pidf.lo at location server - HTTP GET */
+			/* ! requires hack in http_client module */
+			/* ! functions.c => http_client_query => query_params.oneline = 0; */
+			curlres =
+					httpapi.http_client_query(_m, url.s, &pidfhdr, NULL, NULL);
+			/* free memory */
+			url.s = NULL;
+			url.len = 0;
+			/* only HTTP 2xx responses are accepted */
+			if(curlres >= 300 || curlres < 100) {
+				LM_ERR("http GET failed with error: %d\n", curlres);
+				/* free memory */
+				lost_delete_geoheader_list(geolist);
+				lost_free_string(&pidfhdr);
+				lost_free_string(&geohdr);
+				goto err;
+			}
+
+			pidf.s = pidfhdr.s;
+			pidf.len = pidfhdr.len;
+
+			if(pidf.len > 0) {
+
+				LM_DBG("LbR pidf-lo: [%.*s]\n", pidf.len, pidf.s);
+
+				/* parse the pidf-lo */
+				loc = lost_parse_pidf(pidf, urn);
+				/* free memory */
+				pidf.s = NULL;
+				pidf.len = 0;
+			} else {
+				LM_WARN("dereferencing location failed\n");
+			}
 		}
-	} else {
-		LM_ERR("findServiceResponse or presence element not found in "
-			   "[%.*s]\n",
-				pidf.len, pidf.s);
-		goto err;
+		/* free memory */
+		lost_delete_geoheader_list(geolist);
+		lost_free_string(&geohdr);
+		lost_free_string(&pidfhdr);
 	}
 
-	/* free memory */
-	pidf.s = NULL;
-	pidf.len = 0;
-	lost_free_string(&pidfhdr);
+	if(loc == NULL) {
+		LM_ERR("location object not found\n");
+		goto err;
+	}
 
 	/* check if connection exits */
 	if(httpapi.http_connection_exists(&con) == 0) {
@@ -634,22 +665,20 @@ int lost_function(struct sip_msg *_m, char *_con, char *_uri, char *_name,
 		goto err;
 	}
 	/* assemble findService request */
-	res.s = lost_find_service_request(loc, &res.len);
+	req.s = lost_find_service_request(loc, &req.len);
 	/* free memory */
 	lost_free_loc(loc);
 	loc = NULL;
-	xmlFreeDoc(doc);
-	doc = NULL;
 
-	if(res.len == 0) {
+	if(req.len == 0) {
 		LM_ERR("lost request failed\n");
 		goto err;
 	}
 
-	LM_DBG("findService request: [%.*s]\n", res.len, res.s);
+	LM_DBG("findService request: [%.*s]\n", req.len, req.s);
 
 	/* send findService request to mapping server - HTTP POST */
-	curlres = httpapi.http_connect(_m, &con, NULL, &ret, mtlost, &res);
+	curlres = httpapi.http_connect(_m, &con, NULL, &ret, mtlost, &req);
 	/* only HTTP 2xx responses are accepted */
 	if(curlres >= 300 || curlres < 100) {
 		LM_ERR("[%.*s] failed with error: %d\n", con.len, con.s, curlres);
@@ -660,7 +689,7 @@ int lost_function(struct sip_msg *_m, char *_con, char *_uri, char *_name,
 	LM_DBG("[%.*s] returned: %d\n", con.len, con.s, curlres);
 
 	/* free memory */
-	lost_free_string(&res);
+	lost_free_string(&req);
 
 	if(ret.len == 0) {
 		LM_ERR("findService request failed\n");
@@ -700,7 +729,7 @@ int lost_function(struct sip_msg *_m, char *_con, char *_uri, char *_name,
 			lost_free_string(&ret);
 			goto err;
 		}
-		LM_INFO("### LOST uri [%.*s]\n", uri.len, uri.s);
+		LM_INFO("### LOST uri\t[%.*s]\n", uri.len, uri.s);
 		/* get the displayName element */
 		name.s = lost_get_content(root, name_element, &name.len);
 		if(name.len == 0) {
@@ -709,7 +738,7 @@ int lost_function(struct sip_msg *_m, char *_con, char *_uri, char *_name,
 			lost_free_string(&ret);
 			goto err;
 		}
-		LM_INFO("### LOST name [%.*s]\n", name.len, name.s);
+		LM_INFO("### LOST din\t[%.*s]\n", name.len, name.s);
 	} else if((!xmlStrcmp(root->name, (const xmlChar *)"errors"))) {
 
 		LM_DBG("findService error response received\n");
@@ -733,8 +762,6 @@ int lost_function(struct sip_msg *_m, char *_con, char *_uri, char *_name,
 
 	/* free memory */
 	lost_free_string(&ret);
-	xmlFreeDoc(doc);
-	doc = NULL;
 
 	/* set writable pvars */
 	pvname.rs = name;
@@ -764,11 +791,13 @@ int lost_function(struct sip_msg *_m, char *_con, char *_uri, char *_name,
 	return (err.len > 0) ? LOST_SERVER_ERROR : LOST_SUCCESS;
 
 err:
-	if(loc != NULL) {
-		lost_free_loc(loc);
-	}
+	/* free memory */
 	if(doc != NULL) {
 		xmlFreeDoc(doc);
+		doc = NULL;
+	}
+	if(loc != NULL) {
+		lost_free_loc(loc);
 	}
 
 	return LOST_CLIENT_ERROR;

+ 8 - 6
src/modules/lost/lost.c

@@ -49,6 +49,10 @@ MODULE_VERSION
 /* Module parameter variables */
 httpc_api_t httpapi;
 
+/* lost: any (0), cid (1), http (2) or https (3) (default: 0) */
+int lost_geoloc_type = 0;
+/* lost: Geolocation header value order: first (0) or last (1) (default: 0) */
+int lost_geoloc_order = 0;
 /* held request: response time (default: 0 = no timeout) */
 int held_resp_time = 0;
 /* held request: exact is true (1) or false (0) (default: false) */
@@ -96,16 +100,14 @@ static cmd_export_t cmds[] = {
 		{"lost_query", (cmd_function)w_lost_query_all, 6, fixup_lost_query_all,
 				fixup_free_lost_query_all,
 				REQUEST_ROUTE | FAILURE_ROUTE | BRANCH_ROUTE},
-		{0, 0, 0, 0, 0, 0}
-};
+		{0, 0, 0, 0, 0, 0}};
 
 /* Exported parameters */
-static param_export_t params[] = {
-		{"exact_type", PARAM_INT, &held_exact_type},
+static param_export_t params[] = {{"exact_type", PARAM_INT, &held_exact_type},
 		{"response_time", PARAM_INT, &held_resp_time},
 		{"location_type", PARAM_STR, &held_loc_type},
-		{0, 0, 0}
-};
+		{"geoheader_type", PARAM_INT, &lost_geoloc_type},
+		{"geoheader_order", PARAM_INT, &lost_geoloc_order}, {0, 0, 0}};
 
 /* Module interface */
 struct module_exports exports = {

+ 2 - 2
src/modules/lost/pidf.c

@@ -98,8 +98,8 @@ xmlNodePtr xmlNodeGetNodeByName(
 	while(cur) {
 		xmlNodePtr match = NULL;
 		if(xmlStrcasecmp(cur->name, (unsigned char *)name) == 0) {
-			if(!ns || (cur->ns &&
-				xmlStrcasecmp(cur->ns->prefix, (unsigned char *)ns) == 0))
+			if(!ns || (cur->ns && xmlStrcasecmp(cur->ns->prefix,
+									(unsigned char *)ns) == 0))
 				return cur;
 		}
 		match = xmlNodeGetNodeByName(cur->children, name, ns);

+ 1 - 1
src/modules/lost/pidf.h

@@ -39,7 +39,7 @@
 #include <libxml/xpath.h>
 #include <libxml/xpathInternals.h>
 
-#define BUFSIZE 128	/* temporary buffer to hold geolocation */
+#define BUFSIZE 128	   /* temporary buffer to hold geolocation */
 #define RANDSTRSIZE 16 /* temporary id in a findService request */
 
 xmlNodePtr xmlNodeGetNodeByName(

+ 301 - 3
src/modules/lost/utilities.c

@@ -380,7 +380,7 @@ char *lost_get_geolocation_header(struct sip_msg *msg, int *lgth)
 				&& (hf->name.len == LOST_GEOLOC_HEADER_SIZE - 2)) {
 			/* possible hit */
 			if(strncasecmp(hf->name.s, LOST_GEOLOC_HEADER,
-					   LOST_GEOLOC_HEADER_SIZE)	== 0) {
+								LOST_GEOLOC_HEADER_SIZE) == 0) {
 
 				res = (char *)pkg_malloc((hf->body.len + 1) * sizeof(char));
 				if(res == NULL) {
@@ -427,7 +427,7 @@ char *lost_get_pai_header(struct sip_msg *msg, int *lgth)
 				&& (hf->name.len == LOST_PAI_HEADER_SIZE - 2)) {
 			/* possible hit */
 			if(strncasecmp(hf->name.s, LOST_PAI_HEADER,
-						LOST_PAI_HEADER_SIZE) == 0) {
+								LOST_PAI_HEADER_SIZE) == 0) {
 
 				LM_DBG("P-A-I body:  [%.*s]\n", hf->body.len, hf->body.s);
 
@@ -512,6 +512,303 @@ char *lost_get_from_header(struct sip_msg *msg, int *lgth)
 	return res;
 }
 
+/*
+ * lost_delete_geoheader_list(list)
+ * removes geoheader list from private memory
+ */
+void lost_delete_geoheader_list(p_geolist_t list)
+{
+
+	p_geolist_t curr;
+
+	while((curr = list) != NULL) {
+		list = curr->next;
+		if(curr->value != NULL) {
+			pkg_free(curr->value);
+		}
+		if(curr->param != NULL) {
+			pkg_free(curr->param);
+		}
+		pkg_free(curr);
+	}
+
+	list = NULL;
+
+	return;
+}
+
+/*
+ * lost_get_geoheader_value(list, type, rtype)
+ * returns geoheader value and type (rtype) of given type
+ */
+char *lost_get_geoheader_value(p_geolist_t list, geotype_t type, int *rtype)
+{
+
+	p_geolist_t head = list;
+	char *value = NULL;
+
+	if(head == NULL) {
+		return value;
+	}
+
+	/* type is not important, take first element value and type */
+	if((type == ANY) || (type == UNKNOWN)) {
+		*rtype = head->type;
+		return head->value;
+	}
+
+	/* take first element value and type of given type */
+	while(head) {
+		if(type == head->type) {
+			value = head->value;
+			*rtype = head->type;
+			break;
+		}
+		head = head->next;
+	}
+
+	return value;
+}
+
+
+/*
+ * lost_reverse_geoheader_list(list)
+ * reverses list order
+ */
+void lost_reverse_geoheader_list(p_geolist_t *head)
+{
+
+	p_geolist_t prev = NULL;
+	p_geolist_t next = NULL;
+	p_geolist_t current = *head;
+
+	while(current != NULL) {
+		next = current->next;
+		current->next = prev;
+		prev = current;
+		current = next;
+	}
+
+	*head = prev;
+}
+
+/*
+ * lost_copy_geoheader_value(src, len)
+ * returns a header vaule string (src to src + len) allocated in private memory
+ */
+char *lost_copy_geoheader_value(char *src, int len)
+{
+
+	char *res = NULL;
+
+	res = (char *)pkg_malloc((len + 1) * sizeof(char));
+	if(res == NULL) {
+		LM_ERR("no more private memory\n");
+		return res;
+	} else {
+		memset(res, 0, len + 1);
+		memcpy(res, src, len + 1);
+		res[len] = '\0';
+	}
+
+	return res;
+}
+
+/*
+ * lost_new_geoheader_list(hdr, items)
+ * searches and parses Geolocation header and returns a list
+ * allocated in private memory and an item count
+ */
+p_geolist_t lost_new_geoheader_list(str hdr, int *items)
+{
+
+	char *search = NULL;
+	char *cidptr = NULL;
+	char *urlptr = NULL;
+	char *ptr = NULL;
+
+	int count = 0;
+	int len = 0;
+
+	p_geolist_t list = NULL;
+	p_geolist_t new = NULL;
+
+	LM_DBG("parsing geolocation header value ...\n");
+
+	/* search the complete header field */
+	search = hdr.s;
+	for(int i = 0; i < hdr.len; i++) {
+		/* check for cid content */
+		/* <cid:x> might be the shortest */
+		if(strlen(search) > 6) {
+			if((*(search + 0) == '<')
+					&& ((*(search + 1) == 'c') || (*(search + 1) == 'C'))
+					&& ((*(search + 2) == 'i') || (*(search + 2) == 'I'))
+					&& ((*(search + 3) == 'd') || (*(search + 3) == 'D'))
+					&& (*(search + 4) == ':')) {
+				cidptr = search + 4;
+				*cidptr = LAQUOT;
+				ptr = cidptr;
+				len = 1;
+				while(*(ptr + len) != '>') {
+					if((len == strlen(ptr)) || (*(ptr + len) == '<')) {
+						LM_WARN("invalid cid: [%.*s]\n", hdr.len, hdr.s);
+						break;
+					}
+					len++;
+				}
+				if((*(ptr + len) == '>') && (len > 6)) {
+					new = (p_geolist_t)pkg_malloc(sizeof(s_geolist_t));
+					if(new == NULL) {
+						LM_ERR("no more private memory\n");
+					} else {
+
+						LM_DBG("\t[%.*s]\n", len + 1, cidptr);
+
+						new->value = lost_copy_geoheader_value(cidptr, len + 1);
+						new->param = NULL;
+						new->type = CID;
+						new->next = list;
+						list = new;
+						count++;
+
+						LM_DBG("adding cid [%s]\n", new->value);
+					}
+				} else {
+					LM_WARN("invalid value: [%.*s]\n", hdr.len, hdr.s);
+				}
+			}
+		}
+
+		/* check for http(s) content */
+		/* <http://goo.gl/> might be the shortest */
+		if(strlen(search) > 10) {
+			if((*(search + 0) == '<')
+					&& ((*(search + 1) == 'h') || (*(search + 1) == 'H'))
+					&& ((*(search + 2) == 't') || (*(search + 2) == 'T'))
+					&& ((*(search + 3) == 't') || (*(search + 3) == 'T'))
+					&& ((*(search + 4) == 'p') || (*(search + 4) == 'P'))) {
+				urlptr = search + 1;
+				ptr = urlptr;
+				len = 0;
+				while(*(ptr + len) != '>') {
+					if((len == strlen(ptr)) || (*(ptr + len) == '<')) {
+						LM_WARN("invalid url: [%.*s]\n", hdr.len, hdr.s);
+						break;
+					}
+					len++;
+				}
+				if((*(ptr + len) == '>') && (len > 10)) {
+					new = (p_geolist_t)pkg_malloc(sizeof(s_geolist_t));
+					if(new == NULL) {
+						LM_ERR("no more private memory\n");
+					} else {
+
+						LM_DBG("\t[%.*s]\n", len, urlptr);
+
+						new->value = lost_copy_geoheader_value(urlptr, len);
+						new->param = NULL;
+						if(*(search + 5) == ':') {
+
+							LM_DBG("adding http url [%s]\n", new->value);
+
+							new->type = HTTP;
+						} else if(((*(search + 5) == 's')
+										  || (*(search + 5) == 'S'))
+								  && (*(search + 6) == ':')) {
+
+							LM_DBG("adding https url [%s]\n", new->value);
+
+							new->type = HTTPS;
+						}
+						new->next = list;
+						list = new;
+						count++;
+					}
+				} else {
+					LM_WARN("invalid value: [%.*s]\n", hdr.len, hdr.s);
+				}
+			}
+		}
+		search++;
+	}
+
+	*items = count;
+
+	return list;
+}
+
+/*
+ * lost_parse_pidf(pidf, urn)
+ * parses pidf and returns a new location object
+ */
+p_loc_t lost_parse_pidf(str pidf, str urn)
+{
+
+	p_loc_t loc = NULL;
+
+	xmlDocPtr doc = NULL;
+	xmlNodePtr root = NULL;
+
+	/* read and parse pidf-lo */
+	doc = xmlReadMemory(pidf.s, pidf.len, 0, NULL,
+			XML_PARSE_NOBLANKS | XML_PARSE_NONET | XML_PARSE_NOCDATA);
+
+	if(doc == NULL) {
+		LM_WARN("invalid xml (pidf-lo): [%.*s]\n", pidf.len, pidf.s);
+		doc = xmlRecoverMemory(pidf.s, pidf.len);
+		if(doc == NULL) {
+			LM_ERR("xml (pidf-lo) recovery failed on: [%.*s]\n", pidf.len,
+					pidf.s);
+			goto err;
+		}
+
+		LM_DBG("xml (pidf-lo) recovered\n");
+	}
+
+	root = xmlDocGetRootElement(doc);
+	if(root == NULL) {
+		LM_ERR("empty pidf-lo document\n");
+		goto err;
+	}
+	if((!xmlStrcmp(root->name, (const xmlChar *)"presence"))
+			|| (!xmlStrcmp(root->name, (const xmlChar *)"locationResponse"))) {
+		/* get the geolocation: point or circle, urn, ... */
+		loc = lost_new_loc(urn);
+		if(loc == NULL) {
+			LM_ERR("location object allocation failed\n");
+			goto err;
+		}
+		if(lost_parse_location_info(root, loc) < 0) {
+			LM_ERR("location element not found\n");
+			goto err;
+		}
+	} else {
+		LM_ERR("findServiceResponse or presence element not found in "
+			   "[%.*s]\n",
+				pidf.len, pidf.s);
+		goto err;
+	}
+
+	/* free memory */
+	xmlFreeDoc(doc);
+	doc = NULL;
+
+	return loc;
+
+err:
+	LM_ERR("pidflo parsing error\n");
+	/* free memory */
+	if(doc != NULL) {
+		xmlFreeDoc(doc);
+		doc = NULL;
+	}
+	if(loc != NULL) {
+		lost_free_loc(loc);
+	}
+	return NULL;
+}
+
 /*
  * lost_parse_geo(node, loc)
  * parses locationResponse (pos|circle) and writes 
@@ -586,6 +883,7 @@ err:
 	LM_ERR("no more private memory\n");
 	return -1;
 }
+
 /*
  * lost_xpath_location(doc, path, loc)
  * performs xpath expression on locationResponse and writes 
@@ -733,7 +1031,7 @@ int lost_xpath_location(xmlDocPtr doc, char *path, p_loc_t loc)
 					memset(ptr, 0, buffersize);
 					memcpy(ptr, (char *)(xmlbuff + remove), buffersize);
 					ptr[buffersize] = '\0';
-					
+
 					/* trim the result */
 					tmp = lost_trim_content(ptr, &len);
 

+ 40 - 15
src/modules/lost/utilities.h

@@ -32,6 +32,8 @@
 #ifndef LOST_UTILITIES_H
 #define LOST_UTILITIES_H
 
+#define LAQUOT '<'
+
 #define LOST_GEOLOC_HEADER "Geolocation: "
 #define LOST_GEOLOC_HEADER_SIZE strlen(LOST_GEOLOC_HEADER)
 #define LOST_PAI_HEADER "P-Asserted-Identity: "
@@ -67,35 +69,54 @@
 #define HELD_EXACT_TRUE 1
 #define HELD_EXACT_FALSE 0
 
-#define BUFSIZE 128	/* temporary buffer to hold geolocation */
+#define BUFSIZE 128	   /* temporary buffer to hold geolocation */
 #define RANDSTRSIZE 16 /* temporary id in a findService request */
 
 typedef struct LOC
 {
-	char *identity;		/* location idendity (findServiceRequest) */
-	char *urn;			/* service URN (findServiceRequest) */ 
-	char *xpath;		/* civic address (findServiceRequest) */
-	char *geodetic;		/* geodetic location (findServiceRequest) */
-	char *longitude;	/* geo longitude */
-	char *latitude;		/* geo latitude */
-	char *profile;		/* location profile (findServiceRequest) */
-	int radius;			/* geo radius (findServiceRequest) */
-	int recursive;		/* recursion true|false (findServiceRequest)*/
-	int boundary;       /* boundary ref|value (findServiceRequest)*/
+	char *identity;	 /* location idendity (findServiceRequest) */
+	char *urn;		 /* service URN (findServiceRequest) */
+	char *xpath;	 /* civic address (findServiceRequest) */
+	char *geodetic;	 /* geodetic location (findServiceRequest) */
+	char *longitude; /* geo longitude */
+	char *latitude;	 /* geo latitude */
+	char *profile;	 /* location profile (findServiceRequest) */
+	int radius;		 /* geo radius (findServiceRequest) */
+	int recursive;	 /* recursion true|false (findServiceRequest)*/
+	int boundary;	 /* boundary ref|value (findServiceRequest)*/
 } s_loc_t, *p_loc_t;
 
 typedef struct HELD
 {
-	char *identity;		/* location idendity (locationRequest) */
-	char *type;			/* location type (locationRequest) */ 
-	int time;			/* response time (locationRequest) */
-	int exact;			/* exact true|false (locationRequest)*/
+	char *identity; /* location idendity (locationRequest) */
+	char *type;		/* location type (locationRequest) */
+	int time;		/* response time (locationRequest) */
+	int exact;		/* exact true|false (locationRequest)*/
 } s_held_t, *p_held_t;
 
+typedef enum GEOTYPE
+{
+	ANY,		 /* any type */
+	CID,		 /* content-indirection */
+	HTTP,		 /* http uri */
+	HTTPS,		 /* https uri */
+	UNKNOWN = -1 /* unknown */
+} geotype_t;
+
+typedef struct GEOLIST
+{
+	char *value;	/* geolocation header value */
+	char *param;	/* value parameter */
+	geotype_t type; /* type */
+	struct GEOLIST *next;
+} s_geolist_t, *p_geolist_t;
+
 void lost_rand_str(char *, size_t);
 void lost_free_loc(p_loc_t);
 void lost_free_held(p_held_t);
 void lost_free_string(str *);
+void lost_reverse_geoheader_list(p_geolist_t *);
+void lost_delete_geoheader_list(p_geolist_t);
 
 int lost_parse_location_info(xmlNodePtr, p_loc_t);
 int lost_xpath_location(xmlDocPtr, char *, p_loc_t);
@@ -110,8 +131,12 @@ char *lost_get_from_header(struct sip_msg *, int *);
 char *lost_get_pai_header(struct sip_msg *, int *);
 char *lost_get_childname(xmlNodePtr, const char *, int *);
 char *lost_trim_content(char *, int *);
+char *lost_copy_geoheader_value(char *, int);
+char *lost_get_geoheader_value(p_geolist_t, geotype_t, int *);
 
 p_loc_t lost_new_loc(str);
+p_loc_t lost_parse_pidf(str, str);
 p_held_t lost_new_held(str, str, int, int);
+p_geolist_t lost_new_geoheader_list(str, int *);
 
 #endif