private void CheckValidTryParse(string input, RangeHeaderValue expectedResult)
        {
            RangeHeaderValue result = null;

            Assert.True(RangeHeaderValue.TryParse(input, out result));
            Assert.Equal(expectedResult, result);
        }
Beispiel #2
0
        private void CheckInvalidParse(string input)
        {
            Assert.Throws <FormatException>(() => { RangeHeaderValue.Parse(input); });

            Assert.False(RangeHeaderValue.TryParse(input, out RangeHeaderValue result));
            Assert.Null(result);
        }
        private void CheckInvalidTryParse(string input)
        {
            RangeHeaderValue result = null;

            Assert.False(RangeHeaderValue.TryParse(input, out result));
            Assert.Null(result);
        }
        public void TryParse_Invalid()
        {
            RangeHeaderValue res;

            Assert.IsFalse(RangeHeaderValue.TryParse("bytes=4,33", out res), "#1");
            Assert.IsNull(res, "#2");
        }
        public void TryParse()
        {
            RangeHeaderValue res;

            Assert.IsTrue(RangeHeaderValue.TryParse("bytes=4-33", out res), "#1");
            Assert.AreEqual("bytes", res.Unit, "#2");
            Assert.AreEqual(4, res.Ranges.First().From, "#3");
            Assert.AreEqual(33, res.Ranges.First().To, "#4");
        }
        public static bool IsRangeHeaderExist(this WebHeaderCollection headerCollection)
        {
            RangeHeaderValue rangeHeader = new RangeHeaderValue();

            if (!RangeHeaderValue
                .TryParse(headerCollection[HttpRequestHeader.Range], out rangeHeader))
            {
                return(false);
            }
            return(rangeHeader != null && rangeHeader.Ranges.Any());
        }
        public FileRangeResult(string fileName, string range, string mediaType)
        {
            this.fileName  = fileName;
            this.mediaType = mediaType;
            hasValidRange  = RangeHeaderValue.TryParse(range, out RangeHeaderValue header);

            if (hasValidRange)
            {
                RangeItemHeaderValue headerValue = header.Ranges.ElementAt(0);

                from = headerValue.From;
                to   = headerValue.To;
            }
        }
Beispiel #8
0
    private static void CheckValidTryParse(string input, long?expectedFrom, long?expectedTo)
    {
        RangeHeaderValue result;

        Assert.True(RangeHeaderValue.TryParse("byte=" + input, out result), input);

        var ranges = result.Ranges.ToArray();

        Assert.Single(ranges);

        var range = ranges.First();

        Assert.Equal(expectedFrom, range.From);
        Assert.Equal(expectedTo, range.To);
    }
Beispiel #9
0
    private static void CheckValidTryParse(string input, params Tuple <long?, long?>[] expectedRanges)
    {
        RangeHeaderValue result;

        Assert.True(RangeHeaderValue.TryParse("byte=" + input, out result), input);

        var ranges = result.Ranges.ToArray();

        Assert.Equal(expectedRanges.Length, ranges.Length);

        for (int i = 0; i < expectedRanges.Length; i++)
        {
            Assert.Equal(expectedRanges[i].Item1, ranges[i].From);
            Assert.Equal(expectedRanges[i].Item2, ranges[i].To);
        }
    }
        public static bool IsRangeHeaderCorrect(this WebHeaderCollection headerCollection, long fileLen)
        {
            RangeHeaderValue rangeHeader = new RangeHeaderValue();

            if (!RangeHeaderValue
                .TryParse(headerCollection[HttpRequestHeader.Range], out rangeHeader))
            {
                return(false);
            }
            bool unitIsNotbytes          = rangeHeader.Unit != "bytes";
            bool multipleRanges          = rangeHeader.Ranges.Count > 1;
            RangeItemHeaderValue range   = rangeHeader.Ranges.First();
            bool start_end_fileLen_error = !(range.RangeHeaderStart(fileLen) < fileLen && range.RangeHeaderEnd(fileLen) < fileLen);

            return(!(unitIsNotbytes || multipleRanges || start_end_fileLen_error));
        }
