Browse code

module_k/xcap_server: Added support HTTP 1.1 pre-conditions (If-Match: and If-None-Match:)

- Also fixed the ETag: header send by xcap_server as it should be a
quoted-string (RFC 2616 section 3.11 and 14.19).
- If-Match (RFC 2616 section 14.24) and If-None-Match (RFC 2616
section 14.26) are supported for the DELETE, GET, and PUT HTTP
requests.

This feature is useful as it enables clients to cache large XML
documents (user-profiles/avatars/resource-lists) and only have to
download them when they have actually changed (using If-None-Match:)

If-Match: can help with the race conditions present when you have
multiple simultaneous logins by helping to prevent clients from
overwriting changes made by other clients.

pd authored on 29/07/2011 10:56:14
Showing 2 changed files
... ...
@@ -18,5 +18,6 @@ DEFS+=-DOPENSER_MOD_INTERFACE
18 18
 
19 19
 SERLIBPATH=../../lib
20 20
 SER_LIBS+=$(SERLIBPATH)/srdb1/srdb1
21
+SER_LIBS+=$(SERLIBPATH)/kcore/kcore
21 22
 
22 23
 include ../../Makefile.modules
... ...
@@ -44,6 +44,7 @@
44 44
 #include "../../parser/parse_uri.h"
45 45
 #include "../../modules_k/xcap_client/xcap_callbacks.h"
46 46
 #include "../../modules/sl/sl.h"
47
+#include "../../lib/kcore/cmpapi.h"
47 48
 
48 49
 #include "xcap_misc.h"
49 50
 
... ...
@@ -54,8 +55,10 @@ MODULE_VERSION
54 54
 
55 55
 static int xcaps_put_db(str* user, str *domain, xcap_uri_t *xuri, str *etag,
56 56
 		str* doc);
57
-static int xcaps_get_db(str* user, str *domain, xcap_uri_t *xuri,
58
-		str *etag, str *doc);
57
+static int xcaps_get_db_doc(str* user, str *domain, xcap_uri_t *xuri,
58
+		str *doc);
59
+static int xcaps_get_db_etag(str* user, str *domain, xcap_uri_t *xuri,
60
+		str *etag);
59 61
 static int xcaps_del_db(str* user, str *domain, xcap_uri_t *xuri);
60 62
 
