Represents a normalized, spec-compatible (with NuGet extensions), Semantic Version as defined at http://semver.org
        protected override Expression VisitBinary(BinaryExpression node)
        {
            // Change equality comparisons on Version to normalized comparisons on NormalizedVersion
            if (node.NodeType == ExpressionType.Equal)
            {
                // Figure out which side is the target
                ConstantExpression constSide = (node.Left as ConstantExpression) ?? (node.Right as ConstantExpression);
                if (constSide != null && constSide.Type == typeof(string))
                {
                    MemberExpression memberSide = (node.Right as MemberExpression) ?? (node.Left as MemberExpression);
                    if (memberSide != null && memberSide.Member == _versionMember)
                    {
                        // We have a "Package.Version == <constant>" expression!

                        // Transform the constant version into a normalized version
                        string newVersion = SemanticVersionExtensions.Normalize((string)constSide.Value);

                        // Create a new expression that checks the new constant against NormalizedVersion instead
                        return(Expression.MakeBinary(
                                   ExpressionType.Equal,
                                   left: Expression.Constant(newVersion),
                                   right: Expression.MakeMemberAccess(memberSide.Expression, _normalizedVersionMember)));
                    }
                }
            }
            return(node);
        }
Example #2
0
        public PackageViewModel(Package package)
        {
            _package = package;
            Version  = String.IsNullOrEmpty(package.NormalizedVersion) ?
                       SemanticVersionExtensions.Normalize(package.Version) :
                       package.NormalizedVersion;
            Description         = package.Description;
            ReleaseNotes        = package.ReleaseNotes;
            IconUrl             = package.IconUrl;
            ProjectUrl          = package.ProjectUrl;
            LicenseUrl          = package.LicenseUrl;
            HideLicenseReport   = package.HideLicenseReport;
            LatestVersion       = package.IsLatest;
            LatestStableVersion = package.IsLatestStable;
            LastUpdated         = package.Published;
            Listed           = package.Listed;
            DownloadCount    = package.DownloadCount;
            Prerelease       = package.IsPrerelease;
            LicenseReportUrl = package.LicenseReportUrl;

            var licenseNames = package.LicenseNames;

            if (!String.IsNullOrEmpty(licenseNames))
            {
                LicenseNames = licenseNames.Split(',').Select(l => l.Trim());
            }
        }
 static string BuildFileName(
     string id,
     string version)
 {
     return(string.Format(
                Constants.PackageFileSavePathTemplate,
                id.ToLowerInvariant(),
                SemanticVersionExtensions.Normalize(version).ToLowerInvariant(), // No matter what ends up getting passed in, the version should be normalized
                Constants.NuGetPackageFileExtension));
 }
