Refactor range-parsing - quark - quark web server
 (HTM) git clone git://git.suckless.org/quark
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) LICENSE
       ---
 (DIR) commit db4e35d3d5cbd5bfac61cfb8edadd47c4608a864
 (DIR) parent 6b508a0e073fd4e29540f8808d919ff72c1893fb
 (HTM) Author: Laslo Hunhold <dev@frign.de>
       Date:   Thu, 23 Jul 2020 18:16:08 +0200
       
       Refactor range-parsing
       
       Quark previously didn't really handle suffix-range-requests
       (those of the form "-num", asking for the last num bytes) properly
       and also did not catch the error when the lower in the range
       "lower-upper" was actually larger than or equal to the size of the
       requested file.
       
       I always planned to refactor the parsing but got the motivation by
       Eric Radman <ericshane@eradman.com>, who kindly reported the latter bug
       to me.
       
       Signed-off-by: Laslo Hunhold <dev@frign.de>
       
       Diffstat:
         M http.c                              |     109 ++++++++++++++++++++++++-------
       
       1 file changed, 84 insertions(+), 25 deletions(-)
       ---
 (DIR) diff --git a/http.c b/http.c
       @@ -562,36 +562,95 @@ http_send_response(int fd, struct request *r)
                                return http_send_status(fd, S_BAD_REQUEST);
                        }
                        *(q++) = '\0';
       -                if (p[0]) {
       +
       +                /*
       +                 *  byte-range=first\0last...
       +                 *             ^      ^
       +                 *             |      |
       +                 *             p      q
       +                 */
       +
       +                /*
       +                 * make sure we only have a single range,
       +                 * and not a comma separated list, which we
       +                 * will refuse to accept out of spite towards
       +                 * this horrible part of the spec
       +                 */
       +                if (strchr(q, ',')) {
       +                        goto not_satisfiable;
       +                }
       +
       +                if (p[0] != '\0') {
       +                        /*
       +                         * Range has format "first-last" or "first-",
       +                         * i.e. return bytes 'first' to 'last' (or the
       +                         * last byte if 'last' is not given),
       +                         * inclusively, and byte-numbering beginning at 0
       +                         */
                                lower = strtonum(p, 0, LLONG_MAX, &err);
       -                }
       -                if (!err && q[0]) {
       +                        if (!err) {
       +                                if (q[0] != '\0') {
       +                                        upper = strtonum(q, 0, LLONG_MAX,
       +                                                         &err);
       +                                } else {
       +                                        upper = st.st_size - 1;
       +                                }
       +                        }
       +                        if (err) {
       +                                /* one of the strtonum()'s failed */
       +                                return http_send_status(fd, S_BAD_REQUEST);
       +                        }
       +
       +                        /* check ranges */
       +                        if (lower > upper || lower >= st.st_size) {
       +                                goto not_satisfiable;
       +                        }
       +
       +                        /* adjust upper limit to be at most the last byte */
       +                        upper = MIN(upper, st.st_size - 1);
       +                } else {
       +                        /*
       +                         * Range has format "-num", i.e. return the 'num'
       +                         * last bytes
       +                         */
       +
       +                        /*
       +                         * use upper as a temporary storage for 'num',
       +                         * as we know 'upper' is st.st_size - 1
       +                         */
                                upper = strtonum(q, 0, LLONG_MAX, &err);
       -                }
       -                if (err) {
       -                        return http_send_status(fd, S_BAD_REQUEST);
       -                }
       +                        if (err) {
       +                                return http_send_status(fd, S_BAD_REQUEST);
       +                        }
        
       -                /* check range */
       -                if (lower < 0 || upper < 0 || lower > upper) {
       -                        if (dprintf(fd,
       -                                    "HTTP/1.1 %d %s\r\n"
       -                                    "Date: %s\r\n"
       -                                    "Content-Range: bytes */%zu\r\n"
       -                                    "Connection: close\r\n"
       -                                    "\r\n",
       -                                    S_RANGE_NOT_SATISFIABLE,
       -                                    status_str[S_RANGE_NOT_SATISFIABLE],
       -                                    timestamp(time(NULL), t),
       -                                    st.st_size) < 0) {
       -                                return S_REQUEST_TIMEOUT;
       +                        /* determine lower */
       +                        if (upper > st.st_size) {
       +                                /* more bytes requested than we have */
       +                                lower = 0;
       +                        } else {
       +                                lower = st.st_size - upper;
                                }
       -                        return S_RANGE_NOT_SATISFIABLE;
       -                }
        
       -                /* adjust upper limit */
       -                if (upper >= st.st_size)
       -                        upper = st.st_size-1;
       +                        /* set upper to the correct value */
       +                        upper = st.st_size - 1;
       +                }
       +                goto satisfiable;
       +not_satisfiable:
       +                if (dprintf(fd,
       +                            "HTTP/1.1 %d %s\r\n"
       +                            "Date: %s\r\n"
       +                            "Content-Range: bytes */%zu\r\n"
       +                            "Connection: close\r\n"
       +                            "\r\n",
       +                            S_RANGE_NOT_SATISFIABLE,
       +                            status_str[S_RANGE_NOT_SATISFIABLE],
       +                            timestamp(time(NULL), t),
       +                            st.st_size) < 0) {
       +                        return S_REQUEST_TIMEOUT;
       +                }
       +                return S_RANGE_NOT_SATISFIABLE;
       +satisfiable:
       +                ;
                }
        
                /* mime */