public override bool TryParseValue(string value, object storeValue, ref int index, out object parsedValue) { parsedValue = null; // Some headers support empty/null values. This one doesn't. if (string.IsNullOrEmpty(value) || (index == value.Length)) { return(false); } string dateString = value; if (index > 0) { dateString = value.Substring(index); } DateTimeOffset date; if (!HttpRuleParser.TryStringToDate(dateString, out date)) { return(false); } index = value.Length; parsedValue = date; return(true); }
public void TryStringToDate_UseInvalidDateTimeString_Throw() { DateTimeOffset result = DateTimeOffset.MinValue; Assert.False(HttpRuleParser.TryStringToDate("Sun, 06 Nov 1994 08:49:37 GMT invalid", out result)); Assert.False(HttpRuleParser.TryStringToDate("Sun, 06 Nov 1994 08:49:37 GMT,", out result)); Assert.False(HttpRuleParser.TryStringToDate(",Sun, 06 Nov 1994 08:49:37 GMT", out result)); }
private static bool TryReadDate(string input, ref int current, out DateTimeOffset?date) { date = null; // Make sure we have at least one whitespace between <text> and <date> (if we have <date>) int whitespaceLength = HttpRuleParser.GetWhitespaceLength(input, current); current = current + whitespaceLength; // Read <date> in '<code> <agent> <text> ["<date>"]' if ((current < input.Length) && (input[current] == '"')) { if (whitespaceLength == 0) { return(false); // we have characters after <text> but they were not separated by a whitespace } current++; // skip opening '"' // Find the closing '"' int dateStartIndex = current; while (current < input.Length) { if (input[current] == '"') { break; } current++; } if ((current == input.Length) || (current == dateStartIndex)) { return(false); // we couldn't find the closing '"' or we have an empty quoted string. } DateTimeOffset temp; if (!HttpRuleParser.TryStringToDate(input.Substring(dateStartIndex, current - dateStartIndex), out temp)) { return(false); } date = temp; current++; // skip closing '"' current = current + HttpRuleParser.GetWhitespaceLength(input, current); } return(true); }
public void TryStringToDate_UseOfValidDateTimeStringsInDifferentFormats_ParsedCorrectly() { // We don't need extensive tests, since we let DateTimeOffset do the parsing. This test is just // to validate that we use the correct parameters when calling into DateTimeOffset.ToString(). // RFC1123 date/time value DateTimeOffset expected = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero); DateTimeOffset result = DateTimeOffset.MinValue; Assert.True(HttpRuleParser.TryStringToDate("Sun, 06 Nov 1994 08:49:37 GMT", out result)); Assert.Equal(expected, result); Assert.True(HttpRuleParser.TryStringToDate("Sun, 06 Nov 1994 08:49:37", out result)); Assert.Equal(expected, result); Assert.True(HttpRuleParser.TryStringToDate("6 Nov 1994 8:49:37 GMT", out result)); Assert.Equal(expected, result); Assert.True(HttpRuleParser.TryStringToDate("6 Nov 1994 8:49:37", out result)); Assert.Equal(expected, result); Assert.True(HttpRuleParser.TryStringToDate("Sun, 06 Nov 94 08:49:37", out result)); Assert.Equal(expected, result); Assert.True(HttpRuleParser.TryStringToDate("6 Nov 94 8:49:37", out result)); Assert.Equal(expected, result); // RFC850 date/time value Assert.True(HttpRuleParser.TryStringToDate("Sunday, 06-Nov-94 08:49:37 GMT", out result)); Assert.Equal(expected, result); Assert.True(HttpRuleParser.TryStringToDate("Sunday, 6-Nov-94 8:49:37", out result)); Assert.Equal(expected, result); // ANSI C's asctime() format Assert.True(HttpRuleParser.TryStringToDate("Sun Nov 06 08:49:37 1994", out result)); Assert.Equal(expected, result); Assert.True(HttpRuleParser.TryStringToDate("Sun Nov 6 8:49:37 1994", out result)); Assert.Equal(expected, result); // RFC5322 date/time expected = new DateTimeOffset(1997, 11, 8, 9, 55, 6, new TimeSpan(-6, 0, 0)); Assert.True(HttpRuleParser.TryStringToDate("Sat, 08 Nov 1997 09:55:06 -0600", out result)); Assert.Equal(expected, result); expected = new DateTimeOffset(1997, 11, 8, 9, 55, 6, TimeSpan.Zero); Assert.True(HttpRuleParser.TryStringToDate("8 Nov 1997 9:55:6", out result)); Assert.Equal(expected, result); expected = new DateTimeOffset(1997, 11, 8, 9, 55, 6, new TimeSpan(2, 0, 0)); Assert.True(HttpRuleParser.TryStringToDate("Sat, 8 Nov 1997 9:55:6 +0200", out result)); Assert.Equal(expected, result); }
// Gets a parameter of the given name and attempts to extract a date. // Returns null if the parameter is not present or the format is incorrect. private DateTimeOffset?GetDate(string parameter) { NameValueHeaderValue dateParameter = NameValueHeaderValue.Find(_parameters, parameter); DateTimeOffset date; if (dateParameter != null) { string dateString = dateParameter.Value; // Should have quotes, remove them. if (IsQuoted(dateString)) { dateString = dateString.Substring(1, dateString.Length - 2); } if (HttpRuleParser.TryStringToDate(dateString, out date)) { return(date); } } return(null); }
private static int GetRangeConditionLength(StringSegment input, int startIndex, out RangeConditionHeaderValue?parsedValue) { Contract.Requires(startIndex >= 0); parsedValue = null; // Make sure we have at least 2 characters if (StringSegment.IsNullOrEmpty(input) || (startIndex + 1 >= input.Length)) { return(0); } var current = startIndex; // Caller must remove leading whitespaces. DateTimeOffset date = DateTimeOffset.MinValue; EntityTagHeaderValue?entityTag = null; // Entity tags are quoted strings optionally preceded by "W/". By looking at the first two character we // can determine whether the string is en entity tag or a date. var firstChar = input[current]; var secondChar = input[current + 1]; if ((firstChar == '\"') || (((firstChar == 'w') || (firstChar == 'W')) && (secondChar == '/'))) { // trailing whitespaces are removed by GetEntityTagLength() var entityTagLength = EntityTagHeaderValue.GetEntityTagLength(input, current, out entityTag); if (entityTagLength == 0) { return(0); } current = current + entityTagLength; // RangeConditionHeaderValue only allows 1 value. There must be no delimiter/other chars after an // entity tag. if (current != input.Length) { return(0); } } else { if (!HttpRuleParser.TryStringToDate(input.Subsegment(current), out date)) { return(0); } // If we got a valid date, then the parser consumed the whole string (incl. trailing whitespaces). current = input.Length; } parsedValue = new RangeConditionHeaderValue(); if (entityTag == null) { parsedValue._lastModified = date; } else { parsedValue._entityTag = entityTag; } return(current - startIndex); }
internal static int GetRetryConditionLength(string input, int startIndex, out object parsedValue) { Debug.Assert(startIndex >= 0); parsedValue = null; if (string.IsNullOrEmpty(input) || (startIndex >= input.Length)) { return(0); } int current = startIndex; // Caller must remove leading whitespace. DateTimeOffset date = DateTimeOffset.MinValue; int deltaSeconds = -1; // use -1 to indicate that the value was not set. 'delta' values are always >=0 // We either have a timespan or a date/time value. Determine which one we have by looking at the first char. // If it is a number, we have a timespan, otherwise we assume we have a date. char firstChar = input[current]; if ((firstChar >= '0') && (firstChar <= '9')) { int deltaStartIndex = current; int deltaLength = HttpRuleParser.GetNumberLength(input, current, false); // The value must be in the range 0..2^31 if ((deltaLength == 0) || (deltaLength > HttpRuleParser.MaxInt32Digits)) { return(0); } current = current + deltaLength; current = current + HttpRuleParser.GetWhitespaceLength(input, current); // RetryConditionHeaderValue only allows 1 value. There must be no delimiter/other chars after 'delta' if (current != input.Length) { return(0); } if (!HeaderUtilities.TryParseInt32(input.Substring(deltaStartIndex, deltaLength), out deltaSeconds)) { return(0); // int.TryParse() may return 'false' if the value has 10 digits and is > Int32.MaxValue. } } else { if (!HttpRuleParser.TryStringToDate(input.Substring(current), out date)) { return(0); } // If we got a valid date, then the parser consumed the whole string (incl. trailing whitespace). current = input.Length; } RetryConditionHeaderValue result = new RetryConditionHeaderValue(); if (deltaSeconds == -1) // we didn't change delta, so we must have found a date. { result._date = date; } else { result._delta = new TimeSpan(0, 0, deltaSeconds); } parsedValue = result; return(current - startIndex); }
public static bool TryParseDate(StringSegment input, out DateTimeOffset result) { return(HttpRuleParser.TryStringToDate(input, out result)); }
// name=value; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite={Strict|Lax|None}; httponly private static int GetSetCookieLength(StringSegment input, int startIndex, out SetCookieHeaderValue?parsedValue) { Contract.Requires(startIndex >= 0); var offset = startIndex; parsedValue = null; if (StringSegment.IsNullOrEmpty(input) || (offset >= input.Length)) { return(0); } var result = new SetCookieHeaderValue(); // The caller should have already consumed any leading whitespace, commas, etc.. // Name=value; // Name var itemLength = HttpRuleParser.GetTokenLength(input, offset); if (itemLength == 0) { return(0); } result._name = input.Subsegment(offset, itemLength); offset += itemLength; // = (no spaces) if (!ReadEqualsSign(input, ref offset)) { return(0); } // value or "quoted value" // The value may be empty result._value = CookieHeaderParserShared.GetCookieValue(input, ref offset); // *(';' SP cookie-av) while (offset < input.Length) { if (input[offset] == ',') { // Divider between headers break; } if (input[offset] != ';') { // Expecting a ';' between parameters return(0); } offset++; offset += HttpRuleParser.GetWhitespaceLength(input, offset); // cookie-av = expires-av / max-age-av / domain-av / path-av / secure-av / samesite-av / httponly-av / extension-av itemLength = HttpRuleParser.GetTokenLength(input, offset); if (itemLength == 0) { // Trailing ';' or leading into garbage. Let the next parser fail. break; } var token = input.Subsegment(offset, itemLength); offset += itemLength; // expires-av = "Expires=" sane-cookie-date if (StringSegment.Equals(token, ExpiresToken, StringComparison.OrdinalIgnoreCase)) { // = (no spaces) if (!ReadEqualsSign(input, ref offset)) { return(0); } // We don't want to include comma, becouse date may contain it (eg. Sun, 06 Nov...) var dateString = ReadToSemicolonOrEnd(input, ref offset, includeComma: false); DateTimeOffset expirationDate; if (!HttpRuleParser.TryStringToDate(dateString, out expirationDate)) { // Invalid expiration date, abort return(0); } result.Expires = expirationDate; } // max-age-av = "Max-Age=" non-zero-digit *DIGIT else if (StringSegment.Equals(token, MaxAgeToken, StringComparison.OrdinalIgnoreCase)) { // = (no spaces) if (!ReadEqualsSign(input, ref offset)) { return(0); } itemLength = HttpRuleParser.GetNumberLength(input, offset, allowDecimal: false); if (itemLength == 0) { return(0); } var numberString = input.Subsegment(offset, itemLength); long maxAge; if (!HeaderUtilities.TryParseNonNegativeInt64(numberString, out maxAge)) { // Invalid expiration date, abort return(0); } result.MaxAge = TimeSpan.FromSeconds(maxAge); offset += itemLength; } // domain-av = "Domain=" domain-value // domain-value = <subdomain> ; defined in [RFC1034], Section 3.5, as enhanced by [RFC1123], Section 2.1 else if (StringSegment.Equals(token, DomainToken, StringComparison.OrdinalIgnoreCase)) { // = (no spaces) if (!ReadEqualsSign(input, ref offset)) { return(0); } // We don't do any detailed validation on the domain. result.Domain = ReadToSemicolonOrEnd(input, ref offset); } // path-av = "Path=" path-value // path-value = <any CHAR except CTLs or ";"> else if (StringSegment.Equals(token, PathToken, StringComparison.OrdinalIgnoreCase)) { // = (no spaces) if (!ReadEqualsSign(input, ref offset)) { return(0); } // We don't do any detailed validation on the path. result.Path = ReadToSemicolonOrEnd(input, ref offset); } // secure-av = "Secure" else if (StringSegment.Equals(token, SecureToken, StringComparison.OrdinalIgnoreCase)) { result.Secure = true; } // samesite-av = "SameSite=" samesite-value // samesite-value = "Strict" / "Lax" / "None" else if (StringSegment.Equals(token, SameSiteToken, StringComparison.OrdinalIgnoreCase)) { if (!ReadEqualsSign(input, ref offset)) { result.SameSite = SameSiteMode.Unspecified; } else { var enforcementMode = ReadToSemicolonOrEnd(input, ref offset); if (StringSegment.Equals(enforcementMode, SameSiteStrictToken, StringComparison.OrdinalIgnoreCase)) { result.SameSite = SameSiteMode.Strict; } else if (StringSegment.Equals(enforcementMode, SameSiteLaxToken, StringComparison.OrdinalIgnoreCase)) { result.SameSite = SameSiteMode.Lax; } else if (StringSegment.Equals(enforcementMode, SameSiteNoneToken, StringComparison.OrdinalIgnoreCase)) { result.SameSite = SameSiteMode.None; } else { result.SameSite = SameSiteMode.Unspecified; } } } // httponly-av = "HttpOnly" else if (StringSegment.Equals(token, HttpOnlyToken, StringComparison.OrdinalIgnoreCase)) { result.HttpOnly = true; } // extension-av = <any CHAR except CTLs or ";"> else { var tokenStart = offset - itemLength; ReadToSemicolonOrEnd(input, ref offset, includeComma: true); result.Extensions.Add(input.Subsegment(tokenStart, offset - tokenStart)); } } parsedValue = result; return(offset - startIndex); }