]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: http: url-encoded parsing function can run throught wrapped buffer
authorThierry FOURNIER <tfournier@haproxy.com>
Wed, 20 May 2015 13:28:12 +0000 (15:28 +0200)
committerWilly Tarreau <w@1wt.eu>
Wed, 20 May 2015 14:05:38 +0000 (16:05 +0200)
The functions smp_fetch_param(), find_next_url_param() and
find_url_param_pos() can look for argument in 2 chunks and not only
one.

src/proto_http.c

index 33a35681b4ea843eaf65cc7882cd9220c2ee70b8..fa3be6a917091fe4634cc81cf82fc1f65cf356f2 100644 (file)
@@ -11503,31 +11503,108 @@ static inline int is_param_delimiter(char c, char delim)
        return c == '&' || c == ';' || c == delim;
 }
 
+/* after increasing a pointer value, it can exceed the first buffer
+ * size. This function transform the value of <ptr> according with
+ * the expected position. <chunks> is an array of the one or two
+ * avalaible chunks. The first value is the start of the first chunk,
+ * the second value if the end+1 of the first chunks. The third value
+ * is NULL or the start of the second chunk and the fourth value is
+ * the end+1 of the second chunk. The function returns 1 if does a
+ * wrap, else returns 0.
+ */
+static inline int fix_pointer_if_wrap(const char **chunks, const char **ptr)
+{
+       if (*ptr < chunks[1])
+               return 0;
+       if (!chunks[2])
+               return 0;
+       *ptr = chunks[2] + ( *ptr - chunks[1] );
+       return 1;
+}
+
 /*
  * Given a url parameter, find the starting position of the first occurence,
  * or NULL if the parameter is not found.
  *
  * Example: if query_string is "yo=mama;ye=daddy" and url_param_name is "ye",
  * the function will return query_string+8.
+ *
+ * Warning:this function returns a pointer that can be point to the first chunk
+ * or the second chunk. The caller must be check the position before using the
+ * result.
  */
