/// <summary>
        /// Initialises a new instance of the <see cref="OrderByProperty"/> class.
        /// </summary>
        /// <param name="rawValue">The raw value.</param>
        /// <param name="model">The model.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="rawValue"/> or <paramref name="model"/> are null.</exception>
        /// <exception cref="ODataException">Thrown if there is an error parsing the <paramref name="rawValue"/>.</exception>
        internal OrderByProperty(string rawValue, EdmComplexType model)
        {
            RawValue = rawValue ?? throw new ArgumentNullException(nameof(rawValue));

            if (model is null)
            {
                throw new ArgumentNullException(nameof(model));
            }

            if (rawValue.IndexOf(' ') == -1)
            {
                PropertyPath = PropertyPath.For(rawValue, model);
            }
            else
            {
                PropertyPath = PropertyPath.For(rawValue.SubstringBefore(' '), model);

                if (rawValue.EndsWith(" asc", StringComparison.Ordinal))
                {
                    Direction = OrderByDirection.Ascending;
                }
                else if (rawValue.EndsWith(" desc", StringComparison.Ordinal))
                {
                    Direction = OrderByDirection.Descending;
                }
                else
                {
                    throw ODataException.BadRequest(ExceptionMessage.InvalidOrderByDirection(rawValue.SubstringAfter(' '), PropertyPath.Property.Name), "$orderby");
                }
            }
        }
Example #2
0
        internal static UnaryOperatorKind ToUnaryOperatorKind(this string operatorType)
        {
            switch (operatorType)
            {
            case "not":
                return(UnaryOperatorKind.Not);

            default:
                throw ODataException.BadRequest(ExceptionMessage.InvalidOperator(operatorType), "$filter");
            }
        }
        /// <summary>
        /// Gets the <see cref="EdmProperty"/> declared in this type with the specified name.
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <returns>The <see cref="EdmProperty"/> declared in this type with the specified name.</returns>
        /// <exception cref="ODataException">The type does not contain a property with the specified name.</exception>
        public EdmProperty GetProperty(string name)
        {
            for (int i = 0; i < Properties.Count; i++)
            {
                EdmProperty property = Properties[i];

                if (property.Name.Equals(name, StringComparison.Ordinal))
                {
                    return(property);
                }
            }

            throw ODataException.BadRequest(ExceptionMessage.EdmTypeDoesNotContainProperty(FullName, name), FullName);
        }
        /// <summary>
        /// Validates the specified query options.
        /// </summary>
        /// <param name="queryOptions">The query options.</param>
        /// <param name="validationSettings">The validation settings.</param>
        /// <exception cref="ODataException">Thrown if the validation fails.</exception>
        internal static void Validate(ODataQueryOptions queryOptions, ODataValidationSettings validationSettings)
        {
            if (queryOptions.RawValues.Top is null)
            {
                return;
            }

            if ((validationSettings.AllowedQueryOptions & AllowedQueryOptions.Top) != AllowedQueryOptions.Top)
            {
                throw ODataException.NotImplemented("The query option $top is not implemented by this service", "$top");
            }

            if (queryOptions.Top.Value < 0 || queryOptions.Top.Value > validationSettings.MaxTop)
            {
                throw ODataException.BadRequest($"The integer value for $top is invalid, it must be an integer greater than zero and below the max value of {validationSettings.MaxTop.ToString(CultureInfo.InvariantCulture)} allowed by this service", "$top");
            }
        }
        /// <summary>
        /// Validates the specified query options.
        /// </summary>
        /// <param name="queryOptions">The query options.</param>
        /// <param name="validationSettings">The validation settings.</param>
        /// <exception cref="ODataException">Thrown if the validation fails.</exception>
        internal static void Validate(ODataQueryOptions queryOptions, ODataValidationSettings validationSettings)
        {
            if (queryOptions.RawValues.Skip is null)
            {
                return;
            }

            if ((validationSettings.AllowedQueryOptions & AllowedQueryOptions.Skip) != AllowedQueryOptions.Skip)
            {
                throw ODataException.NotImplemented("The query option $skip is not implemented by this service", "$skip");
            }

            if (queryOptions.Skip.Value < 0)
            {
                throw ODataException.BadRequest("The value for OData query $skip must be a non-negative numeric value", "$skip");
            }
        }
