Example #1
0
        // Segments have the following order:
        // 5 - Literal segments
        // 4 - Multi-part segments && Constrained parameter segments
        // 3 - Unconstrained parameter segements
        // 2 - Constrained wildcard parameter segments
        // 1 - Unconstrained wildcard parameter segments
        private static int ComputeGenerationDigit(TemplateSegment segment)
        {
            if (segment.Parts.Count > 1)
            {
                return(4);
            }

            var part = segment.Parts[0];

            if (part.IsLiteral)
            {
                return(5);
            }
            else
            {
                Debug.Assert(part.IsParameter);
                var digit = part.IsCatchAll ? 1 :  3;

                if (part.InlineConstraints != null && part.InlineConstraints.Any())
                {
                    digit++;
                }

                return(digit);
            }
        }
Example #2
0
        public void ToRoutePatternPathSegment()
        {
            // Arrange
            var literalPartA    = RoutePatternFactory.LiteralPart("A");
            var paramPartB      = RoutePatternFactory.ParameterPart("B");
            var paramPartC      = RoutePatternFactory.ParameterPart("C");
            var paramPartD      = RoutePatternFactory.ParameterPart("D");
            var separatorPartE  = RoutePatternFactory.SeparatorPart("E");
            var templateSegment = new TemplateSegment(RoutePatternFactory.Segment(paramPartC, literalPartA, separatorPartE, paramPartB));

            // Act
            var routePatternPathSegment = templateSegment.ToRoutePatternPathSegment();

            templateSegment.Parts[1] = new TemplatePart(RoutePatternFactory.ParameterPart("D"));
            templateSegment.Parts.RemoveAt(0);

            // Assert
            Assert.Equal(4, routePatternPathSegment.Parts.Count);
            Assert.IsType <RoutePatternParameterPart>(routePatternPathSegment.Parts[0]);
            Assert.Equal(paramPartC.Name, ((RoutePatternParameterPart)routePatternPathSegment.Parts[0]).Name);
            Assert.IsType <RoutePatternLiteralPart>(routePatternPathSegment.Parts[1]);
            Assert.Equal(literalPartA.Content, ((RoutePatternLiteralPart)routePatternPathSegment.Parts[1]).Content);
            Assert.IsType <RoutePatternSeparatorPart>(routePatternPathSegment.Parts[2]);
            Assert.Equal(separatorPartE.Content, ((RoutePatternSeparatorPart)routePatternPathSegment.Parts[2]).Content);
            Assert.IsType <RoutePatternParameterPart>(routePatternPathSegment.Parts[3]);
            Assert.Equal(paramPartB.Name, ((RoutePatternParameterPart)routePatternPathSegment.Parts[3]).Name);
        }
Example #3
0
        // Segments have the following order:
        // 1 - Literal segments
        // 2 - Constrained parameter segments / Multi-part segments
        // 3 - Unconstrained parameter segments
        // 4 - Constrained wildcard parameter segments
        // 5 - Unconstrained wildcard parameter segments
        private static int ComputeMatchDigit(TemplateSegment segment)
        {
            if (segment.Parts.Count > 1)
            {
                // Multi-part segments should appear after literal segments and along with parameter segments
                return(2);
            }

            var part = segment.Parts[0];

            // Literal segments always go first
            if (part.IsLiteral)
            {
                return(1);
            }
            else
            {
                Debug.Assert(part.IsParameter);
                var digit = part.IsCatchAll ? 5 : 3;

                // If there is a route constraint for the parameter, reduce order by 1
                // Constrained parameters end up with order 2, Constrained catch alls end up with order 4
                if (part.InlineConstraints != null && part.InlineConstraints.Any())
                {
                    digit--;
                }

                return(digit);
            }
        }
Example #4
0
        // Segments have the following order:
        // 5 - Literal segments
        // 4 - Multi-part segments && Constrained parameter segments
        // 3 - Unconstrained parameter segements
        // 2 - Constrained wildcard parameter segments
        // 1 - Unconstrained wildcard parameter segments
        private static int ComputeGenerationDigit(TemplateSegment segment)
        {
            if(segment.Parts.Count > 1)
            {
                return 4;
            }

            var part = segment.Parts[0];
            if(part.IsLiteral)
            {
                return 5;
            }
            else
            {
                Debug.Assert(part.IsParameter);
                var digit = part.IsCatchAll ? 1 :  3;

                if (part.InlineConstraints != null && part.InlineConstraints.Any())
                {
                    digit++;
                }

                return digit;
            }
        }
Example #5
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 foo/bar.moo it will match right away
            // giving p3 value of moo. But if the request is foo/bar, we start matching from the
            // rightmost giving p3 the value of bar, 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 bar 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));
            }
        }
Example #6
0
 public static string ToTemplateSegmentString(this TemplateSegment templateSegment) =>
 string.Join(string.Empty, templateSegment.Parts.Select(ToTemplatePartString));
        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 = Resources.TemplateRoute_MismatchedParameter;
                        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);
            }
        }
        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);
        }
        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);
            }
        }
        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);
            }
        }
Example #11
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);
        }
Example #12
0
        // Segments have the following order:
        // 1 - Literal segments
        // 2 - Constrained parameter segments / Multi-part segments
        // 3 - Unconstrained parameter segments
        // 4 - Constrained wildcard parameter segments
        // 5 - Unconstrained wildcard parameter segments
        private static int ComputeMatchDigit(TemplateSegment segment)
        {
            if (segment.Parts.Count > 1)
            {
                // Multi-part segments should appear after literal segments and along with parameter segments
                return 2;
            }

            var part = segment.Parts[0];
            // Literal segments always go first
            if (part.IsLiteral)
            {
                return 1;
            }
            else
            {
                Debug.Assert(part.IsParameter);
                var digit = part.IsCatchAll ? 5 : 3;

                // If there is a route constraint for the parameter, reduce order by 1
                // Constrained parameters end up with order 2, Constrained catch alls end up with order 4
                if (part.InlineConstraints != null && part.InlineConstraints.Any())
                {
                    digit--;
                }

                return digit;
            }
        }