public IHttpActionResult Get(
            ODataQueryOptions <V2FeedPackage> options,
            string curatedFeedName,
            [FromUri] string semVerLevel = null)
        {
            var result = GetCuratedFeedResult(curatedFeedName);

            if (result.ActionResult != null)
            {
                return(result.ActionResult);
            }

            var semVerLevelKey = SemVerLevelKey.ForSemVerLevel(semVerLevel);

            var queryable = result
                            .Packages
                            .Where(p => p.PackageStatusKey == PackageStatus.Available)
                            .Where(SemVerLevelKey.IsPackageCompliantWithSemVerLevelPredicate(semVerLevel))
                            .ToV2FeedPackageQuery(
                _configurationService.GetSiteRoot(UseHttps()),
                _configurationService.Features.FriendlyLicenses,
                semVerLevelKey)
                            .InterceptWith(new NormalizeVersionInterceptor());

            return(TrackedQueryResult(options, queryable, MaxPageSize, customQuery: true));
        }
Beispiel #2
0
        private async Task <IHttpActionResult> FindPackagesByIdAsync(
            ODataQueryOptions <V2FeedPackage> options,
            string id,
            string semVerLevel,
            bool isNonHijackEnabled)
        {
            if (string.IsNullOrEmpty(id))
            {
                var semVerLevelKey = SemVerLevelKey.ForSemVerLevel(semVerLevel);

                var emptyResult = Enumerable.Empty <Package>().AsQueryable()
                                  .ToV2FeedPackageQuery(
                    GetSiteRoot(),
                    _configurationService.Features.FriendlyLicenses,
                    semVerLevelKey);

                return(TrackedQueryResult(options, emptyResult, MaxPageSize, customQuery: false));
            }

            return(await GetCoreAsync(
                       options,
                       id,
                       version : null,
                       semVerLevel : semVerLevel,
                       allowHijack : true,
                       return404NotFoundWhenNoResults : false,
                       isNonHijackEnabled : isNonHijackEnabled));
        }
Beispiel #3
0
        public IHttpActionResult Get(ODataQueryOptions <V1FeedPackage> options)
        {
            if (!_featureFlagService.IsODataV1GetAllEnabled())
            {
                return(BadRequest(Strings.ODataDisabled));
            }

            if (!ODataQueryVerifier.AreODataOptionsAllowed(options, ODataQueryVerifier.V1Packages,
                                                           _configurationService.Current.IsODataFilterEnabled, nameof(Get)))
            {
                return(BadRequest(ODataQueryVerifier.GetValidationFailedMessage(options)));
            }

            bool result = TryShouldIgnoreOrderById(options, out var shouldIgnoreOrderById);

            if (!result)
            {
                return(BadRequest("Invalid OrderBy parameter"));
            }

            var queryable = GetAll()
                            .Where(p => !p.IsPrerelease && p.PackageStatusKey == PackageStatus.Available)
                            .Where(SemVerLevelKey.IsUnknownPredicate())
                            .WithoutSortOnColumn(Version)
                            .WithoutSortOnColumn(Id, shouldIgnoreOrderById)
                            .ToV1FeedPackageQuery(_configurationService.GetSiteRoot(UseHttps()));

            return(TrackedQueryResult(options, queryable, MaxPageSize, customQuery: true));
        }
Beispiel #4
0
        public async Task <IHttpActionResult> FindPackagesById(
            ODataQueryOptions <V2FeedPackage> options,
            [FromODataUri] string id,
            [FromUri] string semVerLevel = null)
        {
            if (string.IsNullOrEmpty(id))
            {
                var semVerLevelKey = SemVerLevelKey.ForSemVerLevel(semVerLevel);

                var emptyResult = Enumerable.Empty <Package>().AsQueryable()
                                  .ToV2FeedPackageQuery(
                    GetSiteRoot(),
                    _configurationService.Features.FriendlyLicenses,
                    semVerLevelKey);

                return(TrackedQueryResult(options, emptyResult, MaxPageSize, customQuery: false));
            }

            return(await GetCore(
                       options,
                       id,
                       version : null,
                       semVerLevel : semVerLevel,
                       return404NotFoundWhenNoResults : false));
        }
Beispiel #5
0
        public static async Task <SearchAdaptorResult> SearchCore(
            ISearchService searchService,
            HttpRequestBase request,
            IQueryable <Package> packages,
            string searchTerm,
            string targetFramework,
            bool includePrerelease,
            string semVerLevel)
        {
            SearchFilter searchFilter;

            // We can only use Lucene if:
            //  a) We are looking for the latest version of a package OR the Index contains all versions of each package
            //  b) The sort order is something Lucene can handle
            if (TryReadSearchFilter(searchService.ContainsAllVersions, request.RawUrl, false, out searchFilter))
            {
                searchFilter.SearchTerm         = searchTerm;
                searchFilter.IncludePrerelease  = includePrerelease;
                searchFilter.SupportedFramework = targetFramework;
                searchFilter.SemVerLevel        = semVerLevel;

                var results = await GetResultsFromSearchService(searchService, searchFilter);

                return(new SearchAdaptorResult(true, results));
            }

            if (!includePrerelease)
            {
                packages = packages.Where(p => !p.IsPrerelease);
            }

            packages = packages.Where(SemVerLevelKey.IsPackageCompliantWithSemVerLevelPredicate(semVerLevel));

            return(new SearchAdaptorResult(false, packages.Search(searchTerm)));
        }
