[sr-dev] git:master: tcp: read http/1.1 chunked body

Daniel-Constantin Mierla miconda at gmail.com
Mon Aug 30 10:41:37 CEST 2010


Module: sip-router
Branch: master
Commit: b4fbd9f5c164099142c4301660e78a3db63f267c
URL:    http://git.sip-router.org/cgi-bin/gitweb.cgi/sip-router/?a=commit;h=b4fbd9f5c164099142c4301660e78a3db63f267c

Author: Daniel-Constantin Mierla <miconda at gmail.com>
Committer: Daniel-Constantin Mierla <miconda at gmail.com>
Date:   Mon Aug 30 10:35:46 2010 +0200

tcp: read http/1.1 chunked body

- fix for xcap_server module that has to deal with PUT commands from
  xcap clients
- code withing READ_HTTP11 defines (for now off by default)
- conditioned by tcp_accept_no_cl=yes
- able to handle cases of "Expect: 100-continue" and "Transfer-Encoding:
  chunked"

---

 tcp_conn.h |   13 +++++
 tcp_read.c |  157 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 167 insertions(+), 3 deletions(-)

diff --git a/tcp_conn.h b/tcp_conn.h
index 3ce6084..b729df7 100644
--- a/tcp_conn.h
+++ b/tcp_conn.h
@@ -86,6 +86,10 @@ enum tcp_req_states {	H_SKIP_EMPTY, H_SKIP_EMPTY_CR_FOUND, H_SKIP_EMPTY_CRLF_FOU
 		H_CONT_LEN11, H_CONT_LEN12, H_CONT_LEN13, H_L_COLON, 
 		H_CONT_LEN_BODY, H_CONT_LEN_BODY_PARSE,
 		H_STUN_MSG, H_STUN_READ_BODY, H_STUN_FP, H_STUN_END, H_PING_CRLF
+#ifdef READ_HTTP11
+		, H_HTTP11_CHUNK_START, H_HTTP11_CHUNK_SIZE,
+		H_HTTP11_CHUNK_BODY, H_HTTP11_CHUNK_END, H_HTTP11_CHUNK_FINISH
+#endif
 	};
 
 enum tcp_conn_states { S_CONN_ERROR=-2, S_CONN_BAD=-1,
@@ -129,6 +133,9 @@ struct tcp_req{
 	char* body; /* body position */
 	unsigned int b_size; /* buffer size-1 (extra space for 0-term)*/
 	int content_len;
+#ifdef READ_HTTP11
+	int chunk_size;
+#endif
 	unsigned short flags; /* F_TCP_REQ_HAS_CLEN | F_TCP_REQ_COMPLETE */
 	int bytes_to_go; /* how many bytes we have still to read from the body*/
 	enum tcp_req_errors error;
@@ -138,9 +145,15 @@ struct tcp_req{
 /* tcp_req flags */
 #define F_TCP_REQ_HAS_CLEN 1
 #define F_TCP_REQ_COMPLETE 2
+#ifdef READ_HTTP11
+#define F_TCP_REQ_BCHUNKED 4
+#endif
 
 #define TCP_REQ_HAS_CLEN(tr)  ((tr)->flags & F_TCP_REQ_HAS_CLEN)
 #define TCP_REQ_COMPLETE(tr)  ((tr)->flags & F_TCP_REQ_COMPLETE)
+#ifdef READ_HTTP11
+#define TCP_REQ_BCHUNKED(tr)  ((tr)->flags & F_TCP_REQ_BCHUNKED)
+#endif
 
 
 struct tcp_connection;
diff --git a/tcp_read.c b/tcp_read.c
index a4c1461..c71949b 100644
--- a/tcp_read.c
+++ b/tcp_read.c
@@ -103,6 +103,11 @@ int is_msg_complete(struct tcp_req* r);
 
 #endif /* USE_STUN */
 
+#ifdef READ_HTTP11
+#define HTTP11CONTINUE	"HTTP/1.1 100 Continue\r\nContent-Lenght: 0\r\n\r\n"
+#define HTTP11CONTINUE_LEN	(sizeof(HTTP11CONTINUE)-1)
+#endif
+
 #define TCPCONN_TIMEOUT_MIN_RUN  1 /* run the timers each new tick */
 
 /* types used in io_wait* */
@@ -116,6 +121,47 @@ static int tcpmain_sock=-1;
 static struct local_timer tcp_reader_ltimer;
 static ticks_t tcp_reader_prev_ticks;
 
+#ifdef READ_HTTP11
+int tcp_http11_continue(struct tcp_connection *c)
+{
+	struct dest_info dst;
+	char *p;
+	struct msg_start fline;
+	int ret;
+
+	ret = 0;
+
+	p = parse_first_line(c->req.buf, c->req.pos - c->req.buf, &fline);
+	if(p==NULL)
+		return 0;
+
+	if(fline.type!=SIP_REQUEST)
+		return 0;
+
+	/* check if http request */
+	if(fline.u.request.version.len < HTTP_VERSION_LEN
+			|| strncasecmp(fline.u.request.version.s,
+				HTTP_VERSION, HTTP_VERSION_LEN))
+		return 0;
+
+	/* check for Expect header */
+	if(strstr(c->req.buf, "Expect: 100-continue")!=NULL)
+	{
+		init_dst_from_rcv(&dst, &c->rcv);
+		if (tcp_send(&dst, 0, HTTP11CONTINUE, HTTP11CONTINUE_LEN) < 0) {
+			LOG(L_ERR, "HTTP/1.1 continue failed\n");
+		}
+	}
+	/* check for Transfer-Encoding header */
+	if(strstr(c->req.buf, "Transfer-Encoding: chunked")!=NULL)
+	{
+		c->req.flags |= F_TCP_REQ_BCHUNKED;
+		ret = 1;
+	}
+	return ret;
+}
+#endif /* HTTP11 */
+
 
 /** reads data from an existing tcp connection.
  * Side-effects: blacklisting, sets connection state to S_CONN_OK, tcp stats.
@@ -400,6 +446,10 @@ int tcp_read_headers(struct tcp_connection *c, int* read_flags)
 				if (*p=='\n'){
 					/* found LF CR LF */
 					r->state=H_BODY;
+#ifdef READ_HTTP11
+					if (cfg_get(tcp, tcp_cfg, accept_no_cl)!=0)
+						tcp_http11_continue(c);
+#endif
 					if (TCP_REQ_HAS_CLEN(r)){
 						r->body=p+1;
 						r->bytes_to_go=r->content_len;
@@ -410,6 +460,17 @@ int tcp_read_headers(struct tcp_connection *c, int* read_flags)
 						}
 					}else{
 						if (cfg_get(tcp, tcp_cfg, accept_no_cl)!=0) {
+#ifdef READ_HTTP11
+							if(TCP_REQ_BCHUNKED(r)) {
+								r->body=p+1;
+								/* at least 3 bytes: 0\r\n */
+								r->bytes_to_go=3;
+								p++;
+								r->content_len = 0;
+								r->state=H_HTTP11_CHUNK_START;
+								break;
+							}
+#endif
 							r->body=p+1;
 							r->bytes_to_go=0;
 							r->flags|=F_TCP_REQ_COMPLETE;
@@ -598,7 +659,7 @@ int tcp_read_headers(struct tcp_connection *c, int* read_flags)
 			change_state_case(H_CONT_LEN11, 'G', 'g', H_CONT_LEN12);
 			change_state_case(H_CONT_LEN12, 'T', 't', H_CONT_LEN13);
 			change_state_case(H_CONT_LEN13, 'H', 'h', H_L_COLON);
-			
+
 			case H_L_COLON:
 				switch(*p){
 					case ' ':
@@ -611,7 +672,7 @@ int tcp_read_headers(struct tcp_connection *c, int* read_flags)
 				};
 				p++;
 				break;
-			
+
 			case  H_CONT_LEN_BODY:
 				switch(*p){
 					case ' ':
@@ -635,7 +696,7 @@ int tcp_read_headers(struct tcp_connection *c, int* read_flags)
 				}
 				p++;
 				break;
-				
+
 			case H_CONT_LEN_BODY_PARSE:
 				switch(*p){
 					case '0':
@@ -670,6 +731,87 @@ int tcp_read_headers(struct tcp_connection *c, int* read_flags)
 				p++;
 				break;
 			
+#ifdef READ_HTTP11
+			case H_HTTP11_CHUNK_START: /* start a new body chunk: SIZE\r\nBODY\r\n */
+				r->chunk_size = 0;
+				r->state = H_HTTP11_CHUNK_SIZE;
+				break;
+			case H_HTTP11_CHUNK_BODY: /* content of chunnk */
+				remaining=r->pos-p;
+				if (remaining>r->bytes_to_go) remaining=r->bytes_to_go;
+				r->bytes_to_go-=remaining;
+				p+=remaining;
+				if (r->bytes_to_go==0){
+					r->state = H_HTTP11_CHUNK_END;
+					/* shift back body content */
+					if(p-r->chunk_size>0) {
+						memcpy(r->body + r->content_len, p - r->chunk_size,
+								r->chunk_size);
+						r->content_len += r->chunk_size;
+					}
+					goto skip;
+				}
+				break;
+
+			case H_HTTP11_CHUNK_END:
+				switch(*p){
+					case '\r':
+					case ' ':
+					case '\t': /* skip */
+						break;
+					case '\n':
+						r->state = H_HTTP11_CHUNK_START;
+						break;
+					default:
+						LM_ERR("bad chunk, unexpected "
+								"char %c in state %d\n", *p, r->state);
+						r->state=H_SKIP; /* try to find another?*/
+				}
+				p++;
+				break;
+
+			case H_HTTP11_CHUNK_SIZE:
+				switch(*p){
+					case '0': case '1': case '2': case '3':
+					case '4': case '5': case '6': case '7':
+					case '8': case '9':
+						r->chunk_size <<= 4;
+						r->chunk_size += *p - '0';
+						break;
+					case 'a': case 'b': case 'c': case 'd':
+					case 'e': case 'f':
+						r->chunk_size <<= 4;
+						r->chunk_size += *p - 'a' + 10;
+						break;
+					case 'A': case 'B': case 'C': case 'D':
+					case 'E': case 'F':
+						r->chunk_size <<= 4;
+						r->chunk_size += *p - 'A' + 10;
+						break;
+					case '\r':
+					case ' ':
+					case '\t': /* skip */
+						break;
+					case '\n':
+						/* end of line, parse successful */
+						r->state=H_HTTP11_CHUNK_BODY;
+						r->bytes_to_go = r->chunk_size;
+						if (r->bytes_to_go==0){
+							r->state=H_HTTP11_CHUNK_FINISH;
+							r->flags|=F_TCP_REQ_COMPLETE;
+							p++;
+							goto skip;
+						}
+						break;
+					default:
+						LM_ERR("bad chunk size value, unexpected "
+								"char %c in state %d\n", *p, r->state);
+						r->state=H_SKIP; /* try to find another?*/
+				}
+				p++;
+				break;
+#endif
+
 			default:
 				LOG(L_CRIT, "BUG: tcp_read_headers: unexpected state %d\n",
 						r->state);
@@ -801,6 +943,15 @@ again:
 									 &con->rcv);
 			}else
 #endif
+#ifdef READ_HTTP11
+			if (unlikely(req->state==H_HTTP11_CHUNK_FINISH)){
+				/* http chunked request */
+				req->body[req->content_len] = 0;
+				ret = receive_msg(req->start,
+						req->body + req->content_len - req->start,
+						&con->rcv);
+			}else
+#endif
 				ret = receive_msg(req->start, req->parsed-req->start,
 									&con->rcv);
 				




More information about the sr-dev mailing list