Beispiel #1
0
        private bool MatchComplexSegment(
            TemplateSegment routeSegment,
            string requestSegment,
            IReadOnlyDictionary <string, object> defaults,
            RouteValueDictionary values)
        {
            var indexOfLastSegment = routeSegment.Parts.Count - 1;

            // We match the request to the template starting at the rightmost parameter
            // If the last segment of template is optional, then request can match the
            // template with or without the last parameter. So we start with regular matching,
            // but if it doesn't match, we start with next to last parameter. Example:
            // Template: {p1}/{p2}.{p3?}. If the request is one/two.three it will match right away
            // giving p3 value of three. But if the request is one/two, we start matching from the
            // rightmost giving p3 the value of two, then we end up not matching the segment.
            // In this case we start again from p2 to match the request and we succeed giving
            // the value two to p2
            if (routeSegment.Parts[indexOfLastSegment].IsOptional &&
                routeSegment.Parts[indexOfLastSegment - 1].IsOptionalSeperator)
            {
                if (MatchComplexSegmentCore(routeSegment, requestSegment, Defaults, values, indexOfLastSegment))
                {
                    return(true);
                }
                else
                {
                    if (requestSegment.EndsWith(
                            routeSegment.Parts[indexOfLastSegment - 1].Text,
                            StringComparison.OrdinalIgnoreCase))
                    {
                        return(false);
                    }

                    return(MatchComplexSegmentCore(
                               routeSegment,
                               requestSegment,
                               Defaults,
                               values,
                               indexOfLastSegment - 2));
                }
            }
            else
            {
                return(MatchComplexSegmentCore(routeSegment, requestSegment, Defaults, values, indexOfLastSegment));
            }
        }
Beispiel #2
0
        private static bool ParseSegment(TemplateParserContext context, List <TemplateSegment> segments)
        {
            Debug.Assert(context != null);
            Debug.Assert(segments != null);

            var segment = new TemplateSegment();

            while (true)
            {
                if (context.Current == OpenBrace)
                {
                    if (!context.Next())
                    {
                        // This is a dangling open-brace, which is not allowed
                        context.Error = "context does not have next";
                        return(false);
                    }

                    if (context.Current == OpenBrace)
                    {
                        // This is an 'escaped' brace in a literal, like "{{foo"
                        context.Back();
                        if (!ParseLiteral(context, segment))
                        {
                            return(false);
                        }
                    }
                    else
                    {
                        // This is the inside of a parameter
                        if (!ParseParameter(context, segment))
                        {
                            return(false);
                        }
                    }
                }
                else if (context.Current == Separator)
                {
                    // We've reached the end of the segment
                    break;
                }
                else
                {
                    if (!ParseLiteral(context, segment))
                    {
                        return(false);
                    }
                }

                if (!context.Next())
                {
                    // We've reached the end of the string
                    break;
                }
            }

            if (IsSegmentValid(context, segment))
            {
                segments.Add(segment);
                return(true);
            }
            else
            {
                return(false);
            }
        }
Beispiel #3
0
        private static bool IsSegmentValid(TemplateParserContext context, TemplateSegment segment)
        {
            // If a segment has multiple parts, then it can't contain a catch all.
            for (var i = 0; i < segment.Parts.Count; i++)
            {
                var part = segment.Parts[i];
                if (part.IsParameter && part.IsCatchAll && segment.Parts.Count > 1)
                {
                    context.Error = Resources.TemplateRoute_CannotHaveCatchAllInMultiSegment;
                    return(false);
                }
            }

            // if a segment has multiple parts, then only the last one parameter can be optional
            // if it is following a optional seperator.
            for (var i = 0; i < segment.Parts.Count; i++)
            {
                var part = segment.Parts[i];

                if (part.IsParameter && part.IsOptional && segment.Parts.Count > 1)
                {
                    // This optional parameter is the last part in the segment
                    if (i == segment.Parts.Count - 1)
                    {
                        if (!segment.Parts[i - 1].IsLiteral)
                        {
                            // The optional parameter is preceded by something that is not a literal.
                            // Example of error message:
                            // "In the segment '{RouteValue}{param?}', the optional parameter 'param' is preceded
                            // by an invalid segment '{RouteValue}'. Only a period (.) can precede an optional parameter.
                            context.Error = string.Format(
                                Resources.TemplateRoute_OptionalParameterCanbBePrecededByPeriod,
                                segment.DebuggerToString(),
                                part.Name,
                                segment.Parts[i - 1].DebuggerToString());

                            return(false);
                        }
                        else if (segment.Parts[i - 1].Text != PeriodString)
                        {
                            // The optional parameter is preceded by a literal other than period.
                            // Example of error message:
                            // "In the segment '{RouteValue}-{param?}', the optional parameter 'param' is preceded
                            // by an invalid segment '-'. Only a period (.) can precede an optional parameter.
                            context.Error = string.Format(
                                Resources.TemplateRoute_OptionalParameterCanbBePrecededByPeriod,
                                segment.DebuggerToString(),
                                part.Name,
                                segment.Parts[i - 1].Text);

                            return(false);
                        }

                        segment.Parts[i - 1].IsOptionalSeperator = true;
                    }
                    else
                    {
                        // This optional parameter is not the last one in the segment
                        // Example:
                        // An optional parameter must be at the end of the segment.In the segment '{RouteValue?})',
                        // optional parameter 'RouteValue' is followed by ')'
                        var nextPart        = segment.Parts[i + 1];
                        var invalidPartText = nextPart.IsParameter ? nextPart.Name : nextPart.Text;

                        context.Error = string.Format(
                            Resources.TemplateRoute_OptionalParameterHasTobeTheLast,
                            segment.DebuggerToString(),
                            segment.Parts[i].Name,
                            invalidPartText);

                        return(false);
                    }
                }
            }

            // A segment cannot contain two consecutive parameters
            var isLastSegmentParameter = false;

            for (var i = 0; i < segment.Parts.Count; i++)
            {
                var part = segment.Parts[i];
                if (part.IsParameter && isLastSegmentParameter)
                {
                    context.Error = Resources.TemplateRoute_CannotHaveConsecutiveParameters;
                    return(false);
                }

                isLastSegmentParameter = part.IsParameter;
            }

            return(true);
        }