Beispiel #6
0
        public QueryResult(
            ODataQueryOptions <TModel> queryOptions, IQueryable <TModel> queryable, System.Web.Http.ApiController controller, int maxPageSize,
            long?totalResults, Func <ODataQueryOptions <TModel>, ODataQuerySettings, long?, Uri> generateNextLink)
        {
            _queryOptions     = queryOptions;
            _queryable        = queryable;
            _controller       = controller;
            _totalResults     = totalResults;
            _generateNextLink = generateNextLink;

            var queryDictionary = HttpUtility.ParseQueryString(queryOptions.Request.RequestUri.Query);

            _semVerLevelKey = SemVerLevelKey.ForSemVerLevel(queryDictionary["semVerLevel"]);

            if (_totalResults.HasValue && generateNextLink != null)
            {
                _isPagedResult = true;
            }

            // todo: if we decide to no longer support projections
            //AllowedQueryOptions = AllowedQueryOptions.All & ~AllowedQugeteryOptions.Select
            _validationSettings = new ODataValidationSettings()
            {
                MaxNodeCount = 250
            };

            _querySettings = new ODataQuerySettings(QueryResultDefaults.DefaultQuerySettings)
            {
                PageSize = maxPageSize
            };
        }
            public async void ValidPackageIdWithSemVerLevelReturnVersionsWhosePackagesHaveSemVerLevelCompliant(string semVerLevel)
            {
                var queryResult = await _packageVersionsQuery.Execute("nuget", null, null, semVerLevel);

                var allVersionsAreFromPackagesWithSemVerLevelCompliant = queryResult.All(version =>
                {
                    _packageDictionary.TryGetValue(version, out var package);
                    return(SemVerLevelKey.IsPackageCompliantWithSemVerLevelPredicate(semVerLevel).Compile()(package));
                });

                Assert.True(allVersionsAreFromPackagesWithSemVerLevelCompliant);
            }
Beispiel #8
0
        public IHttpActionResult Get(ODataQueryOptions <V1FeedPackage> options)
        {
            if (!ODataQueryVerifier.AreODataOptionsAllowed(options, ODataQueryVerifier.V1Packages,
                                                           _configurationService.Current.IsODataFilterEnabled, nameof(Get)))
            {
                return(BadRequest(ODataQueryVerifier.GetValidationFailedMessage(options)));
            }
            var queryable = _packagesRepository.GetAll()
                            .Where(p => !p.IsPrerelease && p.PackageStatusKey == PackageStatus.Available)
                            .Where(SemVerLevelKey.IsUnknownPredicate())
                            .WithoutSortOnColumn(Version)
                            .WithoutSortOnColumn(Id, ShouldIgnoreOrderById(options))
                            .ToV1FeedPackageQuery(_configurationService.GetSiteRoot(UseHttps()));

            return(QueryResult(options, queryable, MaxPageSize));
        }
        public IHttpActionResult Get(
            ODataQueryOptions <V2FeedPackage> options,
            string curatedFeedName,
            [FromUri] string semVerLevel = null)
        {
            if (!_entities.CuratedFeeds.Any(cf => cf.Name == curatedFeedName))
            {
                return(NotFound());
            }

            var semVerLevelKey = SemVerLevelKey.ForSemVerLevel(semVerLevel);

            var queryable = _curatedFeedService.GetPackages(curatedFeedName)
                            .Where(SemVerLevelKey.IsPackageCompliantWithSemVerLevel(semVerLevel))
                            .ToV2FeedPackageQuery(
                _configurationService.GetSiteRoot(UseHttps()),
                _configurationService.Features.FriendlyLicenses,
                semVerLevelKey)
                            .InterceptWith(new NormalizeVersionInterceptor());

            return(QueryResult(options, queryable, MaxPageSize));
        }
Beispiel #10
0
        private static IEnumerable <Package> GetUpdates(
            IEnumerable <Package> packages,
            ILookup <string, Tuple <NuGetVersion, VersionRange> > versionLookup,
            IEnumerable <NuGetFramework> targetFrameworkValues,
            bool includeAllVersions,
            string semVerLevel)
        {
            var isSemVerLevelCompliant = SemVerLevelKey.IsPackageCompliantWithSemVerLevelPredicate(semVerLevel).Compile();

            var updates = from p in packages.AsEnumerable()
                          let version = NuGetVersion.Parse(p.Version)
                                        where isSemVerLevelCompliant(p) &&
                                        versionLookup[p.PackageRegistration.Id].Any(versionTuple =>
            {
                NuGetVersion clientVersion     = versionTuple.Item1;
                var supportedPackageFrameworks = p.SupportedFrameworks.Select(f => f.FrameworkName);

                VersionRange versionConstraint = versionTuple.Item2;

                return(version > clientVersion &&
                       (targetFrameworkValues == null ||
                        !supportedPackageFrameworks.Any() ||
                        targetFrameworkValues.Any(s => supportedPackageFrameworks.Any(supported => NuGetFrameworkUtility.IsCompatibleWithFallbackCheck(s, supported)))) &&
                       (versionConstraint == null ||
                        versionConstraint.Satisfies(version)));
            })
                                        select p;

            if (!includeAllVersions)
            {
                updates = updates.GroupBy(p => p.PackageRegistration.Id)
                          .Select(g => g.OrderByDescending(p => NuGetVersion.Parse(p.Version)).First());
            }

            return(updates);
        }
