/// <summary> /// Building off of a PathSegmentToken, continue parsing any expand options (nested $filter, $expand, etc) /// to build up an ExpandTermToken which fully represents the tree that makes up this expand term. /// </summary> /// <param name="pathToken">The PathSegmentToken representing the parsed expand path whose options we are now parsing.</param> /// <param name="optionsText">A string of the text between the parenthesis after an expand option.</param> /// <returns>The list of expand term tokens based on the path token, and all available expand options.</returns> internal List <ExpandTermToken> BuildExpandTermToken(PathSegmentToken pathToken, string optionsText) { // Setup a new lexer for parsing the optionsText this.lexer = new ExpressionLexer(optionsText ?? "", true /*moveToFirstToken*/, true /*useSemicolonDelimiter*/); // $expand option with star only support $ref option, $expand option property could be "*" or "*/$ref", special logic will be adopted. if (pathToken.Identifier == UriQueryConstants.Star || (pathToken.Identifier == UriQueryConstants.RefSegment && pathToken.NextToken.Identifier == UriQueryConstants.Star)) { return(BuildStarExpandTermToken(pathToken)); } QueryToken filterOption = null; IEnumerable <OrderByToken> orderByOptions = null; long? topOption = null; long? skipOption = null; bool? countOption = null; long? levelsOption = null; QueryToken searchOption = null; SelectToken selectOption = null; ExpandToken expandOption = null; if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.OpenParen) { // advance past the '(' this.lexer.NextToken(); // Check for (), which is not allowed. if (this.lexer.CurrentToken.Kind == ExpressionTokenKind.CloseParen) { throw new ODataException(ODataErrorStrings.UriParser_MissingExpandOption(pathToken.Identifier)); } // Look for all the supported query options while (this.lexer.CurrentToken.Kind != ExpressionTokenKind.CloseParen) { string text = this.enableCaseInsensitiveBuiltinIdentifier ? this.lexer.CurrentToken.Text.ToLowerInvariant() : this.lexer.CurrentToken.Text; // Prepend '$' prefix if needed. if (this.enableNoDollarQueryOptions && !text.StartsWith(UriQueryConstants.DollarSign, StringComparison.Ordinal)) { text = string.Format(CultureInfo.InvariantCulture, "{0}{1}", UriQueryConstants.DollarSign, text); } switch (text) { case ExpressionConstants.QueryOptionFilter: { // advance to the equal sign this.lexer.NextToken(); string filterText = this.ReadQueryOption(); UriQueryExpressionParser filterParser = new UriQueryExpressionParser(this.MaxFilterDepth, enableCaseInsensitiveBuiltinIdentifier); filterOption = filterParser.ParseFilter(filterText); break; } case ExpressionConstants.QueryOptionOrderby: { // advance to the equal sign this.lexer.NextToken(); string orderByText = this.ReadQueryOption(); UriQueryExpressionParser orderbyParser = new UriQueryExpressionParser(this.MaxOrderByDepth, enableCaseInsensitiveBuiltinIdentifier); orderByOptions = orderbyParser.ParseOrderBy(orderByText); break; } case ExpressionConstants.QueryOptionTop: { // advance to the equal sign this.lexer.NextToken(); string topText = this.ReadQueryOption(); // TryParse requires a non-nullable non-negative long. long top; if (!long.TryParse(topText, out top) || top < 0) { throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidTopOption(topText)); } topOption = top; break; } case ExpressionConstants.QueryOptionSkip: { // advance to the equal sign this.lexer.NextToken(); string skipText = this.ReadQueryOption(); // TryParse requires a non-nullable non-negative long. long skip; if (!long.TryParse(skipText, out skip) || skip < 0) { throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidSkipOption(skipText)); } skipOption = skip; break; } case ExpressionConstants.QueryOptionCount: { // advance to the equal sign this.lexer.NextToken(); string countText = this.ReadQueryOption(); switch (countText) { case ExpressionConstants.KeywordTrue: { countOption = true; break; } case ExpressionConstants.KeywordFalse: { countOption = false; break; } default: { throw new ODataException(ODataErrorStrings.UriSelectParser_InvalidCountOption(countText)); } } break; } case ExpressionConstants.QueryOptionLevels: { levelsOption = ResolveLevelOption(); break; } case ExpressionConstants.QueryOptionSearch: { // advance to the equal sign this.lexer.NextToken(); string searchText = this.ReadQueryOption(); SearchParser searchParser = new SearchParser(this.MaxSearchDepth); searchOption = searchParser.ParseSearch(searchText); break; } case ExpressionConstants.QueryOptionSelect: { // advance to the equal sign this.lexer.NextToken(); string selectText = this.ReadQueryOption(); SelectExpandParser innerSelectParser = new SelectExpandParser(selectText, this.maxRecursionDepth, enableCaseInsensitiveBuiltinIdentifier); selectOption = innerSelectParser.ParseSelect(); break; } case ExpressionConstants.QueryOptionExpand: { // advance to the equal sign this.lexer.NextToken(); string expandText = this.ReadQueryOption(); // As 2016/1/8, the navigation property is only supported in entity type, and will support in ComplexType in future. IEdmStructuredType targetEntityType = null; if (this.resolver != null && this.parentEntityType != null) { var parentProperty = this.resolver.ResolveProperty(parentEntityType, pathToken.Identifier) as IEdmNavigationProperty; // it is a navigation property, need to find the type. // Like $expand=Friends($expand=Trips($expand=*)), when expandText becomes "Trips($expand=*)", // find navigation property Trips of Friends, then get Entity type of Trips. if (parentProperty != null) { targetEntityType = parentProperty.ToEntityType(); } } SelectExpandParser innerExpandParser = new SelectExpandParser( resolver, expandText, targetEntityType, this.maxRecursionDepth - 1, this.enableCaseInsensitiveBuiltinIdentifier, this.enableNoDollarQueryOptions); expandOption = innerExpandParser.ParseExpand(); break; } default: { throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } } } // Move past the ')' this.lexer.NextToken(); } // Either there was no '(' at all or we just read past the ')' so we should be at the end if (this.lexer.CurrentToken.Kind != ExpressionTokenKind.End) { throw new ODataException(ODataErrorStrings.UriSelectParser_TermIsNotValid(this.lexer.ExpressionText)); } // TODO, there should be some check here in case pathToken identifier is $ref, select, expand and levels options are not allowed. List <ExpandTermToken> expandTermTokenList = new List <ExpandTermToken>(); ExpandTermToken currentToken = new ExpandTermToken(pathToken, filterOption, orderByOptions, topOption, skipOption, countOption, levelsOption, searchOption, selectOption, expandOption); expandTermTokenList.Add(currentToken); return(expandTermTokenList); }
/// <summary> /// Parses the <paramref name="queryUri"/> and returns a new instance of <see cref="SyntacticTree"/> /// describing the query specified by the uri. /// </summary> /// <param name="queryUri">The absolute URI which holds the query to parse. This must be a path relative to the <paramref name="serviceBaseUri"/>.</param> /// <param name="serviceBaseUri">The base URI of the service.</param> /// <param name="maxDepth">The maximum depth of any single query part. Security setting to guard against DoS attacks causing stack overflows and such.</param> /// <returns>A new instance of <see cref="SyntacticTree"/> which represents the query specified in the <paramref name="queryUri"/>.</returns> public static SyntacticTree ParseUri(Uri queryUri, Uri serviceBaseUri, int maxDepth) { ExceptionUtils.CheckArgumentNotNull(queryUri, "queryUri"); if (!queryUri.IsAbsoluteUri) { throw new ArgumentException(Strings.SyntacticTree_UriMustBeAbsolute(queryUri), "queryUri"); } ExceptionUtils.CheckArgumentNotNull(serviceBaseUri, "serviceBaseUri"); if (!serviceBaseUri.IsAbsoluteUri) { throw new ArgumentException(Strings.SyntacticTree_UriMustBeAbsolute(serviceBaseUri), "serviceBaseUri"); } if (maxDepth <= 0) { throw new ArgumentException(Strings.SyntacticTree_MaxDepthInvalid, "maxDepth"); } UriPathParser pathParser = new UriPathParser(new ODataUriParserSettings() { PathLimit = maxDepth }); var path = pathParser.ParsePathIntoSegments(queryUri, serviceBaseUri); // COMPAT 32: Differencies in query options parsing in WCF DS // // We allow non-system $ query options in the lexical space. // We allow multiple instances of a custom or non-system $ query option in the lexical space. // TODO: we need to decide whether we want to allow multiple system $ query options with the same name (OIPI suggests that this is valid); we currently don't. List <CustomQueryOptionToken> queryOptions = QueryOptionUtils.ParseQueryOptions(queryUri); IDictionary <string, string> parameterAliases = queryOptions.GetParameterAliases(); QueryToken filter = null; string filterQuery = queryOptions.GetQueryOptionValueAndRemove(UriQueryConstants.FilterQueryOption); if (filterQuery != null) { UriQueryExpressionParser expressionParser = new UriQueryExpressionParser(maxDepth); filter = expressionParser.ParseFilter(filterQuery); } IEnumerable <OrderByToken> orderByTokens = null; string orderByQuery = queryOptions.GetQueryOptionValueAndRemove(UriQueryConstants.OrderByQueryOption); if (orderByQuery != null) { UriQueryExpressionParser expressionParser = new UriQueryExpressionParser(maxDepth); orderByTokens = expressionParser.ParseOrderBy(orderByQuery); } SelectToken select = null; string selectQuery = queryOptions.GetQueryOptionValueAndRemove(UriQueryConstants.SelectQueryOption); if (selectQuery != null) { SelectExpandParser selectParser = new SelectExpandParser(selectQuery, ODataUriParserSettings.DefaultSelectExpandLimit); select = selectParser.ParseSelect(); } ExpandToken expand = null; string expandQuery = queryOptions.GetQueryOptionValueAndRemove(UriQueryConstants.ExpandQueryOption); if (expandQuery != null) { SelectExpandParser expandParser = new SelectExpandParser(expandQuery, ODataUriParserSettings.DefaultSelectExpandLimit); expand = expandParser.ParseExpand(); } int? skip = null; string skipQuery = queryOptions.GetQueryOptionValueAndRemove(UriQueryConstants.SkipQueryOption); if (skipQuery != null) { int skipValue; if (!TryUriStringToNonNegativeInteger(skipQuery, out skipValue)) { throw new ODataException(Strings.SyntacticTree_InvalidSkipQueryOptionValue(skipQuery)); } skip = skipValue; } int? top = null; string topQuery = queryOptions.GetQueryOptionValueAndRemove(UriQueryConstants.TopQueryOption); if (topQuery != null) { int topValue; if (!TryUriStringToNonNegativeInteger(topQuery, out topValue)) { throw new ODataException(Strings.SyntacticTree_InvalidTopQueryOptionValue(topQuery)); } top = topValue; } string countQuery = queryOptions.GetQueryOptionValueAndRemove(UriQueryConstants.CountQueryOption); bool? count = ParseQueryCount(countQuery); string format = queryOptions.GetQueryOptionValueAndRemove(UriQueryConstants.FormatQueryOption); return(new SyntacticTree( parameterAliases, path, filter, orderByTokens, select, expand, skip, top, count, format, queryOptions.Count == 0 ? null : new ReadOnlyCollection <CustomQueryOptionToken>(queryOptions))); }