Beispiel #4
0
        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);
            }
        }
Beispiel #5
0
        private static bool ParseParameter(TemplateParserContext context, TemplateSegment segment)
        {
            context.Mark();

            while (true)
            {
                if (context.Current == OpenBrace)
                {
                    // This is an open brace inside of a parameter, it has to be escaped
                    if (context.Next())
                    {
                        if (context.Current != OpenBrace)
                        {
                            // If we see something like "{p1:regex(^\d{3", we will come here.
                            context.Error = Resources.TemplateRoute_UnescapedBrace;
                            return(false);
                        }
                    }
                    else
                    {
                        // This is a dangling open-brace, which is not allowed
                        // Example: "{p1:regex(^\d{"
                        context.Error = Resources.TemplateRoute_MismatchedParameter;
                        return(false);
                    }
                }
                else if (context.Current == CloseBrace)
                {
                    // When we encounter Closed brace here, it either means end of the parameter or it is a closed
                    // brace in the parameter, in that case it needs to be escaped.
                    // Example: {p1:regex(([}}])\w+}. First pair is escaped one and last marks end of the parameter
                    if (!context.Next())
                    {
                        // This is the end of the string -and we have a valid parameter
                        context.Back();
                        break;
                    }

                    if (context.Current == CloseBrace)
                    {
                        // This is an 'escaped' brace in a parameter name
                    }
                    else
                    {
                        // This is the end of the parameter
                        context.Back();
                        break;
                    }
                }

                if (!context.Next())
                {
                    // This is a dangling open-brace, which is not allowed
                    context.Error = Resources.TemplateRoute_MismatchedParameter;
                    return(false);
                }
            }

            var rawParameter = context.Capture();
            var decoded      = rawParameter.Replace("}}", "}").Replace("{{", "{");

            // At this point, we need to parse the raw name for inline constraint,
            // default values and optional parameters.
            var templatePart = InlineRouteParameterParser.ParseRouteParameter(decoded);

            if (templatePart.IsCatchAll && templatePart.IsOptional)
            {
                context.Error = Resources.TemplateRoute_CatchAllCannotBeOptional;
                return(false);
            }

            if (templatePart.IsOptional && templatePart.DefaultValue != null)
            {
                // Cannot be optional and have a default value.
                // The only way to declare an optional parameter is to have a ? at the end,
                // hence we cannot have both default value and optional parameter within the template.
                // A workaround is to add it as a separate entry in the defaults argument.
                context.Error = Resources.TemplateRoute_OptionalCannotHaveDefaultValue;
                return(false);
            }

            var parameterName = templatePart.Name;

            if (IsValidParameterName(context, parameterName))
            {
                segment.Parts.Add(templatePart);
                return(true);
            }
            else
            {
                return(false);
            }
        }
Beispiel #6
0
        private bool MatchComplexSegmentCore(
            TemplateSegment routeSegment,
            string requestSegment,
            IReadOnlyDictionary <string, object> defaults,
            RouteValueDictionary values,
            int indexOfLastSegmentUsed)
        {
            Debug.Assert(routeSegment != null);
            Debug.Assert(routeSegment.Parts.Count > 1);

            // Find last literal segment and get its last index in the string
            var lastIndex = requestSegment.Length;

            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

            var outValues = new RouteValueDictionary();

            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
                {
                    Debug.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;
                            Debug.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
                        outValues.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.
            if (lastIndex == 0 || routeSegment.Parts[0].IsParameter)
            {
                foreach (var item in outValues)
                {
                    values.Add(item.Key, item.Value);
                }

                return(true);
            }

            return(false);
        }