Example #6
0
        private static int?ParseInt(string rawValue)
        {
            if (rawValue is null)
            {
                return(null);
            }

            string value = rawValue.SubstringAfter('=');

            if (int.TryParse(value, out int integer))
            {
                return(integer);
            }

            string queryOption = rawValue.SubstringBefore('=');

            throw ODataException.BadRequest(ExceptionMessage.QueryOptionValueMustBePositiveInteger(queryOption), queryOption);
        }
Example #7
0
        /// <summary>
        /// Validates the specified query options.
        /// </summary>
        /// <param name="queryOptions">The query options.</param>
        /// <param name="validationSettings">The validation settings.</param>
        /// <exception cref="ODataException">Thrown if the validation fails.</exception>
        internal static void Validate(ODataQueryOptions queryOptions, ODataValidationSettings validationSettings)
        {
            if (queryOptions.RawValues.Count is null)
            {
                return;
            }

            if ((validationSettings.AllowedQueryOptions & AllowedQueryOptions.Count) != AllowedQueryOptions.Count)
            {
                throw ODataException.NotImplemented("The query option $count is not implemented by this service", "$count");
            }

            if (queryOptions.RawValues.Count != "$count=true" &&
                queryOptions.RawValues.Count != "$count=false")
            {
                throw ODataException.BadRequest("The supplied value for OData query $count is invalid, valid options are 'true' and 'false'", "$count");
            }
        }
            internal QueryNode ParseQueryNode(Lexer lexer)
            {
                while (lexer.MoveNext())
                {
                    Token token = lexer.Current;

                    switch (token.TokenType)
                    {
                    case TokenType.And:
                    case TokenType.Or:
                        _nextBinaryOperatorKind = token.Value.ToBinaryOperatorKind();
                        UpdateExpressionTree();
                        break;

                    default:
                        _tokens.Enqueue(token);
                        break;
                    }
                }

                _nextBinaryOperatorKind = BinaryOperatorKind.None;
                UpdateExpressionTree();

                if (_groupingDepth != 0 || _nodeStack.Count != 1)
                {
                    throw ODataException.BadRequest(ExceptionMessage.UnableToParseFilter("an extra opening or missing closing parenthesis may be present"), "$filter");
                }

                QueryNode node = _nodeStack.Pop();

                if (node is BinaryOperatorNode binaryNode && binaryNode.Right is null)
                {
                    throw ODataException.BadRequest(ExceptionMessage.UnableToParseFilter($"the binary operator {binaryNode.OperatorKind.ToString()} has no right node"), "$filter");
                }

                return(node);
            }
            private QueryNode ParseQueryNode()
            {
                if (_tokens.Count == 0)
                {
                    throw ODataException.BadRequest(ExceptionMessage.UnableToParseFilter("an incomplete filter has been specified"), "$filter");
                }

                QueryNode node;

                switch (_tokens.Peek().TokenType)
                {
                case TokenType.FunctionName:
                    node = ParseFunctionCallNode();
                    break;

                case TokenType.OpenParentheses:
                    _groupingDepth++;
                    _tokens.Dequeue();
                    node = ParseQueryNode();
                    break;

                case TokenType.PropertyName:
                    node = ParsePropertyAccessNode();
                    break;

                case TokenType.UnaryOperator:
                    Token token = _tokens.Dequeue();
                    node = ParseQueryNode();
                    node = new UnaryOperatorNode(node, token.Value.ToUnaryOperatorKind());
                    break;

                default:
                    throw ODataException.BadRequest(ExceptionMessage.UnableToParseFilter($"unexpected {_tokens.Peek().Value}", _tokens.Peek().Position), "$filter");
                }

                return(node);
            }
