private void CheckValidTryParse(string input, RangeHeaderValue expectedResult) { RangeHeaderValue result = null; Assert.True(RangeHeaderValue.TryParse(input, out result)); Assert.Equal(expectedResult, result); }
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; } }
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); }
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)); }
/// <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);
/// <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); }
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" })); }
// 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); }
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); }
[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); }