Example #4
0
        public virtual Package FindPackageByIdAndVersion(string id, string version, bool allowPrerelease = true)
        {
            if (String.IsNullOrWhiteSpace(id))
            {
                throw new ArgumentNullException("id");
            }

            // Optimization: Everytime we look at a package we almost always want to see
            // all the other packages with the same ID via the PackageRegistration property.
            // This resulted in a gnarly query.
            // Instead, we can always query for all packages with the ID.
            IEnumerable <Package> packagesQuery = _packageRepository.GetAll()
                                                  .Include(p => p.LicenseReports)
                                                  .Include(p => p.PackageRegistration)
                                                  .Where(p => (p.PackageRegistration.Id == id));

            if (String.IsNullOrEmpty(version) && !allowPrerelease)
            {
                // If there's a specific version given, don't bother filtering by prerelease. You could be asking for a prerelease package.
                packagesQuery = packagesQuery.Where(p => !p.IsPrerelease);
            }

            var packageVersions = packagesQuery.ToList();

            Package package;

            if (String.IsNullOrEmpty(version))
            {
                package = packageVersions.FirstOrDefault(p => p.IsLatestStable);

                if (package == null && allowPrerelease)
                {
                    package = packageVersions.FirstOrDefault(p => p.IsLatest);
                }

                // If we couldn't find a package marked as latest, then
                // return the most recent one (prerelease ones were already filtered out if appropriate...)
                if (package == null)
                {
                    package = packageVersions.OrderByDescending(p => p.Version).FirstOrDefault();
                }
            }
            else
            {
                package = packageVersions.SingleOrDefault(
                    p => p.PackageRegistration.Id.Equals(id, StringComparison.OrdinalIgnoreCase) &&
                    (
                        String.Equals(p.NormalizedVersion, SemanticVersionExtensions.Normalize(version), StringComparison.OrdinalIgnoreCase)
                    ));
            }
            return(package);
        }
        protected void InterceptPackageMaterialized(Package package)
        {
            if (package == null)
            {
                return;
            }

            var packageNormalizedVersion = String.IsNullOrEmpty(package.NormalizedVersion)
                ? SemanticVersionExtensions.Normalize(package.Version)
                : package.NormalizedVersion;

            int downloadCount;

            if (_downloadCountService.TryGetDownloadCountForPackage(package.PackageRegistration.Id, packageNormalizedVersion, out downloadCount))
            {
                package.DownloadCount = downloadCount;
            }
        }
        private static string BuildFileName(Package package)
        {
            if (package == null)
            {
                throw new ArgumentNullException("package");
            }

            if (package.PackageRegistration == null ||
                String.IsNullOrWhiteSpace(package.PackageRegistration.Id) ||
                (String.IsNullOrWhiteSpace(package.NormalizedVersion) && String.IsNullOrWhiteSpace(package.Version)))
            {
                throw new ArgumentException("The package is missing required data.", "package");
            }

            return(BuildFileName(
                       package.PackageRegistration.Id,
                       String.IsNullOrEmpty(package.NormalizedVersion) ?
                       SemanticVersionExtensions.Normalize(package.Version) :
                       package.NormalizedVersion));
        }