Beispiel #11
0
        public IHttpActionResult GetUpdates(
            ODataQueryOptions <V2FeedPackage> options,
            [FromODataUri] string packageIds,
            [FromODataUri] string versions,
            [FromODataUri] bool includePrerelease,
            [FromODataUri] bool includeAllVersions,
            [FromODataUri] string targetFrameworks   = "",
            [FromODataUri] string versionConstraints = "",
            [FromUri] string semVerLevel             = null)
        {
            if (string.IsNullOrEmpty(packageIds) || string.IsNullOrEmpty(versions))
            {
                return(TrackedQueryResult(
                           options,
                           Enumerable.Empty <V2FeedPackage>().AsQueryable(),
                           MaxPageSize,
                           customQuery: false));
            }

            if (!ODataQueryVerifier.AreODataOptionsAllowed(options, ODataQueryVerifier.V2GetUpdates,
                                                           _configurationService.Current.IsODataFilterEnabled, nameof(GetUpdates)))
            {
                return(BadRequest(ODataQueryVerifier.GetValidationFailedMessage(options)));
            }

            // Workaround https://github.com/NuGet/NuGetGallery/issues/674 for NuGet 2.1 client.
            // Can probably eventually be retired (when nobody uses 2.1 anymore...)
            // Note - it was URI un-escaping converting + to ' ', undoing that is actually a pretty conservative substitution because
            // space characters are never accepted as valid by VersionUtility.ParseFrameworkName.
            if (!string.IsNullOrEmpty(targetFrameworks))
            {
                targetFrameworks = targetFrameworks.Replace(' ', '+');
            }

            var idValues              = packageIds.Trim().ToLowerInvariant().Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
            var versionValues         = versions.Trim().Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
            var targetFrameworkValues = string.IsNullOrEmpty(targetFrameworks)
                                            ? null
                                            : targetFrameworks.Split('|').Select(tfx => NuGetFramework.Parse(tfx)).ToList();
            var versionConstraintValues = string.IsNullOrEmpty(versionConstraints)
                                            ? new string[idValues.Length]
                                            : versionConstraints.Split('|');

            if (idValues.Length == 0 || idValues.Length != versionValues.Length || idValues.Length != versionConstraintValues.Length)
            {
                // Exit early if the request looks invalid
                return(TrackedQueryResult(
                           options,
                           Enumerable.Empty <V2FeedPackage>().AsQueryable(),
                           MaxPageSize,
                           customQuery: false));
            }

            var versionLookup = idValues.Select((id, i) =>
            {
                NuGetVersion currentVersion;
                if (NuGetVersion.TryParse(versionValues[i], out currentVersion))
                {
                    VersionRange versionConstraint = null;
                    if (versionConstraintValues[i] != null)
                    {
                        if (!VersionRange.TryParse(versionConstraintValues[i], out versionConstraint))
                        {
                            versionConstraint = null;
                        }
                    }
                    return(Tuple.Create(id, Tuple.Create(currentVersion, versionConstraint)));
                }
                return(null);
            })
                                .Where(t => t != null)
                                .ToLookup(t => t.Item1, t => t.Item2, StringComparer.OrdinalIgnoreCase);

            var packages = GetAll()
                           .Include(p => p.PackageRegistration)
                           .Include(p => p.SupportedFrameworks)
                           .Where(p => p.Listed && (includePrerelease || !p.IsPrerelease) &&
                                  idValues.Contains(p.PackageRegistration.Id.ToLower()) &&
                                  p.PackageStatusKey == PackageStatus.Available)
                           .OrderBy(p => p.PackageRegistration.Id);

            var semVerLevelKey = SemVerLevelKey.ForSemVerLevel(semVerLevel);
            var queryable      = GetUpdates(packages, versionLookup, targetFrameworkValues, includeAllVersions, semVerLevel)
                                 .AsQueryable()
                                 .ToV2FeedPackageQuery(
                GetSiteRoot(),
                _configurationService.Features.FriendlyLicenses,
                semVerLevelKey);

            return(TrackedQueryResult(options, queryable, MaxPageSize, customQuery: false));
        }
Beispiel #12
0
        private async Task <IHttpActionResult> GetCore(
            ODataQueryOptions <V2FeedPackage> options,
            string id,
            string version,
            string semVerLevel,
            bool return404NotFoundWhenNoResults)
        {
            var packages = GetAll()
                           .Include(p => p.PackageRegistration)
                           .Where(p => p.PackageStatusKey == PackageStatus.Available &&
                                  p.PackageRegistration.Id.Equals(id, StringComparison.OrdinalIgnoreCase))
                           .Where(SemVerLevelKey.IsPackageCompliantWithSemVerLevelPredicate(semVerLevel));

            if (!string.IsNullOrEmpty(version))
            {
                NuGetVersion nugetVersion;
                if (NuGetVersion.TryParse(version, out nugetVersion))
                {
                    // Our APIs expect to receive normalized version strings.
                    // We need to compare normalized versions or we can never retrieve SemVer2 package versions.
                    var normalizedString = nugetVersion.ToNormalizedString();
                    packages = packages.Where(p => p.NormalizedVersion == normalizedString);
                }
            }

            var  semVerLevelKey = SemVerLevelKey.ForSemVerLevel(semVerLevel);
            bool?customQuery    = null;

            // try the search service
            try
            {
                var searchAdaptorResult = await SearchAdaptor.FindByIdAndVersionCore(
                    _searchService,
                    GetTraditionalHttpContext().Request,
                    packages,
                    id,
                    version,
                    semVerLevel : semVerLevel);

                // If intercepted, create a paged queryresult
                if (searchAdaptorResult.ResultsAreProvidedBySearchService)
                {
                    customQuery = false;

                    // Packages provided by search service
                    packages = searchAdaptorResult.Packages;

                    // Add explicit Take() needed to limit search hijack result set size if $top is specified
                    var totalHits = packages.LongCount();

                    if (totalHits == 0 && return404NotFoundWhenNoResults)
                    {
                        _telemetryService.TrackODataCustomQuery(customQuery);
                        return(NotFound());
                    }

                    var pagedQueryable = packages
                                         .Take(options.Top != null ? Math.Min(options.Top.Value, MaxPageSize) : MaxPageSize)
                                         .ToV2FeedPackageQuery(
                        GetSiteRoot(),
                        _configurationService.Features.FriendlyLicenses,
                        semVerLevelKey);

                    return(TrackedQueryResult(
                               options,
                               pagedQueryable,
                               MaxPageSize,
                               totalHits,
                               (o, s, resultCount) => SearchAdaptor.GetNextLink(Request.RequestUri, resultCount, new { id }, o, s, semVerLevelKey),
                               customQuery));
                }
                else
                {
                    customQuery = true;
                }
            }
            catch (Exception ex)
            {
                // Swallowing Exception intentionally. If *anything* goes wrong in search, just fall back to the database.
                // We don't want to break package restores. We do want to know if this happens, so here goes:
                QuietLog.LogHandledException(ex);
            }

            if (return404NotFoundWhenNoResults && !packages.Any())
            {
                _telemetryService.TrackODataCustomQuery(customQuery);
                return(NotFound());
            }

            var queryable = packages.ToV2FeedPackageQuery(
                GetSiteRoot(),
                _configurationService.Features.FriendlyLicenses,
                semVerLevelKey);

            return(TrackedQueryResult(options, queryable, MaxPageSize, customQuery));
        }