61 63
 static int w_xcaps_put(sip_msg_t* msg, char* puri, char* ppath,
... ...
@@ -63,6 +66,8 @@ static int w_xcaps_put(sip_msg_t* msg, char* puri, char* ppath,
63 63
 static int w_xcaps_get(sip_msg_t* msg, char* puri, char* ppath);
64 64
 static int w_xcaps_del(sip_msg_t* msg, char* puri, char* ppath);
65 65
 static int fixup_xcaps_put(void** param, int param_no);
66
+static int check_preconditions(sip_msg_t *msg, str etag_hdr);
67
+static int check_match_header(str body, str *etag);
66 68
 
67 69
 static int mod_init(void);
68 70
 static int child_init(int rank);
... ...
@@ -402,6 +407,8 @@ static str xcaps_str_empty      = {"", 0};
402 402
 static str xcaps_str_ok         = {"OK", 2};
403 403
 static str xcaps_str_srverr     = {"Server error", 12};
404 404
 static str xcaps_str_notfound   = {"Not found", 9};
405
+static str xcaps_str_precon		= {"Precondition Failed", 19};
406
+static str xcaps_str_notmod		= {"Not Modified", 12};
405 407
 static str xcaps_str_appxml     = {"application/xml", 15};
406 408
 static str xcaps_str_apprlxml   = {"application/resource-lists+xml", 30};
407 409
 static str xcaps_str_apprsxml   = {"application/rls-services+xml", 28};
... ...
@@ -499,6 +506,15 @@ static int w_xcaps_put(sip_msg_t* msg, char* puri, char* ppath,
499 499
 				path.len, path.s);
500 500
 		goto error;
501 501
 	}
502
+
503
+	xcaps_get_db_etag(&turi.user, &turi.host, &xuri, &etag);
504
+	if(check_preconditions(msg, etag)!=1)
505
+	{
506
+		xcaps_send_reply(msg, 412, &xcaps_str_precon, &xcaps_str_empty,
507
+				&xcaps_str_empty, &xcaps_str_empty);
508
+		return -2;
509
+	}
510
+
502 511
 	if(xuri.nss==NULL || xuri.node.len<=0)
503 512
 	{
504 513
 		/* full document upload
... ...
@@ -514,7 +530,7 @@ static int w_xcaps_put(sip_msg_t* msg, char* puri, char* ppath,
514 514
 		/* partial document upload
515 515
 		 *   - fetch, update, delete and store
516 516
 		 */
517
-		if(xcaps_get_db(&turi.user, &turi.host, &xuri, &etag, &tbuf)<0)
517
+		if(xcaps_get_db_doc(&turi.user, &turi.host, &xuri, &tbuf)<0)
518 518
 		{
519 519
 			LM_ERR("could not fetch xcap document\n");
520 520
 			goto error;
... ...
@@ -553,8 +569,8 @@ static int w_xcaps_put(sip_msg_t* msg, char* puri, char* ppath,
553 553
 		LM_ERR("could not generate etag\n");
554 554
 		goto error;
555 555
 	}
556
-	etag.s = etag_hdr.s + 6; /* 'ETag: ' */
557
-	etag.len = etag_hdr.len - 8; /* 'ETag: '  '\r\n' */
556
+	etag.s = etag_hdr.s + 7; /* 'ETag: "' */
557
+	etag.len = etag_hdr.len - 10; /* 'ETag: "  "\r\n' */
558 558
 	if(xcaps_put_db(&turi.user, &turi.host,
559 559
 				&xuri, &etag, &body)<0)
560 560
 	{
... ...
@@ -578,20 +594,17 @@ error:
578 578
 /**
579 579
  *
580 580
  */
581
-static int xcaps_get_db(str* user, str *domain, xcap_uri_t *xuri,
582
-		str *etag, str *doc)
581
+static int xcaps_get_db_doc(str* user, str *domain, xcap_uri_t *xuri, str *doc)
583 582
 {
584
-	db_key_t qcols[4];
585
-	db_val_t qvals[4];
583
+	db_key_t qcols[3];
584
+	db_val_t qvals[3];
586 585
 	int ncols = 0;
587
-	db_key_t rcols[4];
586
+	db_key_t rcols[3];
588 587
 	int nrcols = 0;
589 588
 	db1_res_t* db_res = NULL;
590 589
 	str s;
591 590
 
592
-	/* returned cols from xcap table*/
593
-	rcols[nrcols] = &str_etag_col;
594
-	nrcols++;
591
+	/* returned cols from table xcap*/
595 592
 	rcols[nrcols] = &str_doc_col;
596 593
 	nrcols++;
597 594
 
... ...
@@ -632,7 +645,8 @@ static int xcaps_get_db(str* user, str *domain, xcap_uri_t *xuri,
632 632
 		LM_DBG("no document\n");
633 633
 		goto notfound;
634 634
 	}
635
-	/* etag */
635
+
636
+	/* doc */
636 637
 	switch(RES_ROWS(db_res)[0].values[0].type)
637 638
 	{
638 639
 		case DB1_STRING:
... ...
@@ -653,39 +667,100 @@ static int xcaps_get_db(str* user, str *domain, xcap_uri_t *xuri,
653 653
 	}
654 654
 	if(s.len==0)
655 655
 	{
656
-		LM_ERR("no etag in db record\n");
656
+		LM_ERR("no xcap doc in db record\n");
657 657
 		goto error;
658 658
 	}
659
-	etag->len = snprintf(xcaps_etag_buf, XCAPS_ETAG_SIZE,
660
-			"ETag: %.*s\r\n", s.len, s.s);
661
-	if(etag->len < 0)
659
+	if(s.len>xcaps_buf.len-1)
662 660
 	{
663
-		LM_ERR("error printing etag hdr\n ");
661
+		LM_ERR("xcap doc buffer overflow\n");
664 662
 		goto error;
665 663
 	}
666
-	if(etag->len >= XCAPS_ETAG_SIZE)
664
+	doc->len = s.len;
665
+	doc->s = xcaps_buf.s;
666
+	memcpy(doc->s, s.s, s.len);
667
+	doc->s[doc->len] = '\0';
668
+
669
+	xcaps_dbf.free_result(xcaps_db, db_res);
670
+	return 0;
671
+
672
+notfound:
673
+	xcaps_dbf.free_result(xcaps_db, db_res);
674
+	return 1;
675
+
676
+error:
677
+	if(db_res!=NULL)
678
+		xcaps_dbf.free_result(xcaps_db, db_res);
679
+	return -1;
680
+}
681
+
682
+/**
683
+ *
684
+ */
685
+static int xcaps_get_db_etag(str* user, str *domain, xcap_uri_t *xuri, str *etag)
686
+{
687
+	db_key_t qcols[3];
688
+	db_val_t qvals[3];
689
+	int ncols = 0;
690
+	db_key_t rcols[3];
691
+	int nrcols = 0;
692
+	db1_res_t* db_res = NULL;
693
+	str s;
694
+
695
+	/* returned cols from xcap table*/
696
+	rcols[nrcols] = &str_etag_col;
697
+	nrcols++;
698
+
699
+	/* query cols in xcap table*/
700
+	qcols[ncols] = &str_username_col;
701
+	qvals[ncols].type = DB1_STR;
702
+	qvals[ncols].nul = 0;
703
+	qvals[ncols].val.str_val = *user;
704
+	ncols++;
705
+
706
+	qcols[ncols] = &str_domain_col;
707
+	qvals[ncols].type = DB1_STR;
708
+	qvals[ncols].nul = 0;
709
+	qvals[ncols].val.str_val = *domain;
710
+	ncols++;
711
+
712
+	qcols[ncols] = &str_doc_uri_col;
713
+	qvals[ncols].type = DB1_STR;
714
+	qvals[ncols].nul = 0;
715
+	qvals[ncols].val.str_val= xuri->adoc;
716
+	ncols++;
717
+
718
+	if (xcaps_dbf.use_table(xcaps_db, &xcaps_db_table) < 0) 
667 719
 	{
668
-		LM_ERR("etag buffer overflow\n");
720
+		LM_ERR("in use_table-[table]= %.*s\n", xcaps_db_table.len,
721
+				xcaps_db_table.s);
669 722
 		goto error;
670 723
 	}
671 724
 
672
-	etag->s = xcaps_etag_buf;
673
-	etag->s[etag->len] = '\0';
674
-
675
-	/* doc */
676
-	switch(RES_ROWS(db_res)[0].values[1].type)
725
+	if(xcaps_dbf.query(xcaps_db, qcols, NULL, qvals, rcols,
726
+				ncols, nrcols, NULL, &db_res)< 0)
727
+	{
728
+		LM_ERR("in sql query\n");
729
+		goto error;
730
+	}
731
+	if (RES_ROW_N(db_res) <= 0)
732
+	{
733
+		LM_DBG("no document\n");
734
+		goto notfound;
735
+	}
736
+	/* etag */
737
+	switch(RES_ROWS(db_res)[0].values[0].type)
677 738
 	{
678 739
 		case DB1_STRING:
679
-			s.s=(char*)RES_ROWS(db_res)[0].values[1].val.string_val;
740
+			s.s=(char*)RES_ROWS(db_res)[0].values[0].val.string_val;
680 741
 			s.len=strlen(s.s);
681 742
 		break;
682 743
 		case DB1_STR:
683
-			s.len=RES_ROWS(db_res)[0].values[1].val.str_val.len;
684
-			s.s=(char*)RES_ROWS(db_res)[0].values[1].val.str_val.s;
744
+			s.len=RES_ROWS(db_res)[0].values[0].val.str_val.len;
745
+			s.s=(char*)RES_ROWS(db_res)[0].values[0].val.str_val.s;
685 746
 		break;
686 747
 		case DB1_BLOB:
687
-			s.len=RES_ROWS(db_res)[0].values[1].val.blob_val.len;
688
-			s.s=(char*)RES_ROWS(db_res)[0].values[1].val.blob_val.s;
748
+			s.len=RES_ROWS(db_res)[0].values[0].val.blob_val.len;
749
+			s.s=(char*)RES_ROWS(db_res)[0].values[0].val.blob_val.s;
689 750
 		break;
690 751
 		default:
691 752
 			s.len=0;
... ...
@@ -693,18 +768,24 @@ static int xcaps_get_db(str* user, str *domain, xcap_uri_t *xuri,
693 693
 	}
694 694
 	if(s.len==0)
695 695
 	{
696
-		LM_ERR("no xcap doc in db record\n");
696
+		LM_ERR("no etag in db record\n");
697 697
 		goto error;
698 698
 	}
699
-	if(s.len>xcaps_buf.len-1)
699
+	etag->len = snprintf(xcaps_etag_buf, XCAPS_ETAG_SIZE,
700
+			"ETag: \"%.*s\"\r\n", s.len, s.s);
701
+	if(etag->len < 0)
700 702
 	{
701
-		LM_ERR("xcap doc buffer overflow\n");
703
+		LM_ERR("error printing etag hdr\n ");
704
+		goto error;
705
+	}
706
+	if(etag->len >= XCAPS_ETAG_SIZE)
707
+	{
708
+		LM_ERR("etag buffer overflow\n");
702 709
 		goto error;
703 710
 	}
704
-	doc->len = s.len;
705
-	doc->s = xcaps_buf.s;
706
-	memcpy(doc->s, s.s, s.len);
707
-	doc->s[doc->len] = '\0';
711
+
712
+	etag->s = xcaps_etag_buf;
713
+	etag->s[etag->len] = '\0';
708 714
 
709 715
 	xcaps_dbf.free_result(xcaps_db, db_res);
710 716
 	return 0;
... ...
@@ -719,7 +800,6 @@ error:
719 719
 	return -1;
720 720
 }
721 721
 
722
-
723 722
 /**
724 723
  *
725 724
  */
... ...
@@ -775,7 +855,31 @@ static int w_xcaps_get(sip_msg_t* msg, char* puri, char* ppath)
775 775
 		goto error;
776 776
 	}
777 777
 
778
-	if((ret=xcaps_get_db(&turi.user, &turi.host, &xuri, &etag, &body))<0)
778
+	if((ret=xcaps_get_db_etag(&turi.user, &turi.host, &xuri, &etag))<0)
779
+	{ 
780
+		LM_ERR("could not fetch etag for xcap document\n");
781
+		goto error;
782
+	}
783
+	if (ret==1)
784
+	{
785
+		/* doc not found */
786
+		xcaps_send_reply(msg, 404, &xcaps_str_notfound, &xcaps_str_empty,
787
+				&xcaps_str_empty, &xcaps_str_empty);
788
+		return 1;
789
+	}
790
+	
791
+	if((ret=check_preconditions(msg, etag))==-1)
792
+	{
793
+		xcaps_send_reply(msg, 412, &xcaps_str_precon, &xcaps_str_empty,
794
+				&xcaps_str_empty, &xcaps_str_empty);
795
+		return -2;
796
+	} else if (ret==-2) {
797
+		xcaps_send_reply(msg, 304, &xcaps_str_notmod, &xcaps_str_empty,
798
+				&xcaps_str_empty, &xcaps_str_empty);
799
+		return -2;
800
+	}
801
+
802
+	if((ret=xcaps_get_db_doc(&turi.user, &turi.host, &xuri, &body))<0)
779 803
 	{
780 804
 		LM_ERR("could not fetch xcap document\n");
781 805
 		goto error;
... ...
@@ -912,6 +1016,18 @@ static int w_xcaps_del(sip_msg_t* msg, char* puri, char* ppath)
912 912
 		goto error;
913 913
 	}
914 914
 
915
+	if(xcaps_get_db_etag(&turi.user, &turi.host, &xuri, &etag)<0)
916
+	{ 
917
+		LM_ERR("could not fetch etag for xcap document\n");
918
+		goto error;
919
+	}
920
+
921
+	if(check_preconditions(msg, etag)!=1)
922
+	{
923
+		xcaps_send_reply(msg, 412, &xcaps_str_precon, &xcaps_str_empty,
924
+				&xcaps_str_empty, &xcaps_str_empty);
925
+		return -2;
926
+	}
915 927
 
916 928
 	if(xuri.nss==NULL)
917 929
 	{
... ...
@@ -925,7 +1041,7 @@ static int w_xcaps_del(sip_msg_t* msg, char* puri, char* ppath)
925 925
 				&xcaps_str_empty, &xcaps_str_empty);
926 926
 	} else {
927 927
 		/* delete element */
928
-		if(xcaps_get_db(&turi.user, &turi.host, &xuri, &etag, &tbuf)<0)
928
+		if(xcaps_get_db_doc(&turi.user, &turi.host, &xuri, &tbuf)<0)
929 929
 		{
930 930
 			LM_ERR("could not fetch xcap document\n");
931 931
 			goto error;
... ...
@@ -960,8 +1076,8 @@ static int w_xcaps_del(sip_msg_t* msg, char* puri, char* ppath)
960 960
 			LM_ERR("could not generate etag\n");
961 961
 			goto error;
962 962
 		}
963
-		etag.s = etag_hdr.s + 6; /* 'ETag: ' */
964
-		etag.len = etag_hdr.len - 8; /* 'ETag: '  '\r\n' */
963
+		etag.s = etag_hdr.s + 7; /* 'ETag: "' */
964
+		etag.len = etag_hdr.len - 10; /* 'ETag: "  "\r\n' */
965 965
 		if(xcaps_put_db(&turi.user, &turi.host,
966 966
 				&xuri, &etag, &body)<0)
967 967
 		{
... ...
@@ -1085,7 +1201,7 @@ done:
1085 1085
 int xcaps_generate_etag_hdr(str *etag)
1086 1086
 {
1087 1087
 	etag->len = snprintf(xcaps_etag_buf, XCAPS_ETAG_SIZE,
1088
-			"ETag: sr-%d-%d-%d\r\n", xcaps_init_time, my_pid(),
1088
+			"ETag: \"sr-%d-%d-%d\"\r\n", xcaps_init_time, my_pid(),
1089 1089
 			xcaps_etag_counter++);
1090 1090
 	if(etag->len <0)
1091 1091
 	{
... ...
@@ -1127,4 +1243,77 @@ static int fixup_xcaps_put(void** param, int param_no)
1127 1127
 	return 0;
1128 1128
 }
1129 1129
 
1130
+static int check_preconditions(sip_msg_t *msg, str etag_hdr)
1131
+{
1132
+	struct hdr_field* hdr = msg->headers;
1133
+	int ifmatch_found=0;
1134
+	int matched_matched=0;
1135
+	int matched_nonematched=0;
1136
+
1137
+	if (etag_hdr.len > 0)
1138
+	{
1139
+		str etag;
1140
+
1141
+		/* Keep the surrounding "s in the ETag */
1142
+		etag.s = etag_hdr.s + 6; /* 'ETag: ' */
1143
+		etag.len = etag_hdr.len - 8; /* 'ETag: "  "\r\n' */
1130 1144
 
1145
+		while (hdr!=NULL)
1146
+		{
1147
+			if(cmp_hdrname_strzn(&hdr->name, "If-Match", 8)==0)
1148
+			{
1149
+				ifmatch_found = 1;
1150
+				if (check_match_header(hdr->body, &etag)>0)
1151
+					matched_matched = 1;
1152
+			}
1153
+			else if (cmp_hdrname_strzn(&hdr->name, "If-None-Match", 13)==0)
1154
+			{
1155
+				if (check_match_header(hdr->body, &etag)>0)
1156
+					matched_nonematched = 1;
1157
+			}
1158
+			hdr = hdr->next;
1159
+		}
1160
+	} else {
1161
+		while (hdr!=NULL)
1162
+		{
1163
+			if(cmp_hdrname_strzn(&hdr->name, "If-Match", 8)==0)
1164
+				ifmatch_found = 1;
1165
+
1166
+			hdr = hdr->next;
1167
+		}
1168
+	}
1169
+
1170
+	if (ifmatch_found == 1 && matched_matched == 0)
1171
+		return -1;
1172
+	else if (matched_nonematched == 1)
1173
+		return -2;
1174
+	else
1175
+		return 1;
1176
+}
1177
+
1178
+static int check_match_header(str body, str *etag)
1179
+{
1180
+	do
1181
+	{
1182
+		char *start_pos, *end_pos, *old_body_pos;
1183
+		int cur_etag_len;
1184
+
1185
+		if ((start_pos = strchr(body.s, '"')) == NULL)
1186
+			return -1;
1187
+		if ((end_pos = strchr(start_pos + 1, '"')) == NULL)
1188
+			return -1;
1189
+		cur_etag_len = end_pos - start_pos + 1;
1190
+	
1191
+		if (strncmp(start_pos, etag->s, cur_etag_len)==0)
1192
+			return 1;
1193
+		else if (strncmp(start_pos, "\"*\"", cur_etag_len)==0)
1194
+			return 1;
1195
+
1196
+		old_body_pos = body.s;
1197
+		if ((body.s = strchr(end_pos, ',')) == NULL)
1198
+			return -1;
1199
+		body.len -= body.s - old_body_pos;
1200
+	} while (body.len > 0);
1201
+
1202
+	return -1;
1203
+}