Example #10
0
        internal bool MoveNext()
        {
            if (_content.Length == _position)
            {
                return(false);
            }

            for (int i = 0; i < s_tokenDefinitions.Length; i++)
            {
                TokenDefinition tokenDefinition = s_tokenDefinitions[i];

                Match match = tokenDefinition.Regex.Match(_content, _position);

                if (match.Success)
                {
                    if (tokenDefinition.Ignore)
                    {
                        _position += match.Length;
                        i          = -1;
                        continue;
                    }

                    Current    = tokenDefinition.CreateToken(match, _position);
                    _position += match.Length;

                    return(true);
                }
            }

            if (_content.Length != _position)
            {
                throw ODataException.BadRequest(ExceptionMessage.GenericUnableToParseFilter, "$filter");
            }

            return(false);
        }
Example #11
0
        /// <summary>
        /// Initialises a new instance of the <see cref="ODataRawQueryOptions"/> class.
        /// </summary>
        /// <param name="rawQuery">The raw query.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="rawQuery"/> is null.</exception>
        /// <exception cref="ODataException">Thrown if an error occurs parsing the <paramref name="rawQuery"/>.</exception>
        internal ODataRawQueryOptions(string rawQuery)
        {
            if (rawQuery is null)
            {
                throw new ArgumentNullException(nameof(rawQuery));
            }

            // Any + signs we want in the data should have been encoded as %2B,
            // so do the replace first otherwise we replace legitemate + signs!
            _rawQuery = rawQuery.Replace('+', ' ');

            if (_rawQuery.Length > 0)
            {
                foreach (string queryOption in _rawQuery.Slice('&', _rawQuery.IndexOf('?') + 1))
                {
                    // Decode the chunks to prevent splitting the query on an '&' which is actually part of a string value
                    string rawQueryOption = Uri.UnescapeDataString(queryOption);

                    if (rawQueryOption.StartsWith("$select=", StringComparison.Ordinal))
                    {
                        if (rawQueryOption.Equals("$select=", StringComparison.Ordinal))
                        {
                            throw ODataException.BadRequest(ExceptionMessage.QueryOptionValueCannotBeEmpty("$select"), "$select");
                        }

                        Select = rawQueryOption;
                    }
                    else if (rawQueryOption.StartsWith("$filter=", StringComparison.Ordinal))
                    {
                        if (rawQueryOption.Equals("$filter=", StringComparison.Ordinal))
                        {
                            throw ODataException.BadRequest(ExceptionMessage.QueryOptionValueCannotBeEmpty("$filter"), "$filter");
                        }

                        Filter = rawQueryOption;
                    }
                    else if (rawQueryOption.StartsWith("$orderby=", StringComparison.Ordinal))
                    {
                        if (rawQueryOption.Equals("$orderby=", StringComparison.Ordinal))
                        {
                            throw ODataException.BadRequest(ExceptionMessage.QueryOptionValueCannotBeEmpty("$orderby"), "$orderby");
                        }

                        OrderBy = rawQueryOption;
                    }
                    else if (rawQueryOption.StartsWith("$skip=", StringComparison.Ordinal))
                    {
                        if (rawQueryOption.Equals("$skip=", StringComparison.Ordinal))
                        {
                            throw ODataException.BadRequest(ExceptionMessage.QueryOptionValueCannotBeEmpty("$skip"), "$skip");
                        }

                        Skip = rawQueryOption;
                    }
                    else if (rawQueryOption.StartsWith("$top=", StringComparison.Ordinal))
                    {
                        if (rawQueryOption.Equals("$top=", StringComparison.Ordinal))
                        {
                            throw ODataException.BadRequest(ExceptionMessage.QueryOptionValueCannotBeEmpty("$top"), "$top");
                        }

                        Top = rawQueryOption;
                    }
                    else if (rawQueryOption.StartsWith("$count=", StringComparison.Ordinal))
                    {
                        if (rawQueryOption.Equals("$count=", StringComparison.Ordinal))
                        {
                            throw ODataException.BadRequest(ExceptionMessage.QueryOptionValueCannotBeEmpty("$count"), "$count");
                        }

                        Count = rawQueryOption;
                    }
                    else if (rawQueryOption.StartsWith("$format=", StringComparison.Ordinal))
                    {
                        if (rawQueryOption.Equals("$format=", StringComparison.Ordinal))
                        {
                            throw ODataException.BadRequest(ExceptionMessage.QueryOptionValueCannotBeEmpty("$format"), "$format");
                        }

                        Format = rawQueryOption;
                    }
                    else if (rawQueryOption.StartsWith("$expand=", StringComparison.Ordinal))
                    {
                        if (rawQueryOption.Equals("$expand=", StringComparison.Ordinal))
                        {
                            throw ODataException.BadRequest(ExceptionMessage.QueryOptionValueCannotBeEmpty("$expand"), "$expand");
                        }

                        Expand = rawQueryOption;
                    }
                    else if (rawQueryOption.StartsWith("$search=", StringComparison.Ordinal))
                    {
                        if (rawQueryOption.Equals("$search=", StringComparison.Ordinal))
                        {
                            throw ODataException.BadRequest(ExceptionMessage.QueryOptionValueCannotBeEmpty("$search"), "$search");
                        }

                        Search = rawQueryOption;
                    }
                    else if (rawQueryOption.StartsWith("$skiptoken=", StringComparison.Ordinal))
                    {
                        if (rawQueryOption.Equals("$skiptoken=", StringComparison.Ordinal))
                        {
                            throw ODataException.BadRequest(ExceptionMessage.QueryOptionValueCannotBeEmpty("$skiptoken"), "$skiptoken");
                        }

                        SkipToken = rawQueryOption;
                    }
                    else if (rawQueryOption.StartsWith("$", StringComparison.Ordinal))
                    {
                        string optionName = rawQueryOption.SubstringBefore('=');

                        throw ODataException.BadRequest(ExceptionMessage.UnsupportedQueryOption(optionName), optionName);
                    }
                }
            }
        }