Beispiel #13
0
        private async Task <IHttpActionResult> GetCore(
            ODataQueryOptions <V1FeedPackage> options,
            string id,
            string version,
            bool return404NotFoundWhenNoResults,
            bool isNonHijackEnabled)
        {
            var packages = GetAll()
                           .Include(p => p.PackageRegistration)
                           .Where(p => p.PackageRegistration.Id.Equals(id, StringComparison.OrdinalIgnoreCase) &&
                                  !p.IsPrerelease &&
                                  p.PackageStatusKey == PackageStatus.Available)
                           .Where(SemVerLevelKey.IsUnknownPredicate());

            if (!string.IsNullOrEmpty(version))
            {
                packages = packages.Where(p => p.Version == version);
            }

            bool?customQuery = null;

            // try the search service
            try
            {
                var searchService       = _searchServiceFactory.GetService();
                var searchAdaptorResult = await SearchAdaptor.FindByIdAndVersionCore(
                    searchService,
                    GetTraditionalHttpContext().Request,
                    packages,
                    id,
                    version,
                    semVerLevel : null);

                // If intercepted, create a paged queryresult
                if (searchAdaptorResult.ResultsAreProvidedBySearchService)
                {
                    customQuery = false;

                    // Packages provided by search service
                    packages = searchAdaptorResult.Packages;

                    // Add explicit Take() needed to limit search hijack result set size if $top is specified
                    var totalHits = packages.LongCount();

                    if (return404NotFoundWhenNoResults && totalHits == 0)
                    {
                        _telemetryService.TrackODataCustomQuery(customQuery);
                        return(NotFound());
                    }

                    var pagedQueryable = packages
                                         .Take(options.Top != null ? Math.Min(options.Top.Value, MaxPageSize) : MaxPageSize)
                                         .ToV1FeedPackageQuery(GetSiteRoot());

                    return(TrackedQueryResult(
                               options,
                               pagedQueryable,
                               MaxPageSize,
                               totalHits,
                               (o, s, resultCount) => SearchAdaptor.GetNextLink(Request.RequestUri, resultCount, new { id }, o, s),
                               customQuery));
                }
                else
                {
                    customQuery = true;
                }
            }
            catch (Exception ex) when(isNonHijackEnabled)
            {
                // Swallowing exception intentionally if we are allowing a fallback to database. If non-hijacked
                // queries are disabled, let the exception bubble out and the client will retry.
                QuietLog.LogHandledException(ex);
            }

            // If we've reached this point, the hijack to the search service has failed or is not applicable. If
            // non-hijacked queries are disabled, stop here.
            if (!isNonHijackEnabled)
            {
                return(BadRequest(Strings.ODataParametersDisabled));
            }

            if (return404NotFoundWhenNoResults && !packages.Any())
            {
                _telemetryService.TrackODataCustomQuery(customQuery);
                return(NotFound());
            }

            var queryable = packages.ToV1FeedPackageQuery(GetSiteRoot());

            return(TrackedQueryResult(options, queryable, MaxPageSize, customQuery));
        }
        public async Task <IHttpActionResult> Search(
            ODataQueryOptions <V2FeedPackage> options,
            string curatedFeedName,
            [FromODataUri] string searchTerm      = "",
            [FromODataUri] string targetFramework = "",
            [FromODataUri] bool includePrerelease = false,
            [FromUri] string semVerLevel          = null)
        {
            if (!_entities.CuratedFeeds.Any(cf => cf.Name == curatedFeedName))
            {
                return(NotFound());
            }

            // Handle OData-style |-separated list of frameworks.
            string[] targetFrameworkList = (targetFramework ?? "").Split(new[] { '\'', '|' }, StringSplitOptions.RemoveEmptyEntries);

            // For now, we'll just filter on the first one.
            if (targetFrameworkList.Length > 0)
            {
                // Until we support multiple frameworks, we need to prefer aspnet50 over aspnetcore50.
                if (targetFrameworkList.Contains("aspnet50"))
                {
                    targetFramework = "aspnet50";
                }
                else
                {
                    targetFramework = targetFrameworkList[0];
                }
            }

            // Perform actual search
            var curatedFeed = _curatedFeedService.GetFeedByName(curatedFeedName, includePackages: false);
            var packages    = _curatedFeedService.GetPackages(curatedFeedName)
                              .Where(p => p.PackageStatusKey == PackageStatus.Available)
                              .Where(SemVerLevelKey.IsPackageCompliantWithSemVerLevelPredicate(semVerLevel))
                              .OrderBy(p => p.PackageRegistration.Id).ThenBy(p => p.Version);

            // todo: search hijack should take queryOptions instead of manually parsing query options
            var searchAdaptorResult = await SearchAdaptor.SearchCore(
                _searchService,
                GetTraditionalHttpContext().Request,
                packages,
                searchTerm,
                targetFramework,
                includePrerelease,
                curatedFeed : curatedFeed,
                semVerLevel : semVerLevel);

            // Packages provided by search service (even when not hijacked)
            var query = searchAdaptorResult.Packages;

            var semVerLevelKey = SemVerLevelKey.ForSemVerLevel(semVerLevel);

            // If intercepted, create a paged queryresult
            if (searchAdaptorResult.ResultsAreProvidedBySearchService)
            {
                // Add explicit Take() needed to limit search hijack result set size if $top is specified
                var totalHits      = query.LongCount();
                var pagedQueryable = query
                                     .Take(options.Top != null ? Math.Min(options.Top.Value, MaxPageSize) : MaxPageSize)
                                     .ToV2FeedPackageQuery(
                    GetSiteRoot(),
                    _configurationService.Features.FriendlyLicenses,
                    semVerLevelKey);

                return(QueryResult(options, pagedQueryable, MaxPageSize, totalHits, (o, s, resultCount) =>
                {
                    // The nuget.exe 2.x list command does not like the next link at the bottom when a $top is passed.
                    // Strip it of for backward compatibility.
                    if (o.Top == null || (resultCount.HasValue && o.Top.Value >= resultCount.Value))
                    {
                        return SearchAdaptor.GetNextLink(
                            Request.RequestUri,
                            resultCount,
                            new { searchTerm, targetFramework, includePrerelease },
                            o,
                            s);
                    }
                    return null;
                }));
            }

            // If not, just let OData handle things
            var queryable = query.ToV2FeedPackageQuery(
                GetSiteRoot(),
                _configurationService.Features.FriendlyLicenses,
                semVerLevelKey);

            return(QueryResult(options, queryable, MaxPageSize));
        }
        private async Task <IHttpActionResult> GetCore(ODataQueryOptions <V1FeedPackage> options, string id, string version, bool return404NotFoundWhenNoResults)
        {
            var packages = GetAll()
                           .Include(p => p.PackageRegistration)
                           .Where(p => p.PackageRegistration.Id.Equals(id, StringComparison.OrdinalIgnoreCase) &&
                                  !p.IsPrerelease &&
                                  p.PackageStatusKey == PackageStatus.Available)
                           .Where(SemVerLevelKey.IsUnknownPredicate());

            if (!string.IsNullOrEmpty(version))
            {
                packages = packages.Where(p => p.Version == version);
            }

            bool?customQuery = null;

            // try the search service
            try
            {
                var searchService       = _searchServiceFactory.GetService();
                var searchAdaptorResult = await SearchAdaptor.FindByIdAndVersionCore(
                    searchService,
                    GetTraditionalHttpContext().Request,
                    packages,
                    id,
                    version,
                    semVerLevel : null);

                // If intercepted, create a paged queryresult
                if (searchAdaptorResult.ResultsAreProvidedBySearchService)
                {
                    customQuery = false;

                    // Packages provided by search service
                    packages = searchAdaptorResult.Packages;

                    // Add explicit Take() needed to limit search hijack result set size if $top is specified
                    var totalHits = packages.LongCount();

                    if (return404NotFoundWhenNoResults && totalHits == 0)
                    {
                        _telemetryService.TrackODataCustomQuery(customQuery);
                        return(NotFound());
                    }

                    var pagedQueryable = packages
                                         .Take(options.Top != null ? Math.Min(options.Top.Value, MaxPageSize) : MaxPageSize)
                                         .ToV1FeedPackageQuery(GetSiteRoot());

                    return(TrackedQueryResult(
                               options,
                               pagedQueryable,
                               MaxPageSize,
                               totalHits,
                               (o, s, resultCount) => SearchAdaptor.GetNextLink(Request.RequestUri, resultCount, new { id }, o, s),
                               customQuery));
                }
                else
                {
                    customQuery = true;
                }
            }
            catch (Exception ex)
            {
                // Swallowing Exception intentionally. If *anything* goes wrong in search, just fall back to the database.
                // We don't want to break package restores. We do want to know if this happens, so here goes:
                QuietLog.LogHandledException(ex);
            }

            if (return404NotFoundWhenNoResults && !packages.Any())
            {
                _telemetryService.TrackODataCustomQuery(customQuery);
                return(NotFound());
            }

            var queryable = packages.ToV1FeedPackageQuery(GetSiteRoot());

            return(TrackedQueryResult(options, queryable, MaxPageSize, customQuery));
        }
        internal static Package ReadPackage(JObject doc, string semVerLevel)
        {
            var dependencies =
                doc.Value <JArray>("Dependencies")
                .Cast <JObject>()
                .Select(obj => new PackageDependency()
            {
                Id              = obj.Value <string>("Id"),
                VersionSpec     = obj.Value <string>("VersionSpec"),
                TargetFramework = obj.Value <string>("TargetFramework")
            })
                .ToArray();

            var frameworks =
                doc.Value <JArray>("SupportedFrameworks")
                .Select(v => new PackageFramework()
            {
                TargetFramework = v.Value <string>()
            })
                .ToArray();

            var reg = doc["PackageRegistration"];
            PackageRegistration registration = null;

            if (reg != null)
            {
                registration = new PackageRegistration()
                {
                    Id     = reg.Value <string>("Id"),
                    Owners = reg.Value <JArray>("Owners")
                             .Select(v => new User {
                        Username = v.Value <string>()
                    })
                             .ToArray(),
                    DownloadCount = reg.Value <int>("DownloadCount"),
                    IsVerified    = reg.Value <bool>("Verified"),
                    Key           = reg.Value <int>("Key")
                };
            }

            var isLatest       = doc.Value <bool>("IsLatest");
            var isLatestStable = doc.Value <bool>("IsLatestStable");
            var semVer2        = SemVerLevelKey.ForSemVerLevel(semVerLevel) == SemVerLevelKey.SemVer2;

            return(new Package
            {
                Copyright = doc.Value <string>("Copyright"),
                Created = doc.Value <DateTime>("Created"),
                Description = doc.Value <string>("Description"),
                Dependencies = dependencies,
                DownloadCount = doc.Value <int>("DownloadCount"),
                FlattenedAuthors = doc.Value <string>("Authors"),
                FlattenedDependencies = doc.Value <string>("FlattenedDependencies"),
                Hash = doc.Value <string>("Hash"),
                HashAlgorithm = doc.Value <string>("HashAlgorithm"),
                IconUrl = doc.Value <string>("IconUrl"),
                IsLatest = isLatest,
                IsLatestStable = isLatestStable,
                IsLatestSemVer2 = semVer2 ? isLatest : false,
                IsLatestStableSemVer2 = semVer2 ? isLatestStable : false,
                Key = doc.Value <int>("Key"),
                Language = doc.Value <string>("Language"),
                LastUpdated = doc.Value <DateTime>("LastUpdated"),
                LastEdited = doc.Value <DateTime?>("LastEdited"),
                PackageRegistration = registration,
                PackageRegistrationKey = registration?.Key ?? 0,
                PackageFileSize = doc.Value <long>("PackageFileSize"),
                ProjectUrl = doc.Value <string>("ProjectUrl"),
                Published = doc.Value <DateTime>("Published"),
                ReleaseNotes = doc.Value <string>("ReleaseNotes"),
                RequiresLicenseAcceptance = doc.Value <bool>("RequiresLicenseAcceptance"),
                Summary = doc.Value <string>("Summary"),
                Tags = doc.Value <string>("Tags"),
                Title = doc.Value <string>("Title"),
                Version = doc.Value <string>("Version"),
                NormalizedVersion = doc.Value <string>("NormalizedVersion"),
                SupportedFrameworks = frameworks,
                MinClientVersion = doc.Value <string>("MinClientVersion"),
                LicenseUrl = doc.Value <string>("LicenseUrl"),
                LicenseNames = doc.Value <string>("LicenseNames"),
                LicenseReportUrl = doc.Value <string>("LicenseReportUrl"),
                HideLicenseReport = doc.Value <bool>("HideLicenseReport"),
                Listed = doc.Value <bool>("Listed")
            });
        }
