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); }
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)); }
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)); }
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)); }
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)); }
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)); }