Example #12
0
 internal static BinaryOperatorKind ToBinaryOperatorKind(this string operatorType)
 => s_operatorTypeMap.TryGetValue(operatorType, out BinaryOperatorKind binaryOperatorKind)
         ? binaryOperatorKind
         : throw ODataException.BadRequest(ExceptionMessage.InvalidOperator(operatorType), "$filter");
        internal static ConstantNode ParseConstantNode(Token token)
        {
            switch (token.TokenType)
            {
            case TokenType.Base64Binary:
                byte[] binaryValue = Convert.FromBase64String(token.Value);
                return(ConstantNode.Binary(token.Value, binaryValue));

            case TokenType.Date:
                if (DateTime.TryParseExact(token.Value, ParserSettings.ODataDateFormat, ParserSettings.CultureInfo, DateTimeStyles.AssumeLocal, out DateTime dateTimeValue))
                {
                    return(ConstantNode.Date(token.Value, dateTimeValue));
                }

                throw ODataException.BadRequest(ExceptionMessage.UnableToParseDate, "$filter");

            case TokenType.DateTimeOffset:
                if (DateTimeOffset.TryParse(token.Value, ParserSettings.CultureInfo, ParserSettings.DateTimeStyles, out DateTimeOffset dateTimeOffsetValue))
                {
                    return(ConstantNode.DateTimeOffset(token.Value, dateTimeOffsetValue));
                }

                throw ODataException.BadRequest(ExceptionMessage.UnableToParseDateTimeOffset, "$filter");

            case TokenType.Decimal:
                string decimalText = token.Value.EndsWith("m", StringComparison.OrdinalIgnoreCase)
                        ? token.Value.Substring(0, token.Value.Length - 1)
                        : token.Value;
                decimal decimalValue = decimal.Parse(decimalText, ParserSettings.CultureInfo);
                return(ConstantNode.Decimal(token.Value, decimalValue));

            case TokenType.Double:
                return(ParseDouble(token));

            case TokenType.Duration:
                string durationText = token.Value.Substring(9, token.Value.Length - 10)
                                      .Replace("P", string.Empty)
                                      .Replace("DT", ".")
                                      .Replace("H", ":")
                                      .Replace("M", ":")
                                      .Replace("S", string.Empty);
                var durationTimeSpanValue = TimeSpan.Parse(durationText, ParserSettings.CultureInfo);
                return(ConstantNode.Duration(token.Value, durationTimeSpanValue));

            case TokenType.EdmType:
                EdmType edmType = EdmType.GetEdmType(token.Value);
                return(ConstantNode.EdmTypeNode(token.Value, edmType));

            case TokenType.Enum:
                int         firstQuote        = token.Value.IndexOf('\'');
                string      edmEnumTypeName   = token.Value.Substring(0, firstQuote);
                EdmEnumType edmEnumType       = (EdmEnumType)EdmType.GetEdmType(edmEnumTypeName);
                string      edmEnumMemberName = token.Value.Substring(firstQuote + 1, token.Value.Length - firstQuote - 2);
                object      enumValue         = edmEnumType.GetClrValue(edmEnumMemberName);
                Type        constantNodeType  = typeof(ConstantNode <>).MakeGenericType(edmEnumType.ClrType);

                return((ConstantNode)Activator.CreateInstance(constantNodeType, BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { edmEnumType, token.Value, enumValue }, null));

            case TokenType.False:
                return(ConstantNode.False);

            case TokenType.Guid:
                var guidValue = Guid.ParseExact(token.Value, "D");
                return(ConstantNode.Guid(token.Value, guidValue));

            case TokenType.Integer:
                return(ParseInteger(token));

            case TokenType.Null:
                return(ConstantNode.Null);

            case TokenType.Single:
                string singleText  = token.Value.Substring(0, token.Value.Length - 1);
                float  singleValue = float.Parse(singleText, ParserSettings.CultureInfo);
                return(ConstantNode.Single(token.Value, singleValue));

            case TokenType.String:
                string stringText = token.Value.Trim('\'').Replace("''", "'");
                return(ConstantNode.String(token.Value, stringText));

            case TokenType.TimeOfDay:
                var timeSpanTimeOfDayValue = TimeSpan.Parse(token.Value, ParserSettings.CultureInfo);
                return(ConstantNode.Time(token.Value, timeSpanTimeOfDayValue));

            case TokenType.True:
                return(ConstantNode.True);

            default:
                throw new NotSupportedException(token.TokenType.ToString());
            }
        }
            private QueryNode ParseFunctionCallNode()
            {
                BinaryOperatorNode binaryNode = null;
                FunctionCallNode   node       = null;

                var stack = new Stack <FunctionCallNode>();

                while (_tokens.Count > 0)
                {
                    Token token = _tokens.Dequeue();

                    switch (token.TokenType)
                    {
                    case TokenType.Base64Binary:
                    case TokenType.Date:
                    case TokenType.DateTimeOffset:
                    case TokenType.Decimal:
                    case TokenType.Double:
                    case TokenType.Duration:
                    case TokenType.EdmType:
                    case TokenType.Enum:
                    case TokenType.False:
                    case TokenType.Guid:
                    case TokenType.Integer:
                    case TokenType.Null:
                    case TokenType.Single:
                    case TokenType.String:
                    case TokenType.TimeOfDay:
                    case TokenType.True:
                        ConstantNode constantNode = ConstantNodeParser.ParseConstantNode(token);

                        if (stack.Count > 0)
                        {
                            stack.Peek().AddParameter(constantNode);
                        }
                        else
                        {
                            if (binaryNode == null)
                            {
                                throw ODataException.BadRequest(ExceptionMessage.GenericUnableToParseFilter, "$filter");
                            }

                            binaryNode.Right = constantNode;
                        }

                        break;

                    case TokenType.BinaryOperator:
                        binaryNode = new BinaryOperatorNode(node, token.Value.ToBinaryOperatorKind(), null);
                        break;

                    case TokenType.CloseParentheses:
                        if (_groupingDepth == 0)
                        {
                            throw ODataException.BadRequest(ExceptionMessage.UnableToParseFilter($"the closing parenthesis not expected", token.Position), "$filter");
                        }

                        _groupingDepth--;

                        if (stack.Count > 0)
                        {
                            FunctionCallNode lastNode = stack.Pop();

                            if (stack.Count > 0)
                            {
                                stack.Peek().AddParameter(lastNode);
                            }
                            else
                            {
                                if (binaryNode != null)
                                {
                                    binaryNode.Right = lastNode;
                                }
                                else
                                {
                                    node = lastNode;
                                }
                            }
                        }

                        break;

                    case TokenType.Comma:
                        if (_tokens.Count > 0 && _tokens.Peek().TokenType == TokenType.CloseParentheses)
                        {
                            // If there is a comma in a function call, there should be another parameter followed by a closing comma
                            throw ODataException.BadRequest(ExceptionMessage.UnableToParseFilter($"the function {node?.Name} has a missing parameter or extra comma", token.Position), "$filter");
                        }

                        break;

                    case TokenType.FunctionName:
                        node = new FunctionCallNode(token.Value);
                        break;

                    case TokenType.OpenParentheses:
                        if (_tokens.Count > 0 && _tokens.Peek().TokenType == TokenType.CloseParentheses)
                        {
                            // All OData functions have at least 1 or 2 parameters
                            throw ODataException.BadRequest(ExceptionMessage.UnableToParseFilter($"the function {node?.Name} has no parameters specified", token.Position), "$filter");
                        }

                        _groupingDepth++;
                        stack.Push(node);
                        break;

                    case TokenType.PropertyName:
                        var propertyAccessNode = new PropertyAccessNode(PropertyPath.For(token.Value, _model));

                        if (stack.Count > 0)
                        {
                            stack.Peek().AddParameter(propertyAccessNode);
                        }
                        else
                        {
                            if (binaryNode == null)
                            {
                                throw ODataException.BadRequest(ExceptionMessage.GenericUnableToParseFilter, "$filter");
                            }

                            binaryNode.Right = propertyAccessNode;
                        }

                        break;

                    default:
                        throw ODataException.BadRequest(ExceptionMessage.UnableToParseFilter($"unexpected {token.Value}", token.Position), "$filter");
                    }
                }

                if (binaryNode != null)
                {
                    return(binaryNode);
                }

                return(node);
            }
            private QueryNode ParsePropertyAccessNode()
            {
                QueryNode result = null;

                QueryNode          leftNode     = null;
                BinaryOperatorKind operatorKind = BinaryOperatorKind.None;
                QueryNode          rightNode    = null;

                while (_tokens.Count > 0)
                {
                    Token token = _tokens.Dequeue();

                    switch (token.TokenType)
                    {
                    case TokenType.Base64Binary:
                    case TokenType.Date:
                    case TokenType.DateTimeOffset:
                    case TokenType.Decimal:
                    case TokenType.Double:
                    case TokenType.Duration:
                    case TokenType.Enum:
                    case TokenType.False:
                    case TokenType.Guid:
                    case TokenType.Integer:
                    case TokenType.Null:
                    case TokenType.Single:
                    case TokenType.String:
                    case TokenType.TimeOfDay:
                    case TokenType.True:
                        rightNode = ConstantNodeParser.ParseConstantNode(token);
                        break;

                    case TokenType.BinaryOperator:
                        if (operatorKind != BinaryOperatorKind.None)
                        {
                            result    = new BinaryOperatorNode(leftNode, operatorKind, rightNode);
                            leftNode  = null;
                            rightNode = null;
                        }

                        operatorKind = token.Value.ToBinaryOperatorKind();
                        break;

                    case TokenType.CloseParentheses:
                        _groupingDepth--;
                        break;

                    case TokenType.FunctionName:
                        rightNode = new FunctionCallNode(token.Value);
                        break;

                    case TokenType.OpenParentheses:
                        _groupingDepth++;
                        break;

                    case TokenType.PropertyName:
                        var propertyAccessNode = new PropertyAccessNode(PropertyPath.For(token.Value, _model));

                        if (leftNode is null)
                        {
                            leftNode = propertyAccessNode;
                        }
                        else if (rightNode is null)
                        {
                            rightNode = propertyAccessNode;
                        }

                        break;

                    default:
                        throw ODataException.BadRequest(ExceptionMessage.UnableToParseFilter($"unexpected {token.Value}", token.Position), "$filter");
                    }
                }

                result = result is null
                    ? new BinaryOperatorNode(leftNode, operatorKind, rightNode)
                    : new BinaryOperatorNode(result, operatorKind, leftNode ?? rightNode);

                return(result);
            }