Example #7
0
        public virtual ActionResult DisplayPackage(string id, string version)
        {
            string normalized = SemanticVersionExtensions.Normalize(version);

            if (!String.Equals(version, normalized))
            {
                // Permanent redirect to the normalized one (to avoid multiple URLs for the same content)
                return(RedirectToActionPermanent("DisplayPackage", new { id = id, version = normalized }));
            }

            var package = _packageService.FindPackageByIdAndVersion(id, version);

            if (package == null)
            {
                return(HttpNotFound());
            }
            var model = new DisplayPackageViewModel(package);

            if (package.IsOwner(User))
            {
                // Tell logged-in package owners not to cache the package page, so they won't be confused about the state of pending edits.
                Response.Cache.SetCacheability(HttpCacheability.NoCache);
                Response.Cache.SetNoStore();
                Response.Cache.SetMaxAge(TimeSpan.Zero);
                Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);

                var pendingMetadata = _editPackageService.GetPendingMetadata(package);
                if (pendingMetadata != null)
                {
                    model.SetPendingMetadata(pendingMetadata);
                }
            }

            ViewBag.FacebookAppID = _config.FacebookAppId;
            return(View(model));
        }
        public Document ToDocument()
        {
            var document = new Document();

            // Note: Used to identify index records for updates
            document.Add(new Field("PackageRegistrationKey",
                    Package.PackageRegistrationKey.ToString(CultureInfo.InvariantCulture),
                    Field.Store.YES,
                    Field.Index.NOT_ANALYZED));

            document.Add(new Field("Key", Package.Key.ToString(CultureInfo.InvariantCulture), Field.Store.YES, Field.Index.NOT_ANALYZED));

            if (CuratedFeedKeys != null)
            {
                foreach (var feedKey in CuratedFeedKeys)
                {
                    document.Add(new Field("CuratedFeedKey", feedKey.ToString(CultureInfo.InvariantCulture), Field.Store.NO, Field.Index.NOT_ANALYZED));
                }
            }

            var field = new Field("Id-Exact", Package.PackageRegistration.Id.ToLowerInvariant(), Field.Store.NO, Field.Index.NOT_ANALYZED);

            field.Boost = 2.5f;
            document.Add(field);

            // Store description so we can show them in search results
            field = new Field("Description", Package.Description, Field.Store.YES, Field.Index.ANALYZED);
            field.Boost = 0.1f;
            document.Add(field);

            // We store the Id/Title field in multiple ways, so that it's possible to match using multiple
            // styles of search
            // Note: no matter which way we store it, it will also be processed by the Analyzer later.

            // Style 1: As-Is Id, no tokenizing (so you can search using dot or dash-joined terms)
            // Boost this one
            field = new Field("Id", Package.PackageRegistration.Id, Field.Store.NO, Field.Index.ANALYZED);
            document.Add(field);

            // Style 2: dot+dash tokenized (so you can search using undotted terms)
            field = new Field("Id", SplitId(Package.PackageRegistration.Id), Field.Store.NO, Field.Index.ANALYZED);
            field.Boost = 0.8f;
            document.Add(field);

            // Style 3: camel-case tokenized (so you can search using parts of the camelCasedWord). 
            // De-boosted since matches are less likely to be meaningful
            field = new Field("Id", CamelSplitId(Package.PackageRegistration.Id), Field.Store.NO, Field.Index.ANALYZED);
            field.Boost = 0.25f;
            document.Add(field);

            // If an element does not have a Title, fall back to Id, same as the website.
            var workingTitle = String.IsNullOrEmpty(Package.Title)
                                   ? Package.PackageRegistration.Id
                                   : Package.Title;

            // As-Is (stored for search results)
            field = new Field("Title", workingTitle, Field.Store.YES, Field.Index.ANALYZED);
            field.Boost = 0.9f;
            document.Add(field);

            // no need to store dot+dash tokenized - we'll handle this in the analyzer
            field = new Field("Title", SplitId(workingTitle), Field.Store.NO, Field.Index.ANALYZED);
            field.Boost = 0.8f;
            document.Add(field);

            // camel-case tokenized
            field = new Field("Title", CamelSplitId(workingTitle), Field.Store.NO, Field.Index.ANALYZED);
            field.Boost = 0.5f;
            document.Add(field);

            if (!String.IsNullOrEmpty(Package.Tags))
            {
                // Store tags so we can show them in search results
                field = new Field("Tags", Package.Tags, Field.Store.YES, Field.Index.ANALYZED);
                field.Boost = 0.8f;
                document.Add(field);
            }

            document.Add(new Field("Authors", Package.FlattenedAuthors.ToStringSafe(), Field.Store.YES, Field.Index.ANALYZED));

            // Fields for storing data to avoid hitting SQL while doing searches
            if (!String.IsNullOrEmpty(Package.IconUrl))
            {
                document.Add(new Field("IconUrl", Package.IconUrl, Field.Store.YES, Field.Index.NO));
            }

            if (Package.PackageRegistration.Owners.AnySafe())
            {
                string flattenedOwners = String.Join(";", Package.PackageRegistration.Owners.Select(o => o.Username));
                document.Add(new Field("Owners", flattenedOwners, Field.Store.NO, Field.Index.ANALYZED));
                document.Add(new Field("FlattenedOwners", flattenedOwners, Field.Store.YES, Field.Index.NO));
            }

            document.Add(new Field("Copyright", Package.Copyright.ToStringSafe(), Field.Store.YES, Field.Index.NO));
            document.Add(new Field("Created", Package.Created.ToString(CultureInfo.InvariantCulture), Field.Store.YES, Field.Index.NO));
            document.Add(new Field("FlattenedDependencies", Package.FlattenedDependencies.ToStringSafe(), Field.Store.YES, Field.Index.NO));
            document.Add(new Field("Hash", Package.Hash.ToStringSafe(), Field.Store.YES, Field.Index.NO));
            document.Add(new Field("HashAlgorithm", Package.HashAlgorithm.ToStringSafe(), Field.Store.YES, Field.Index.NO));
            document.Add(new Field("Id-Original", Package.PackageRegistration.Id, Field.Store.YES, Field.Index.NO));
            document.Add(new Field("LastUpdated", Package.LastUpdated.ToString(CultureInfo.InvariantCulture), Field.Store.YES, Field.Index.NO));
            if (Package.LastEdited != null)
            {
                document.Add(new Field("LastEdited", Package.LastEdited.Value.ToString(CultureInfo.InvariantCulture), Field.Store.YES, Field.Index.NO));
            }

            document.Add(new Field("Language", Package.Language.ToStringSafe(), Field.Store.YES, Field.Index.NO));
            document.Add(new Field("LicenseUrl", Package.LicenseUrl.ToStringSafe(), Field.Store.YES, Field.Index.NO));
            document.Add(new Field("MinClientVersion", Package.MinClientVersion.ToStringSafe(), Field.Store.YES, Field.Index.NO));
            document.Add(new Field("Version", Package.Version.ToStringSafe(), Field.Store.YES, Field.Index.NO));
            
            string normalizedVersion = String.IsNullOrEmpty(Package.NormalizedVersion) ? 
                SemanticVersionExtensions.Normalize(Package.Version) : 
                Package.NormalizedVersion;
            document.Add(new Field("NormalizedVersion", normalizedVersion.ToStringSafe(), Field.Store.YES, Field.Index.NO));
            
            document.Add(new Field("VersionDownloadCount", Package.DownloadCount.ToString(CultureInfo.InvariantCulture), Field.Store.YES, Field.Index.NO));
            document.Add(new Field("PackageFileSize", Package.PackageFileSize.ToString(CultureInfo.InvariantCulture), Field.Store.YES, Field.Index.NO));
            document.Add(new Field("ProjectUrl", Package.ProjectUrl.ToStringSafe(), Field.Store.YES, Field.Index.NO));
            document.Add(new Field("Published", Package.Published.ToString(CultureInfo.InvariantCulture), Field.Store.YES, Field.Index.NO));
            document.Add(new Field("ReleaseNotes", Package.ReleaseNotes.ToStringSafe(), Field.Store.YES, Field.Index.NO));
            document.Add(new Field("RequiresLicenseAcceptance", Package.RequiresLicenseAcceptance.ToString(), Field.Store.YES, Field.Index.NO));
            document.Add(new Field("Summary", Package.Summary.ToStringSafe(), Field.Store.YES, Field.Index.NO));
            document.Add(new Field("LicenseNames", Package.LicenseNames.ToStringSafe(), Field.Store.YES, Field.Index.NO));
            document.Add(new Field("LicenseReportUrl", Package.LicenseReportUrl.ToStringSafe(), Field.Store.YES, Field.Index.NO));
            document.Add(new Field("HideLicenseReport", Package.HideLicenseReport.ToStringSafe(), Field.Store.YES, Field.Index.NO));

            if (Package.SupportedFrameworks.AnySafe())
            {
                string joinedFrameworks = string.Join(";", Package.SupportedFrameworks.Select(f => f.FrameworkName));
                document.Add(new Field("JoinedSupportedFrameworks", joinedFrameworks, Field.Store.YES,
                                       Field.Index.NO));
            }

            // Fields meant for filtering, also storing data to avoid hitting SQL while doing searches
            document.Add(new Field("IsLatest", Package.IsLatest.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
            document.Add(new Field("IsLatestStable", Package.IsLatestStable.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));

            // Fields meant for filtering, sorting
            document.Add(new Field("PublishedDate", Package.Published.Ticks.ToString(CultureInfo.InvariantCulture), Field.Store.NO, Field.Index.NOT_ANALYZED));
            document.Add(new Field("EditedDate", (Package.LastEdited ?? Package.Published).Ticks.ToString(CultureInfo.InvariantCulture), Field.Store.NO, Field.Index.NOT_ANALYZED));
            document.Add(
                 new Field("DownloadCount", Package.PackageRegistration.DownloadCount.ToString(CultureInfo.InvariantCulture), Field.Store.YES, Field.Index.NOT_ANALYZED));

            string displayName = String.IsNullOrEmpty(Package.Title) ? Package.PackageRegistration.Id : Package.Title;
            document.Add(new Field("DisplayName", displayName.ToLower(CultureInfo.CurrentCulture), Field.Store.NO, Field.Index.NOT_ANALYZED));

            return document;
        }
        public virtual async Task <ActionResult> GetPackage(string id, string version)
        {
            // some security paranoia about URL hacking somehow creating e.g. open redirects
            // validate user input: explicit calls to the same validators used during Package Registrations
            // Ideally shouldn't be necessary?
            if (!PackageIdValidator.IsValidPackageId(id ?? ""))
            {
                return(new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, "The format of the package id is invalid"));
            }

            if (!String.IsNullOrEmpty(version))
            {
                SemanticVersion dummy;
                if (!SemanticVersion.TryParse(version, out dummy))
                {
                    return(new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, "The package version is not a valid semantic version"));
                }
            }

            // Normalize the version
            version = SemanticVersionExtensions.Normalize(version);

            // if the version is null, the user is asking for the latest version. Presumably they don't want includePrerelease release versions.
            // The allow prerelease flag is ignored if both partialId and version are specified.
            // In general we want to try to add download statistics for any package regardless of whether a version was specified.

            Package package = null;

            try
            {
                package = PackageService.FindPackageByIdAndVersion(id, version, allowPrerelease: false);
                if (package == null)
                {
                    return(new HttpStatusCodeWithBodyResult(
                               HttpStatusCode.NotFound, String.Format(CultureInfo.CurrentCulture, Strings.PackageWithIdAndVersionNotFound, id, version)));
                }

                try
                {
                    var stats = new PackageStatistics
                    {
                        // IMPORTANT: Timestamp is managed by the database.
                        IPAddress        = Request.UserHostAddress,
                        UserAgent        = Request.UserAgent,
                        Package          = package,
                        Operation        = Request.Headers["NuGet-Operation"],
                        DependentPackage = Request.Headers["NuGet-DependentPackage"],
                        ProjectGuids     = Request.Headers["NuGet-ProjectGuids"],
                    };

                    PackageService.AddDownloadStatistics(stats);
                }
                catch (ReadOnlyModeException)
                {
                    // *gulp* Swallowed. It's OK not to add statistics and ok to not log errors in read only mode.
                }
                catch (SqlException e)
                {
                    // Log the error and continue
                    QuietLog.LogHandledException(e);
                }
                catch (DataException e)
                {
                    // Log the error and continue
                    QuietLog.LogHandledException(e);
                }
            }
            catch (SqlException e)
            {
                QuietLog.LogHandledException(e);
            }
            catch (DataException e)
            {
                QuietLog.LogHandledException(e);
            }

            // Fall back to constructing the URL based on the package version and ID.
            if (String.IsNullOrEmpty(version) && package == null)
            {
                // Database was unavailable and we don't have a version, return a 503
                return(new HttpStatusCodeWithBodyResult(HttpStatusCode.ServiceUnavailable, Strings.DatabaseUnavailable_TrySpecificVersion));
            }
            return(await PackageFileService.CreateDownloadPackageActionResultAsync(
                       HttpContext.Request.Url,
                       id,
                       String.IsNullOrEmpty(version)?package.NormalizedVersion : version));
        }