Beispiel #17
0
        public async Task <IHttpActionResult> Get(
            ODataQueryOptions <V2FeedPackage> options,
            [FromUri] string semVerLevel = null)
        {
            // Setup the search
            var packages = GetAll()
                           .Where(p => p.PackageStatusKey == PackageStatus.Available)
                           .Where(SemVerLevelKey.IsPackageCompliantWithSemVerLevelPredicate(semVerLevel))
                           .WithoutSortOnColumn(Version)
                           .WithoutSortOnColumn(Id, ShouldIgnoreOrderById(options))
                           .InterceptWith(new NormalizeVersionInterceptor());

            var  semVerLevelKey = SemVerLevelKey.ForSemVerLevel(semVerLevel);
            bool?customQuery    = null;

            // Try the search service
            try
            {
                HijackableQueryParameters hijackableQueryParameters = null;
                if (_searchService is ExternalSearchService && SearchHijacker.IsHijackable(options, out hijackableQueryParameters))
                {
                    var searchAdaptorResult = await SearchAdaptor.FindByIdAndVersionCore(
                        _searchService,
                        GetTraditionalHttpContext().Request,
                        packages,
                        hijackableQueryParameters.Id,
                        hijackableQueryParameters.Version,
                        semVerLevel : semVerLevel);

                    // If intercepted, create a paged queryresult
                    if (searchAdaptorResult.ResultsAreProvidedBySearchService)
                    {
                        customQuery = false;

                        // Packages provided by search service
                        packages = searchAdaptorResult.Packages;

                        // Add explicit Take() needed to limit search hijack result set size if $top is specified
                        var totalHits      = packages.LongCount();
                        var pagedQueryable = packages
                                             .Take(options.Top != null ? Math.Min(options.Top.Value, MaxPageSize) : MaxPageSize)
                                             .ToV2FeedPackageQuery(
                            GetSiteRoot(),
                            _configurationService.Features.FriendlyLicenses,
                            semVerLevelKey);

                        return(TrackedQueryResult(
                                   options,
                                   pagedQueryable,
                                   MaxPageSize,
                                   totalHits,
                                   (o, s, resultCount) => SearchAdaptor.GetNextLink(Request.RequestUri, resultCount, null, o, s, semVerLevelKey),
                                   customQuery));
                    }
                    else
                    {
                        customQuery = true;
                    }
                }
                else
                {
                    customQuery = true;
                }
            }
            catch (Exception ex)
            {
                // Swallowing Exception intentionally. If *anything* goes wrong in search, just fall back to the database.
                // We don't want to break package restores. We do want to know if this happens, so here goes:
                QuietLog.LogHandledException(ex);
            }

            // Reject only when try to reach database.
            if (!ODataQueryVerifier.AreODataOptionsAllowed(options, ODataQueryVerifier.V2Packages,
                                                           _configurationService.Current.IsODataFilterEnabled, nameof(Get)))
            {
                return(BadRequest(ODataQueryVerifier.GetValidationFailedMessage(options)));
            }

            var queryable = packages.ToV2FeedPackageQuery(
                GetSiteRoot(),
                _configurationService.Features.FriendlyLicenses,
                semVerLevelKey);

            return(TrackedQueryResult(options, queryable, MaxPageSize, customQuery));
        }
        private async Task <IHttpActionResult> GetCore(
            ODataQueryOptions <V2FeedPackage> options,
            string id,
            string version,
            string semVerLevel,
            bool allowHijack,
            bool return404NotFoundWhenNoResults,
            bool isNonHijackEnabled)
        {
            var packages = GetAll()
                           .Include(p => p.PackageRegistration)
                           .Where(p => p.PackageStatusKey == PackageStatus.Available &&
                                  p.PackageRegistration.Id.Equals(id, StringComparison.OrdinalIgnoreCase))
                           .Where(SemVerLevelKey.IsPackageCompliantWithSemVerLevelPredicate(semVerLevel));

            if (!string.IsNullOrEmpty(version))
            {
                NuGetVersion nugetVersion;
                if (NuGetVersion.TryParse(version, out nugetVersion))
                {
                    // Our APIs expect to receive normalized version strings.
                    // We need to compare normalized versions or we can never retrieve SemVer2 package versions.
                    var normalizedString = nugetVersion.ToNormalizedString();
                    packages = packages.Where(p => p.NormalizedVersion == normalizedString);
                }
            }

            var  semVerLevelKey = SemVerLevelKey.ForSemVerLevel(semVerLevel);
            bool?customQuery    = null;

            if (allowHijack)
            {
                // try the search service
                try
                {
                    var searchService       = _searchServiceFactory.GetService();
                    var searchAdaptorResult = await SearchAdaptor.FindByIdAndVersionCore(
                        searchService,
                        GetTraditionalHttpContext().Request,
                        packages,
                        id,
                        version,
                        semVerLevel : semVerLevel);

                    // If intercepted, create a paged queryresult
                    if (searchAdaptorResult.ResultsAreProvidedBySearchService)
                    {
                        customQuery = false;

                        // Packages provided by search service
                        packages = searchAdaptorResult.Packages;

                        // Add explicit Take() needed to limit search hijack result set size if $top is specified
                        var totalHits = packages.LongCount();

                        if (totalHits == 0 && return404NotFoundWhenNoResults)
                        {
                            _telemetryService.TrackODataCustomQuery(customQuery);
                            return(NotFound());
                        }

                        var pagedQueryable = packages
                                             .Take(options.Top != null ? Math.Min(options.Top.Value, MaxPageSize) : MaxPageSize)
                                             .ToV2FeedPackageQuery(
                            GetSiteRoot(),
                            _configurationService.Features.FriendlyLicenses,
                            semVerLevelKey);

                        return(TrackedQueryResult(
                                   options,
                                   pagedQueryable,
                                   MaxPageSize,
                                   totalHits,
                                   (o, s, resultCount) => SearchAdaptor.GetNextLink(Request.RequestUri, resultCount, new { id }, o, s, semVerLevelKey),
                                   customQuery));
                    }
                    else
                    {
                        customQuery = true;
                    }
                }
                catch (Exception ex)
                {
                    // Swallowing Exception intentionally. If *anything* goes wrong in search, just fall back to the database.
                    // We don't want to break package restores. We do want to know if this happens, so here goes:
                    QuietLog.LogHandledException(ex);
                }
            }

            // When non-hijacked queries are disabled, allow only one non-hijacked pattern: query for a specific ID and
            // version without any fancy OData options. This enables some monitoring and testing and is known to produce
            // a very fast SQL query based on an optimized index.
            var isSimpleLookup = !string.IsNullOrWhiteSpace(id) &&
                                 !string.IsNullOrWhiteSpace(version) &&
                                 options.RawValues.Expand == null &&
                                 options.RawValues.Filter == null &&
                                 options.RawValues.Format == null &&
                                 options.RawValues.InlineCount == null &&
                                 options.RawValues.OrderBy == null &&
                                 options.RawValues.Select == null &&
                                 options.RawValues.Skip == null &&
                                 options.RawValues.SkipToken == null &&
                                 options.RawValues.Top == null;

            if (!allowHijack || !isNonHijackEnabled)
            {
                if (!isSimpleLookup)
                {
                    return(BadRequest(Strings.ODataParametersDisabled));
                }

                customQuery = true;
            }

            if (return404NotFoundWhenNoResults && !packages.Any())
            {
                _telemetryService.TrackODataCustomQuery(customQuery);
                return(NotFound());
            }

            var queryable = packages.ToV2FeedPackageQuery(
                GetSiteRoot(),
                _configurationService.Features.FriendlyLicenses,
                semVerLevelKey);

            return(TrackedQueryResult(options, queryable, MaxPageSize, customQuery));
        }
        public async Task <IHttpActionResult> Search(
            ODataQueryOptions <V2FeedPackage> options,
            [FromODataUri] string searchTerm      = "",
            [FromODataUri] string targetFramework = "",
            [FromODataUri] bool includePrerelease = false,
            [FromUri] string semVerLevel          = null)
        {
            // Handle OData-style |-separated list of frameworks.
            string[] targetFrameworkList = (targetFramework ?? "").Split(new[] { '\'', '|' }, StringSplitOptions.RemoveEmptyEntries);

            // For now, we'll just filter on the first one.
            if (targetFrameworkList.Length > 0)
            {
                // Until we support multiple frameworks, we need to prefer aspnet50 over aspnetcore50.
                if (targetFrameworkList.Contains("aspnet50"))
                {
                    targetFramework = "aspnet50";
                }
                else
                {
                    targetFramework = targetFrameworkList[0];
                }
            }

            // Perform actual search
            var packages = GetAll()
                           .Include(p => p.PackageRegistration)
                           .Include(p => p.PackageRegistration.Owners)
                           .Where(p => p.Listed && p.PackageStatusKey == PackageStatus.Available)
                           .Where(SemVerLevelKey.IsPackageCompliantWithSemVerLevelPredicate(semVerLevel))
                           .OrderBy(p => p.PackageRegistration.Id).ThenBy(p => p.Version)
                           .AsNoTracking();

            // todo: search hijack should take options instead of manually parsing query options
            var searchService       = _searchServiceFactory.GetService();
            var searchAdaptorResult = await SearchAdaptor.SearchCore(
                searchService,
                GetTraditionalHttpContext().Request,
                packages,
                searchTerm,
                targetFramework,
                includePrerelease,
                semVerLevel : semVerLevel);

            // Packages provided by search service (even when not hijacked)
            var query = searchAdaptorResult.Packages;

            var  semVerLevelKey = SemVerLevelKey.ForSemVerLevel(semVerLevel);
            bool?customQuery    = null;

            // If intercepted, create a paged queryresult
            if (searchAdaptorResult.ResultsAreProvidedBySearchService)
            {
                customQuery = false;

                // Add explicit Take() needed to limit search hijack result set size if $top is specified
                var totalHits      = query.LongCount();
                var pagedQueryable = query
                                     .Take(options.Top != null ? Math.Min(options.Top.Value, MaxPageSize) : MaxPageSize)
                                     .ToV2FeedPackageQuery(
                    GetSiteRoot(),
                    _configurationService.Features.FriendlyLicenses,
                    semVerLevelKey);

                return(TrackedQueryResult(
                           options,
                           pagedQueryable,
                           MaxPageSize,
                           totalHits,
                           (o, s, resultCount) =>
                {
                    // The nuget.exe 2.x list command does not like the next link at the bottom when a $top is passed.
                    // Strip it of for backward compatibility.
                    if (o.Top == null || (resultCount.HasValue && o.Top.Value >= resultCount.Value))
                    {
                        return SearchAdaptor.GetNextLink(Request.RequestUri, resultCount, new { searchTerm, targetFramework, includePrerelease }, o, s, semVerLevelKey);
                    }
                    return null;
                },
                           customQuery));
            }
            else
            {
                customQuery = true;
            }

            if (!_featureFlagService.IsODataV2SearchNonHijackedEnabled())
            {
                return(BadRequest(Strings.ODataParametersDisabled));
            }

            //Reject only when try to reach database.
            if (!ODataQueryVerifier.AreODataOptionsAllowed(options, ODataQueryVerifier.V2Search,
                                                           _configurationService.Current.IsODataFilterEnabled, nameof(Search)))
            {
                return(BadRequest(ODataQueryVerifier.GetValidationFailedMessage(options)));
            }

            // If not, just let OData handle things
            var queryable = query.ToV2FeedPackageQuery(
                GetSiteRoot(),
                _configurationService.Features.FriendlyLicenses,
                semVerLevelKey);

            return(TrackedQueryResult(options, queryable, MaxPageSize, customQuery));
        }
        public async Task <IHttpActionResult> Search(
            ODataQueryOptions <V1FeedPackage> options,
            [FromODataUri] string searchTerm      = "",
            [FromODataUri] string targetFramework = "")
        {
            // Handle OData-style |-separated list of frameworks.
            string[] targetFrameworkList = (targetFramework ?? "").Split(new[] { '\'', '|' }, StringSplitOptions.RemoveEmptyEntries);

            // For now, we'll just filter on the first one.
            if (targetFrameworkList.Length > 0)
            {
                // Until we support multiple frameworks, we need to prefer aspnet50 over aspnetcore50.
                if (targetFrameworkList.Contains("aspnet50"))
                {
                    targetFramework = "aspnet50";
                }
                else
                {
                    targetFramework = targetFrameworkList[0];
                }
            }

            // Perform actual search
            var packages = GetAll()
                           .Include(p => p.PackageRegistration)
                           .Include(p => p.PackageRegistration.Owners)
                           .Where(p => p.Listed && !p.IsPrerelease && p.PackageStatusKey == PackageStatus.Available)
                           .Where(SemVerLevelKey.IsUnknownPredicate())
                           .OrderBy(p => p.PackageRegistration.Id).ThenBy(p => p.Version)
                           .AsNoTracking();

            // todo: search hijack should take queryOptions instead of manually parsing query options
            var searchService       = _searchServiceFactory.GetService();
            var searchAdaptorResult = await SearchAdaptor.SearchCore(
                searchService,
                GetTraditionalHttpContext().Request,
                packages,
                searchTerm,
                targetFramework,
                includePrerelease : false,
                semVerLevel : null);

            // Packages provided by search service (even when not hijacked)
            var  query       = searchAdaptorResult.Packages;
            bool?customQuery = null;

            // If intercepted, create a paged queryresult
            if (searchAdaptorResult.ResultsAreProvidedBySearchService)
            {
                customQuery = false;

                // Add explicit Take() needed to limit search hijack result set size if $top is specified
                var totalHits      = query.LongCount();
                var pagedQueryable = query
                                     .Take(options.Top != null ? Math.Min(options.Top.Value, MaxPageSize) : MaxPageSize)
                                     .ToV1FeedPackageQuery(GetSiteRoot());

                return(TrackedQueryResult(
                           options,
                           pagedQueryable,
                           MaxPageSize,
                           totalHits,
                           (o, s, resultCount) => SearchAdaptor.GetNextLink(Request.RequestUri, resultCount, new { searchTerm, targetFramework }, o, s),
                           customQuery));
            }
            else
            {
                customQuery = true;
            }

            if (!ODataQueryVerifier.AreODataOptionsAllowed(options, ODataQueryVerifier.V1Search,
                                                           _configurationService.Current.IsODataFilterEnabled, nameof(Search)))
            {
                return(BadRequest(ODataQueryVerifier.GetValidationFailedMessage(options)));
            }

            // If not, just let OData handle things
            var queryable = query.ToV1FeedPackageQuery(GetSiteRoot());

            return(TrackedQueryResult(options, queryable, MaxPageSize, customQuery));
        }
        private async Task <IHttpActionResult> GetCore(
            ODataQueryOptions <V2FeedPackage> options,
            string curatedFeedName,
            string id,
            string normalizedVersion,
            bool return404NotFoundWhenNoResults,
            string semVerLevel)
        {
            var curatedFeed = _entities.CuratedFeeds.FirstOrDefault(cf => cf.Name == curatedFeedName);

            if (curatedFeed == null)
            {
                return(NotFound());
            }

            var packages = _curatedFeedService
                           .GetPackages(curatedFeedName)
                           .Where(p => p.PackageStatusKey == PackageStatus.Available)
                           .Where(SemVerLevelKey.IsPackageCompliantWithSemVerLevelPredicate(semVerLevel))
                           .Where(p => p.PackageRegistration.Id.Equals(id, StringComparison.OrdinalIgnoreCase));

            if (!string.IsNullOrEmpty(normalizedVersion))
            {
                packages = packages.Where(p => p.NormalizedVersion == normalizedVersion);
            }

            var semVerLevelKey = SemVerLevelKey.ForSemVerLevel(semVerLevel);

            // try the search service
            try
            {
                var searchAdaptorResult = await SearchAdaptor.FindByIdAndVersionCore(
                    _searchService,
                    GetTraditionalHttpContext().Request,
                    packages,
                    id,
                    normalizedVersion,
                    curatedFeed : curatedFeed,
                    semVerLevel : semVerLevel);

                // If intercepted, create a paged queryresult
                if (searchAdaptorResult.ResultsAreProvidedBySearchService)
                {
                    // Packages provided by search service
                    packages = searchAdaptorResult.Packages;

                    // Add explicit Take() needed to limit search hijack result set size if $top is specified
                    var totalHits = packages.LongCount();

                    if (return404NotFoundWhenNoResults && totalHits == 0)
                    {
                        return(NotFound());
                    }

                    var pagedQueryable = packages
                                         .Take(options.Top != null ? Math.Min(options.Top.Value, MaxPageSize) : MaxPageSize)
                                         .ToV2FeedPackageQuery(GetSiteRoot(), _configurationService.Features.FriendlyLicenses, semVerLevelKey);

                    return(QueryResult(options, pagedQueryable, MaxPageSize, totalHits, (o, s, resultCount) =>
                                       SearchAdaptor.GetNextLink(Request.RequestUri, resultCount, new { id }, o, s)));
                }
            }
            catch (Exception ex)
            {
                // Swallowing Exception intentionally. If *anything* goes wrong in search, just fall back to the database.
                // We don't want to break package restores. We do want to know if this happens, so here goes:
                QuietLog.LogHandledException(ex);
            }

            if (return404NotFoundWhenNoResults && !packages.Any())
            {
                return(NotFound());
            }

            var queryable = packages.ToV2FeedPackageQuery(
                GetSiteRoot(),
                _configurationService.Features.FriendlyLicenses,
                semVerLevelKey);

            return(QueryResult(options, queryable, MaxPageSize));
        }