Beispiel #11
0
        /// <summary>
        /// Copy a stream into the response body
        /// </summary>
        /// <param name="response">Current <see cref="HttpResponse"/></param>
        /// <param name="stream">The <see cref="Stream"/> to copy from</param>
        /// <param name="contentType">The content type for the response</param>
        /// <param name="contentDisposition">The content disposition to allow file downloads</param>
        /// <returns><see cref="Task"/></returns>
        public static async Task FromStream(this HttpResponse response, Stream source, string contentType, ContentDisposition contentDisposition = null)
        {
            var contentLength = source.Length;

            response.Headers["Accept-Ranges"] = "bytes";

            response.ContentType = contentType;

            if (contentDisposition != null)
            {
                response.Headers["Content-Disposition"] = contentDisposition.ToString();
            }

            if (RangeHeaderValue.TryParse(response.HttpContext.Request.Headers["Range"].ToString(), out var rangeHeader))
            {
                //Server should return multipart/byteranges; if asking for more than one range but pfft...
                var rangeStart = rangeHeader.Ranges.First().From;
                var rangeEnd   = rangeHeader.Ranges.First().To ?? contentLength - 1;

                if (!rangeStart.HasValue || rangeEnd > contentLength - 1)
                {
                    response.StatusCode = (int)HttpStatusCode.RequestedRangeNotSatisfiable;
                }
                else
                {
                    response.Headers["Content-Range"] = $"bytes {rangeStart}-{rangeEnd}/{contentLength}";
                    response.StatusCode = (int)HttpStatusCode.PartialContent;
                    if (!source.CanSeek)
                    {
                        throw new InvalidOperationException("Sending Range Responses requires a seekable stream eg. FileStream or MemoryStream");
                    }

                    source.Seek(rangeStart.Value, SeekOrigin.Begin);
                    await StreamCopyOperation.CopyToAsync(source, response.Body, rangeEnd - rangeStart.Value + 1, 65536, response.HttpContext.RequestAborted);
                }
            }
            else
            {
                await StreamCopyOperation.CopyToAsync(source, response.Body, default, 65536, response.HttpContext.RequestAborted);
Beispiel #12
0
        /// <summary> 获取文件分块信息
        /// </summary>
        /// <param name="request"></param>
        /// <param name="filePath"></param>
        /// <returns></returns>
        private PartialFileInfo GetPartialFileInfo(HttpRequest request, string filePath)
        {
            PartialFileInfo partialFileInfo = new PartialFileInfo(filePath);

            if (RangeHeaderValue.TryParse(request.Headers[HeaderNames.Range].ToString(), out RangeHeaderValue rangeHeaderValue))
            {
                var range = rangeHeaderValue.Ranges.FirstOrDefault();
                if (range.From.HasValue && range.From < 0 || range.To.HasValue && range.To > partialFileInfo.FileLength - 1)
                {
                    return(null);
                }
                var from = range.From;
                var to   = range.To;
                if (from.HasValue)
                {
                    if (from.Value >= partialFileInfo.FileLength)
                    {
                        return(null);
                    }
                    if (!to.HasValue || to.Value >= partialFileInfo.FileLength)
                    {
                        to = partialFileInfo.FileLength - 1;
                    }
                }
                else
                {
                    if (to.Value == 0)
                    {
                        return(null);
                    }
                    var bytes = Math.Min(to.Value, partialFileInfo.FileLength);
                    from = partialFileInfo.FileLength - bytes;
                    to   = from + bytes - 1;
                }
                partialFileInfo.IsPartial = true;
                partialFileInfo.Length    = to.Value - from.Value + 1;
            }
            return(partialFileInfo);
        }
Beispiel #13
0
        public IActionResult GetRange([FromHeader(Name = "Range")] string rawRange)
        {
            RangeHeaderValue range;

            if (rawRange != null)
            {
                if (!RangeHeaderValue.TryParse(rawRange, out range))
                {
                    return(new BadRequestResult());
                }
            }
            else
            {
                range = new RangeHeaderValue(0, 256)
                {
                    Unit = "items"
                };
            }

            // Reject multipart range requests for now…
            if (!range.Unit.Equals("items", StringComparison.OrdinalIgnoreCase) || range.Ranges.Count != 1)
            {
                return(new BadRequestResult());
            }

            var firstRange = range.Ranges.First();

            int offset = checked ((int)(firstRange.From ?? 0));
            int count  = firstRange.To != null?
                         Math.Min(256, checked ((int)firstRange.To.GetValueOrDefault()) - offset + 1) :
                             256;

            return(new RangeResult(CodePointProvider.GetCodePoints(offset, count), new ContentRangeHeaderValue(offset, offset + count - 1, 0x110000)
            {
                Unit = "items"
            }));
        }
Beispiel #14
0
        // Checks the Range request header to tell whether to send
        // a "206 Partial Content" response.

        /// <summary>
        /// <para>Checks whether a <c>Range</c> header exists in a request
        /// and, if so, determines whether it is possible to send a <c>206 Partial Content</c> response.</para>
        /// <para>See <see href="https://tools.ietf.org/html/rfc7233">RFC7233</see>
        /// for a normative reference; however, see the Remarks section for more information
        /// about the RFC compliance of this method.</para>
        /// </summary>
        /// <param name="this">The <see cref="IHttpRequest"/> on which this method is called.</param>
        /// <param name="contentLength">The total length, in bytes, of the response entity, i.e.
        /// what would be sent in a <c>200 OK</c> response.</param>
        /// <param name="entityTag">An entity tag representing the response entity. This value is checked against
        /// the <c>If-Range</c> header, if it is present.</param>
        /// <param name="lastModifiedUtc">The date and time value, in Coordinated Universal Time,
        /// expressing the last modification time of the resource entity. This value is checked against
        /// the <c>If-Range</c> header, if it is present.</param>
        /// <param name="start">When this method returns <see langword="true"/>, the start of the requested byte range.
        /// This parameter is passed uninitialized.</param>
        /// <param name="upperBound">
        /// <para>When this method returns <see langword="true"/>, the upper bound of the requested byte range.
        /// This parameter is passed uninitialized.</para>
        /// <para>Note that the upper bound of a range is NOT the sum of the range's start and length;
        /// for example, a range expressed as <c>bytes=0-99</c> has a start of 0, an upper bound of 99,
        /// and a length of 100 bytes.</para>
        /// </param>
        /// <returns>
        /// <para>This method returns <see langword="true"/> if the following conditions are satisfied:</para>
        /// <list type="bullet">
        /// <item><description>>the request's HTTP method is <c>GET</c>;</description></item>
        /// <item><description>>a <c>Range</c> header is present in the request;</description></item>
        /// <item><description>>either no <c>If-Range</c> header is present in the request, or it
        /// specifies an entity tag equal to <paramref name="entityTag"/>, or a UTC date and time
        /// equal to <paramref name="lastModifiedUtc"/>;</description></item>
        /// <item><description>>the <c>Range</c> header specifies exactly one range;</description></item>
        /// <item><description>>the specified range is entirely contained in the range from 0 to <paramref name="contentLength"/> - 1.</description></item>
        /// </list>
        /// <para>If the last condition is not satisfied, i.e. the specified range start and/or upper bound
        /// are out of the range from 0 to <paramref name="contentLength"/> - 1, this method does not return;
        /// it throws a <see cref="HttpRangeNotSatisfiableException"/> instead.</para>
        /// <para>If any of the other conditions are not satisfied, this method returns <see langword="false"/>.</para>
        /// </returns>
        /// <remarks>
        /// <para>According to <see href="https://tools.ietf.org/html/rfc7233#section-3.1">RFC7233, Section 3.1</see>,
        /// there are several conditions under which a server may ignore or reject a range request; therefore,
        /// clients are (or should be) prepared to receive a <c>200 OK</c> response with the whole response
        /// entity instead of the requested range(s). For this reason, until the generation of
        /// <c>multipart/byteranges</c> responses is implemented in EmbedIO, this method will ignore
        /// range requests specifying more than one range, even if this behavior is not, strictly speaking,
        /// RFC7233-compliant.</para>
        /// <para>To make clients aware that range requests are accepted for a resource, every <c>200 OK</c>
        /// (or <c>304 Not Modified</c>) response for the same resource should include an <c>Accept-Ranges</c>
        /// header with the string <c>bytes</c> as value.</para>
        /// </remarks>
        public static bool IsRangeRequest(this IHttpRequest @this, long contentLength, string entityTag, DateTime lastModifiedUtc, out long start, out long upperBound)
        {
            start      = 0;
            upperBound = contentLength - 1;

            // RFC7233, Section 3.1:
            // "A server MUST ignore a Range header field received with a request method other than GET."
            if (@this.HttpVerb != HttpVerbs.Get)
            {
                return(false);
            }

            // No Range header, no partial content.
            var rangeHeader = @this.Headers.Get(HttpHeaderNames.Range);

            if (rangeHeader == null)
            {
                return(false);
            }

            // Ignore the Range header if there is no If-Range header
            // or if the If-Range header specifies a non-matching validator.
            // RFC7233, Section 3.2: "If the validator given in the If-Range header field matches the
            //                       current validator for the selected representation of the target
            //                       resource, then the server SHOULD process the Range header field as
            //                       requested.If the validator does not match, the server MUST ignore
            //                       the Range header field.Note that this comparison by exact match,
            //                       including when the validator is an HTTP-date, differs from the
            //                       "earlier than or equal to" comparison used when evaluating an
            //                       If-Unmodified-Since conditional."
            var ifRange = @this.Headers.Get(HttpHeaderNames.IfRange)?.Trim();

            if (ifRange != null && ifRange != entityTag)
            {
                if (!HttpDate.TryParse(ifRange, out var rangeDate))
                {
                    return(false);
                }

                if (rangeDate.UtcDateTime != lastModifiedUtc)
                {
                    return(false);
                }
            }

            // Ignore the Range request header if it cannot be parsed successfully.
            if (!RangeHeaderValue.TryParse(rangeHeader, out var range))
            {
                return(false);
            }

            // EmbedIO does not support multipart/byteranges responses (yet),
            // thus ignore range requests that specify one range.
            if (range.Ranges.Count != 1)
            {
                return(false);
            }

            var firstRange = range.Ranges.First();

            start      = firstRange.From ?? 0L;
            upperBound = firstRange.To ?? contentLength - 1;
            if (start >= contentLength || upperBound < start || upperBound >= contentLength)
            {
                throw HttpException.RangeNotSatisfiable(contentLength);
            }

            return(true);
        }
Beispiel #15
0
        private static HttpWebRequest CreateRequest(string url, string method, Table headers, string type, string body, Table parameters)
        {
            var requestHasBody = BodiedMethods.Contains(method.ToLower());

            // Create body/parameters
            if (body == null && parameters != null)
            {
                // encode as form
                var sb = new StringBuilder();
                foreach (var pair in parameters.Pairs)
                {
                    sb.Append(Uri.EscapeDataString(pair.Key.CastToString()));
                    sb.Append("=");
                    sb.Append(Uri.EscapeDataString(pair.Value.CastToString()));
                    sb.Append("&");
                }
                if (sb.Length > 0)
                {
                    // remove last "&"
                    sb.Length -= 1;
                }

                if (requestHasBody)
                {
                    body = sb.ToString();
                    if (type == null)
                    {
                        type = "application/x-www-form-urlencoded";
                    }
                }
                else if (url.Contains("?"))
                {
                    if (url.Last() != '&')
                    {
                        url += "&";
                    }
                    url += sb.ToString();
                }
                else
                {
                    url += "?" + sb.ToString();
                }
            }

            // Create request
            var request = WebRequest.CreateHttp(url);

            request.Method            = method.ToUpper();
            request.Accept            = "*/*";
            request.KeepAlive         = false;
            request.AllowAutoRedirect = false;
            if (requestHasBody && body != null && body.Length > 0)
            {
                using (var stream = request.GetRequestStream()) {
                    var bodyBytes = Encoding.Default.GetBytes(body);
                    stream.Write(bodyBytes, 0, bodyBytes.Length);
                }
            }

            // Set content type
            if (type != null)
            {
                request.ContentType = type;
            }

            // Add headers
            foreach (var header in headers.Pairs)
            {
                var name  = header.Key.CastToString();
                var value = header.Value.CastToString();
                switch (name.ToLower())
                {
                case "accept":
                    request.Accept = value;
                    break;

                case "connection":
                    request.Connection = value;
                    break;

                case "content-length":
                    request.ContentLength = long.Parse(value);
                    break;

                case "content-type":
                    request.ContentType = value;
                    break;

                case "date":
                    if (DateTime.TryParse(value, out var date))
                    {
                        request.Date = date;
                    }
                    break;

                case "expect":
                    request.Expect = value;
                    break;

                case "host":
                    request.Host = value;
                    break;

                case "if-modified-since":
                    if (DateTime.TryParse(value, out var ifModifiedSince))
                    {
                        request.IfModifiedSince = ifModifiedSince;
                    }
                    break;

                case "range":
                    if (RangeHeaderValue.TryParse(value, out var ranges))
                    {
                        foreach (var range in ranges.Ranges)
                        {
                            if (range.From.HasValue && range.To.HasValue)
                            {
                                request.AddRange(ranges.Unit, range.From.Value, range.To.Value);
                            }
                            else if (range.From.HasValue)
                            {
                                request.AddRange(ranges.Unit, range.From.Value);
                            }
                            else if (range.To.HasValue)
                            {
                                request.AddRange(ranges.Unit, range.To.Value);
                            }
                        }
                    }
                    break;

                case "referer":
                    request.Referer = value;
                    break;

                case "transfer-encoding":
                    request.TransferEncoding = value;
                    break;

                case "user-agent":
                    request.UserAgent = value;
                    break;

                default:
                    request.Headers.Add(name, value);
                    break;
                }
            }

            return(request);
        }
 private void CheckInvalidTryParse(string?input)
 {
     Assert.False(RangeHeaderValue.TryParse(input, out var result));
     Assert.Null(result);
 }
Beispiel #17
0
    [InlineData("-9999999999999999999")]     // 19-digit numbers outside the Int64 range.
    public void TryParse_DifferentInvalidScenarios_AllReturnFalse(string input)
    {
        RangeHeaderValue result;

        Assert.False(RangeHeaderValue.TryParse("byte=" + input, out result));
    }
        public static HttpInitialRequest?ParseRequest(string str)
        {
            var data = str.AsSpan();

            var result = new HttpInitialRequest();

            if (str.StartsWith("GET "))
            {
                result.Method = HttpInitialRequestMethod.GET;
            }
            else if (str.StartsWith("HEAD "))
            {
                result.Method = HttpInitialRequestMethod.HEAD;
            }
            else
            {
                return(null);
            }

            var start = result.Method == HttpInitialRequestMethod.HEAD ? 5 : 4;

            data = data.Slice(start);

            var path = data.Slice(1, data.IndexOf(' ') - 1);

            VerifyPathPlatform(ref path);
            result.Path = new string(path);

            var version = data.Slice(path.Length + 2, 8);

            result.HttpVersion = new string(version);

            var remaining = data.Slice(path.Length + version.Length + 2);

            var rangeIndex = remaining.IndexOf(RangeField);

            if (rangeIndex == -1)
            {
                return(result);
            }
            var range   = remaining.Slice(rangeIndex + 6);
            var newLine = range.IndexOf('\n');
            var content = (newLine != -1 ? new string(range.Slice(0, newLine)) : new string(range)).Trim();

            if (!RangeHeaderValue.TryParse(content, out var value))
            {
                return(result);
            }
            if (value.Ranges.Count == 0)
            {
                return(result);
            }
            var list = new List <HttpRequestRangeField>();

            foreach (var rangeValue in value.Ranges)
            {
                var val = new HttpRequestRangeField();

                if (rangeValue.From == null && rangeValue.To != null)
                {
                    val.Method = HttpRangeRequestMethod.SliceFromStartTo;
                    val.Range  = rangeValue;
                    list.Add(val);
                }
                else if (rangeValue.From != null && rangeValue.From != 0 && rangeValue.To != null && rangeValue.To > rangeValue.From)
                {
                    val.Method = HttpRangeRequestMethod.SliceFromTo;
                    val.Range  = rangeValue;
                    list.Add(val);
                }
                else if (rangeValue.From != null && rangeValue.From == 0 && rangeValue.To == null)
                {
                    val.Method = HttpRangeRequestMethod.SendAll;
                    val.Range  = rangeValue;
                    list.Add(val);
                }
                else if (rangeValue.From != null && rangeValue.From != 0 && rangeValue.To == null)
                {
                    val.Method = HttpRangeRequestMethod.SliceFromToEnd;
                    val.Range  = rangeValue;
                    list.Add(val);
                }
            }
            result.Ranges = list;

            return(result);
        }