Example #10
0
        public virtual async Task <ActionResult> DisplayPackage(string id, string version)
        {
            string normalized = SemanticVersionExtensions.Normalize(version);

            if (!string.Equals(version, normalized))
            {
                // Permanent redirect to the normalized one (to avoid multiple URLs for the same content)
                return(RedirectToActionPermanent("DisplayPackage", new { id = id, version = normalized }));
            }

            var package = _packageService.FindPackageByIdAndVersion(id, version);

            if (package == null)
            {
                return(HttpNotFound());
            }
            var model = new DisplayPackageViewModel(package);

            if (package.IsOwner(User))
            {
                // Tell logged-in package owners not to cache the package page,
                // so they won't be confused about the state of pending edits.
                Response.Cache.SetCacheability(HttpCacheability.NoCache);
                Response.Cache.SetNoStore();
                Response.Cache.SetMaxAge(TimeSpan.Zero);
                Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);

                var pendingMetadata = _editPackageService.GetPendingMetadata(package);
                if (pendingMetadata != null)
                {
                    model.SetPendingMetadata(pendingMetadata);
                }
            }

            var externalSearchService = _searchService as ExternalSearchService;

            if (_searchService.ContainsAllVersions && externalSearchService != null)
            {
                var isIndexedCacheKey = string.Format("IsIndexed_{0}_{1}", package.PackageRegistration.Id, package.Version);
                var isIndexed         = HttpContext.Cache.Get(isIndexedCacheKey) as bool?;
                if (!isIndexed.HasValue)
                {
                    var searchFilter = SearchAdaptor.GetSearchFilter(
                        "id:\"" + package.PackageRegistration.Id + "\" AND version:\"" + package.Version + "\"",
                        1, null, SearchFilter.ODataSearchContext);
                    var results = await externalSearchService.RawSearch(searchFilter);

                    isIndexed = results.Hits > 0;

                    var expiration = Cache.NoAbsoluteExpiration;
                    if (!isIndexed.Value)
                    {
                        expiration = DateTime.UtcNow.Add(TimeSpan.FromSeconds(30));
                    }

                    HttpContext.Cache.Add(isIndexedCacheKey,
                                          isIndexed,
                                          null,
                                          expiration,
                                          Cache.NoSlidingExpiration,
                                          CacheItemPriority.Default, null);
                }

                model.IsIndexed = isIndexed;
            }

            ViewBag.FacebookAppID = _config.FacebookAppId;
            return(View(model));
        }