-static char*
-find_url_param_pos(char* query_string, size_t query_string_l,
+static const char *
+find_url_param_pos(const char **chunks,
                    const char* url_param_name, size_t url_param_name_l,
                    char delim)
 {
-       char *pos, *last;
+       const char *pos, *last, *equal;
+       const char **bufs = chunks;
+       int l1, l2;
 
-       pos  = query_string;
-       last = query_string + query_string_l - url_param_name_l - 1;
 
+       pos  = bufs[0];
+       last = bufs[1];
        while (pos <= last) {
-               if (pos[url_param_name_l] == '=') {
-                       if (memcmp(pos, url_param_name, url_param_name_l) == 0)
-                               return pos;
-                       pos += url_param_name_l + 1;
+               /* Check the equal. */
+               equal = pos + url_param_name_l;
+               if (fix_pointer_if_wrap(chunks, &equal)) {
+                       if (equal >= chunks[3])
+                               return NULL;
+               } else {
+                       if (equal >= chunks[1])
+                               return NULL;
+               }
+               if (*equal == '=') {
+                       if (pos + url_param_name_l > last) {
+                               /* process wrap case, we detect a wrap. In this case, the
+                                * comparison is performed in two parts.
+                                */
+
+                               /* This is the end, we dont have any other chunk. */
+                               if (bufs != chunks || !bufs[2])
+                                       return NULL;
+
+                               /* Compute the length of each part of the comparison. */
+                               l1 = last - pos;
+                               l2 = url_param_name_l - l1;
+
+                               /* The second buffer is too short to contain the compared string. */
+                               if (bufs[2] + l2 > bufs[3])
+                                       return NULL;
+
+                               if (memcmp(pos,     url_param_name,    l1) == 0 &&
+                                   memcmp(bufs[2], url_param_name+l1, l2) == 0)
+                                       return pos;
+
+                               /* Perform wrapping and jump the string who fail the comparison. */
+                               bufs += 2;
+                               pos = bufs[0] + l2;
+                               last = bufs[1];
+
+                       } else {
+                               /* process a simple comparison. */
+                               if (memcmp(pos, url_param_name, url_param_name_l) == 0) {
+                                       return pos; }
+                               pos += url_param_name_l + 1;
+                               if (fix_pointer_if_wrap(chunks, &pos))
+                                       last = bufs[2];
+                       }
+               }
+
+               while (1) {
+                       /* Look for the next delimiter. */
+                       while (pos <= last && !is_param_delimiter(*pos, delim))
+                               pos++;
+                       if (pos < last)
+                               break;
+                       /* process buffer wrapping. */
+                       if (bufs != chunks || !bufs[2])
+                               return NULL;
+                       bufs += 2;
+                       pos = bufs[0];
+                       last = bufs[1];
                }
-               while (pos <= last && !is_param_delimiter(*pos, delim))
-                       pos++;
                pos++;
        }
        return NULL;
@@ -11540,67 +11617,131 @@ find_url_param_pos(char* query_string, size_t query_string_l,
  * not found, zero is returned and value/value_l are not touched.
  */
 static int
-find_next_url_param(char* query_string, char *qs_end,
+find_next_url_param(const char **chunks,
                     const char* url_param_name, size_t url_param_name_l,
-                    char** value, int* value_l, char delim)
+                    const char **vstart, const char **vend, char delim)
 {
-       char *arg_start;
-       char *value_start, *value_end;
+       const char *arg_start, *qs_end;
+       const char *value_start, *value_end;
 
-       arg_start = query_string;
+       arg_start = chunks[0];
+       qs_end = chunks[1];
        if (url_param_name_l) {
-               arg_start = find_url_param_pos(query_string, qs_end - query_string,
+               /* Looks for an argument name. */
+               arg_start = find_url_param_pos(chunks,
                                               url_param_name, url_param_name_l,
                                               delim);
+               /* Check for wrapping. */
+               if (arg_start > qs_end)
+                       qs_end = chunks[3];
        }
        if (!arg_start)
                return 0;
 
        if (!url_param_name_l) {
-               value_start = memchr(arg_start, '=', qs_end - arg_start);
-               if (!value_start)
-                       return 0;
+               while (1) {
+                       /* looks for the first argument. */
+                       value_start = memchr(arg_start, '=', qs_end - arg_start);
+                       if (!value_start) {
+
+                               /* Check for wrapping. */
+                               if (arg_start >= chunks[0] &&
+                                   arg_start <= chunks[1] &&
+                                   chunks[2]) {
+                                       arg_start = chunks[2];
+                                       qs_end = chunks[3];
+                                       continue;
+                               }
+                               return 0;
+                       }
+                       break;
+               }
                value_start++;
        }
-       else
+       else {
+               /* Jump the argument length. */
                value_start = arg_start + url_param_name_l + 1;
 
+               /* Check for pointer wrapping. */
+               if (fix_pointer_if_wrap(chunks, &value_start)) {
+                       /* Update the end pointer. */
+                       qs_end = chunks[3];
+
+                       /* Check for overflow. */
+                       if (value_start > qs_end)
+                               return 0;
+               }
+       }
+
        value_end = value_start;
 
-       while ((value_end < qs_end) && !is_param_delimiter(*value_end, delim))
-               value_end++;
+       while (1) {
+               while ((value_end < qs_end) && !is_param_delimiter(*value_end, delim))
+                       value_end++;
+               if (value_end < qs_end)
+                       break;
+               /* process buffer wrapping. */
+               if (value_end >= chunks[0] &&
+                   value_end <= chunks[1] &&
+                   chunks[2]) {
+                       value_end = chunks[2];
+                       qs_end = chunks[3];
+                       continue;
+               }
+               break;
+       }
 
-       *value = value_start;
-       *value_l = value_end - value_start;
+       *vstart = value_start;
+       *vend = value_end;
        return value_end != value_start;
 }
 
-/* This scans a URL-encoded query string. It relies on ctx->a[0] to point to
- * the beginning of the string and ctx->a[1] to point to the end. The string
- * must be contigous. The pointers are updated for next iteration before
- * leaving.
+/* This scans a URL-encoded query string. It takes an optionally wrapping
+ * string whose first contigous chunk has its beginning in ctx->a[0] and end
+ * in ctx->a[1], and the optional second part in (ctx->a[2]..ctx->a[3]). The
+ * pointers are updated for next iteration before leaving.
  */
 static int
 smp_fetch_param(char delim, const char *name, int name_len, const struct arg *args, struct sample *smp, const char *kw, void *private)
 {
-       char *query_string, *qs_end;
-
-       query_string = smp->ctx.a[0];
-       qs_end = smp->ctx.a[1];
+       const char *vstart, *vend;
+       struct chunk *temp;
+       const char **chunks = (const char **)smp->ctx.a;
 
-       if (!find_next_url_param(query_string, qs_end,
+       if (!find_next_url_param(chunks,
                                 name, name_len,
-                                &smp->data.str.str, &smp->data.str.len,
+                                &vstart, &vend,
                                 delim))
                return 0;
 
-       query_string = smp->data.str.str + smp->data.str.len + 1;
-       smp->ctx.a[0] = query_string;
-
+       /* Create sample. If the value is contiguous, return the pointer as CONST,
+        * if the value is wrapped, copy-it in a buffer.
+        */
        smp->type = SMP_T_STR;
-       smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
+       if (chunks[2] &&
+           vstart >= chunks[0] && vstart <= chunks[1] &&
+           vend >= chunks[2] && vend <= chunks[3]) {
+               /* Wrapped case. */
+               temp = get_trash_chunk();
+               memcpy(temp->str, vstart, chunks[1] - vstart);
+               memcpy(temp->str + ( chunks[1] - vstart ), chunks[2], vend - chunks[2]);
+               smp->data.str.str = temp->str;
+               smp->data.str.len = ( chunks[1] - vstart ) + ( vend - chunks[2] );
+       } else {
+               /* Contiguous case. */
+               smp->data.str.str = (char *)vstart;
+               smp->data.str.len = vend - vstart;
+               smp->flags = SMP_F_VOL_1ST | SMP_F_CONST;
+       }
+
+       /* Update context, check wrapping. */
+       chunks[0] = vend;
+       if (chunks[2] && vend >= chunks[2] && vend <= chunks[3]) {
+               chunks[1] = chunks[3];
+               chunks[2] = NULL;
+       }
 
-       if (query_string < qs_end)
+       if (chunks[0] < chunks[1])
                smp->flags |= SMP_F_NOT_LAST;
 
        return 1;
@@ -11608,9 +11749,10 @@ smp_fetch_param(char delim, const char *name, int name_len, const struct arg *ar
 
 /* This function iterates over each parameter of the query string. It uses
  * ctx->a[0] and ctx->a[1] to store the beginning and end of the current
- * parameter. An optional parameter name is passed in args[0], otherwise
- * any parameter is considered. It supports an optional delimiter argument
- * for the beginning of the string in args[1], which defaults to "?".
+ * parameter. Since it uses smp_fetch_param(), ctx->a[2..3] are both NULL.
+ * An optional parameter name is passed in args[0], otherwise any parameter is
+ * considered. It supports an optional delimiter argument for the beginning of
+ * the string in args[1], which defaults to "?".
  */
 static int
 smp_fetch_url_param(const struct arg *args, struct sample *smp, const char *kw, void *private)
@@ -11646,6 +11788,12 @@ smp_fetch_url_param(const struct arg *args, struct sample *smp, const char *kw,
                        return 0;
 
                smp->ctx.a[1] = msg->chn->buf->p + msg->sl.rq.u + msg->sl.rq.u_l;
+
+               /* Assume that the context is filled with NULL pointer
+                * before the first call.
+                * smp->ctx.a[2] = NULL;
+                * smp->ctx.a[3] = NULL;
+                */
        }
 
        return smp_fetch_param(delim, name, name_len, args, smp, kw, private);
@@ -11653,9 +11801,10 @@ smp_fetch_url_param(const struct arg *args, struct sample *smp, const char *kw,
 
 /* This function iterates over each parameter of the body. This requires
  * that the body has been waited for using http-buffer-request. It uses
- * ctx->a[0] and ctx->a[1] to store the beginning and end of the current
- * parameter. An optional parameter name is passed in args[0], otherwise
- * any parameter is considered.
+ * ctx->a[0] and ctx->a[1] to store the beginning and end of the first
+ * contigous part of the body, and optionally ctx->a[2..3] to reference the
+ * optional second part if the body wraps at the end of the buffer. An optional
+ * parameter name is passed in args[0], otherwise any parameter is considered.
  */
 static int
 smp_fetch_body_param(const struct arg *args, struct sample *smp, const char *kw, void *private)
@@ -11697,11 +11846,19 @@ smp_fetch_body_param(const struct arg *args, struct sample *smp, const char *kw,
                        /* buffer is not wrapped (or empty) */
                        smp->ctx.a[0] = body;
                        smp->ctx.a[1] = body + len;
+
+                       /* Assume that the context is filled with NULL pointer
+                        * before the first call.
+                        * smp->ctx.a[2] = NULL;
+                        * smp->ctx.a[3] = NULL;
+                       */
                }
                else {
                        /* buffer is wrapped, we need to defragment it */
                        smp->ctx.a[0] = body;
                        smp->ctx.a[1] = body + block1;
+                       smp->ctx.a[2] = msg->chn->buf->data;
+                       smp->ctx.a[3] = msg->chn->buf->data + ( len - block1 );
                }
        }
        return smp_fetch_param('&', name, name_len, args, smp, kw, private);