private bool MatchComplexSegment(TemplateSegment routeSegment, string requestSegment, IDictionary <string, object> defaults, RouteValueDictionary values) { Contract.Assert(routeSegment != null); Contract.Assert(routeSegment.Parts.Count > 1); // Find last literal segment and get its last index in the string var lastIndex = requestSegment.Length; var indexOfLastSegmentUsed = routeSegment.Parts.Count - 1; TemplatePart parameterNeedsValue = null; // Keeps track of a parameter segment that is pending a value TemplatePart lastLiteral = null; // Keeps track of the left-most literal we've encountered while (indexOfLastSegmentUsed >= 0) { var newLastIndex = lastIndex; var part = routeSegment.Parts[indexOfLastSegmentUsed]; if (part.IsParameter) { // Hold on to the parameter so that we can fill it in when we locate the next literal parameterNeedsValue = part; } else { Contract.Assert(part.IsLiteral); lastLiteral = part; var startIndex = lastIndex - 1; // If we have a pending parameter subsegment, we must leave at least one character for that if (parameterNeedsValue != null) { startIndex--; } if (startIndex < 0) { return(false); } var indexOfLiteral = requestSegment.LastIndexOf(part.Text, startIndex, StringComparison.OrdinalIgnoreCase); if (indexOfLiteral == -1) { // If we couldn't find this literal index, this segment cannot match return(false); } // If the first subsegment is a literal, it must match at the right-most extent of the request URI. // Without this check if your route had "/Foo/" we'd match the request URI "/somethingFoo/". // This check is related to the check we do at the very end of this function. if (indexOfLastSegmentUsed == (routeSegment.Parts.Count - 1)) { if ((indexOfLiteral + part.Text.Length) != requestSegment.Length) { return(false); } } newLastIndex = indexOfLiteral; } if ((parameterNeedsValue != null) && (((lastLiteral != null) && (part.IsLiteral)) || (indexOfLastSegmentUsed == 0))) { // If we have a pending parameter that needs a value, grab that value int parameterStartIndex; int parameterTextLength; if (lastLiteral == null) { if (indexOfLastSegmentUsed == 0) { parameterStartIndex = 0; } else { parameterStartIndex = newLastIndex; Contract.Assert(false, "indexOfLastSegementUsed should always be 0 from the check above"); } parameterTextLength = lastIndex; } else { // If we're getting a value for a parameter that is somewhere in the middle of the segment if ((indexOfLastSegmentUsed == 0) && (part.IsParameter)) { parameterStartIndex = 0; parameterTextLength = lastIndex; } else { parameterStartIndex = newLastIndex + lastLiteral.Text.Length; parameterTextLength = lastIndex - parameterStartIndex; } } var parameterValueString = requestSegment.Substring(parameterStartIndex, parameterTextLength); if (string.IsNullOrEmpty(parameterValueString)) { // If we're here that means we have a segment that contains multiple sub-segments. // For these segments all parameters must have non-empty values. If the parameter // has an empty value it's not a match. return(false); } else { // If there's a value in the segment for this parameter, use the subsegment value values.Add(parameterNeedsValue.Name, parameterValueString); } parameterNeedsValue = null; lastLiteral = null; } lastIndex = newLastIndex; indexOfLastSegmentUsed--; } // If the last subsegment is a parameter, it's OK that we didn't parse all the way to the left extent of // the string since the parameter will have consumed all the remaining text anyway. If the last subsegment // is a literal then we *must* have consumed the entire text in that literal. Otherwise we end up matching // the route "Foo" to the request URI "somethingFoo". Thus we have to check that we parsed the *entire* // request URI in order for it to be a match. // This check is related to the check we do earlier in this function for LiteralSubsegments. return((lastIndex == 0) || routeSegment.Parts[0].IsParameter); }
private static bool ParseLiteral(TemplateParserContext context, TemplateSegment segment) { context.Mark(); string encoded; while (true) { if (context.Current == Separator) { encoded = context.Capture(); context.Back(); break; } else if (context.Current == OpenBrace) { if (!context.Next()) { // This is a dangling open-brace, which is not allowed context.Error = Resources.TemplateRoute_MismatchedParameter; return(false); } if (context.Current == OpenBrace) { // This is an 'escaped' brace in a literal, like "{{foo" - keep going. } else { // We've just seen the start of a parameter, so back up and return context.Back(); encoded = context.Capture(); context.Back(); break; } } else if (context.Current == CloseBrace) { if (!context.Next()) { // This is a dangling close-brace, which is not allowed context.Error = Resources.TemplateRoute_MismatchedParameter; return(false); } if (context.Current == CloseBrace) { // This is an 'escaped' brace in a literal, like "{{foo" - keep going. } else { // This is an unbalanced close-brace, which is not allowed context.Error = Resources.TemplateRoute_MismatchedParameter; return(false); } } if (!context.Next()) { encoded = context.Capture(); break; } } var decoded = encoded.Replace("}}", "}").Replace("{{", "}"); if (IsValidLiteral(context, decoded)) { segment.Parts.Add(TemplatePart.CreateLiteral(decoded)); return(true); } else { return(false); } }