Example #11
0
        public virtual async Task <ActionResult> GetPackage(string id, string version)
        {
            // some security paranoia about URL hacking somehow creating e.g. open redirects
            // validate user input: explicit calls to the same validators used during Package Registrations
            // Ideally shouldn't be necessary?
            if (!PackageIdValidator.IsValidPackageId(id ?? ""))
            {
                return(new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, "The format of the package id is invalid"));
            }

            // if version is non-null, check if it's semantically correct and normalize it.
            if (!String.IsNullOrEmpty(version))
            {
                SemanticVersion dummy;
                if (!SemanticVersion.TryParse(version, out dummy))
                {
                    return(new HttpStatusCodeWithBodyResult(HttpStatusCode.BadRequest, "The package version is not a valid semantic version"));
                }
                // Normalize the version
                version = SemanticVersionExtensions.Normalize(version);
            }
            else
            {
                // if version is null, get the latest version from the database.
                // This ensures that on package restore scenario where version will be non null, we don't hit the database.
                try
                {
                    var package = PackageService.FindPackageByIdAndVersion(id, version, allowPrerelease: false);
                    if (package == null)
                    {
                        return(new HttpStatusCodeWithBodyResult(HttpStatusCode.NotFound, String.Format(CultureInfo.CurrentCulture, Strings.PackageWithIdAndVersionNotFound, id, version)));
                    }
                    version = package.NormalizedVersion;
                }
                catch (SqlException e)
                {
                    QuietLog.LogHandledException(e);

                    // Database was unavailable and we don't have a version, return a 503
                    return(new HttpStatusCodeWithBodyResult(HttpStatusCode.ServiceUnavailable, Strings.DatabaseUnavailable_TrySpecificVersion));
                }
                catch (DataException e)
                {
                    QuietLog.LogHandledException(e);

                    // Database was unavailable and we don't have a version, return a 503
                    return(new HttpStatusCodeWithBodyResult(HttpStatusCode.ServiceUnavailable, Strings.DatabaseUnavailable_TrySpecificVersion));
                }
            }

            // If metrics service is specified we post the data to it asynchronously. Else we skip stats.
            if (_config != null && _config.MetricsServiceUri != null)
            {
                // Disable warning about not awaiting async calls because we are _intentionally_ not awaiting this.
#pragma warning disable 4014
                Task.Run(() => PostDownloadStatistics(id, version, Request.UserHostAddress, Request.UserAgent, Request.Headers["NuGet-Operation"], Request.Headers["NuGet-DependentPackage"], Request.Headers["NuGet-ProjectGuids"]));
#pragma warning restore 4014
            }

            return(await PackageFileService.CreateDownloadPackageActionResultAsync(
                       HttpContext.Request.Url,
                       id, version));
        }