|
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable disable
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using NuGet.Common;
using NuGet.Configuration;
using NuGet.Packaging;
using NuGet.Packaging.Core;
using NuGet.Packaging.PackageExtraction;
using NuGet.Packaging.Signing;
using NuGet.Versioning;
namespace NuGet.Protocol.Core.Types
{
/// <summary>
/// Contains logics to push or delete packages in Http server or file system
/// </summary>
public class PackageUpdateResource : INuGetResource
{
private const string ServiceEndpoint = "/api/v2/package";
private const string ApiKeyHeader = "X-NuGet-ApiKey";
private const string InvalidApiKey = "invalidapikey";
/// <summary>
/// Create temporary verification api key endpoint: "create-verification-key/[package id]/[package version]"
/// </summary>
private const string TempApiKeyServiceEndpoint = "create-verification-key/{0}/{1}";
private HttpSource _httpSource;
private string _source;
private bool _disableBuffering;
public ISettings Settings { get; set; }
public PackageUpdateResource(string source,
HttpSource httpSource)
{
_source = source;
_httpSource = httpSource;
}
public Uri SourceUri
{
get { return UriUtility.CreateSourceUri(_source); }
}
public async Task Push(
IList<string> packagePaths,
string symbolSource, // empty to not push symbols
int timeoutInSecond,
bool disableBuffering,
Func<string, string> getApiKey,
Func<string, string> getSymbolApiKey,
bool noServiceEndpoint,
bool skipDuplicate,
SymbolPackageUpdateResourceV3 symbolPackageUpdateResource,
ILogger log)
{
await Push(
packagePaths,
symbolSource,
timeoutInSecond,
disableBuffering,
getApiKey,
getSymbolApiKey,
noServiceEndpoint,
skipDuplicate,
symbolPackageUpdateResource,
allowInsecureConnections: false,
log);
}
public async Task PushAsync(
IList<string> packagePaths,
string symbolSource, // empty to not push symbols
int timeoutInSecond,
bool disableBuffering,
Func<string, string> getApiKey,
Func<string, string> getSymbolApiKey,
bool noServiceEndpoint,
bool skipDuplicate,
bool allowSnupkg,
bool allowInsecureConnections,
ILogger log)
{
// TODO: Figure out how to hook this up with the HTTP request
_disableBuffering = disableBuffering;
using var tokenSource = new CancellationTokenSource();
var requestTimeout = TimeSpan.FromSeconds(timeoutInSecond);
tokenSource.CancelAfter(requestTimeout);
var apiKey = getApiKey(_source);
foreach (string packagePath in packagePaths)
{
if (!packagePath.EndsWith(NuGetConstants.SnupkgExtension, StringComparison.OrdinalIgnoreCase))
{
// Push nupkgs and possibly the corresponding snupkgs.
await PushPackagePath(packagePath, _source, symbolSource, apiKey, getSymbolApiKey, noServiceEndpoint, skipDuplicate,
allowSnupkg, allowInsecureConnections, requestTimeout, log, tokenSource.Token);
}
else // Explicit snupkg push
{
// symbolSource is only set when:
// - The user specified it on the command line
// - The endpoint for main package supports pushing snupkgs
if (!string.IsNullOrEmpty(symbolSource))
{
string symbolApiKey = getSymbolApiKey(symbolSource);
await PushSymbolsPath(packagePath, symbolSource, symbolApiKey,
noServiceEndpoint, skipDuplicate, allowSnupkg, allowInsecureConnections,
requestTimeout, log, tokenSource.Token);
}
}
}
}
public async Task Push(
IList<string> packagePaths,
string symbolSource, // empty to not push symbols
int timeoutInSecond,
bool disableBuffering,
Func<string, string> getApiKey,
Func<string, string> getSymbolApiKey,
bool noServiceEndpoint,
bool skipDuplicate,
SymbolPackageUpdateResourceV3 symbolPackageUpdateResource,
bool allowInsecureConnections,
ILogger log)
{
await PushAsync(
packagePaths,
symbolSource,
timeoutInSecond,
disableBuffering,
getApiKey,
getSymbolApiKey,
noServiceEndpoint,
skipDuplicate,
allowSnupkg: symbolPackageUpdateResource is not null,
allowInsecureConnections,
log);
}
public async Task Delete(string packageId,
string packageVersion,
Func<string, string> getApiKey,
Func<string, bool> confirm,
bool noServiceEndpoint,
ILogger log)
{
await Delete(packageId, packageVersion, getApiKey, confirm, noServiceEndpoint, allowInsecureConnections: false, log);
}
public async Task Delete(string packageId,
string packageVersion,
Func<string, string> getApiKey,
Func<string, bool> confirm,
bool noServiceEndpoint,
bool allowInsecureConnections,
ILogger log)
{
var sourceDisplayName = GetSourceDisplayName(_source);
var apiKey = getApiKey(_source);
if (string.IsNullOrEmpty(apiKey) && !IsFileSource())
{
log.LogWarning(string.Format(CultureInfo.CurrentCulture,
Strings.NoApiKeyFound,
sourceDisplayName));
}
if (confirm(string.Format(CultureInfo.CurrentCulture, Strings.DeleteCommandConfirm, packageId, packageVersion, sourceDisplayName)))
{
log.LogWarning(string.Format(
CultureInfo.CurrentCulture,
Strings.DeleteCommandDeletingPackage,
packageId,
packageVersion,
sourceDisplayName
));
await DeletePackage(_source, apiKey, packageId, packageVersion, noServiceEndpoint, allowInsecureConnections, log, CancellationToken.None);
log.LogInformation(string.Format(CultureInfo.CurrentCulture,
Strings.DeleteCommandDeletedPackage,
packageId,
packageVersion));
}
else
{
log.LogInformation(Strings.DeleteCommandCanceled);
}
}
private async Task PushSymbolsPath(string packagePath,
string symbolSource,
string apiKey,
bool noServiceEndpoint,
bool skipDuplicate,
bool allowSnupkg,
bool allowInsecureConnections,
TimeSpan requestTimeout,
ILogger log,
CancellationToken token)
{
// Get the symbol package for this package
string symbolPackagePath = GetSymbolsPath(packagePath, allowSnupkg);
IEnumerable<string> symbolsToPush = LocalFolderUtility.ResolvePackageFromPath(symbolPackagePath, isSnupkg: allowSnupkg);
bool symbolsPathResolved = symbolsToPush != null && symbolsToPush.Any();
//No files were resolved.
if (!symbolsPathResolved)
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
Strings.UnableToFindFile,
packagePath));
}
else
{
Uri symbolSourceUri = UriUtility.CreateSourceUri(symbolSource);
// See if the api key exists
if (string.IsNullOrEmpty(apiKey) && !symbolSourceUri.IsFile)
{
log.LogWarning(string.Format(CultureInfo.CurrentCulture,
Strings.Warning_SymbolServerNotConfigured,
Path.GetFileName(symbolPackagePath),
Strings.DefaultSymbolServer));
}
bool logErrorForHttpSources = true;
foreach (string packageToPush in symbolsToPush)
{
await PushPackageCore(
symbolSource,
apiKey,
packageToPush,
noServiceEndpoint,
skipDuplicate,
requestTimeout,
logErrorForHttpSources,
allowInsecureConnections,
log,
token);
logErrorForHttpSources = false;
}
}
}
/// <summary>
/// Push nupkgs, and if successful, push any corresponding symbols.
/// </summary>
/// <exception cref="ArgumentException">Thrown when any resolved file path does not exist.</exception>
private async Task PushPackagePath(string packagePath,
string source,
string symbolSource, // empty to not push symbols
string apiKey,
Func<string, string> getSymbolApiKey,
bool noServiceEndpoint,
bool skipDuplicate,
bool allowSnupkg,
bool allowInsecureConnections,
TimeSpan requestTimeout,
ILogger log,
CancellationToken token)
{
IEnumerable<string> nupkgsToPush = LocalFolderUtility.ResolvePackageFromPath(packagePath, isSnupkg: false);
bool alreadyWarnedSymbolServerNotConfigured = false;
if (!(nupkgsToPush != null && nupkgsToPush.Any()))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
Strings.UnableToFindFile,
packagePath));
}
Uri packageSourceUri = UriUtility.CreateSourceUri(source);
if (string.IsNullOrEmpty(apiKey) && !packageSourceUri.IsFile)
{
log.LogWarning(string.Format(CultureInfo.CurrentCulture,
Strings.NoApiKeyFound,
GetSourceDisplayName(source)));
}
bool logErrorForHttpSources = true;
foreach (string nupkgToPush in nupkgsToPush)
{
bool packageWasPushed = await PushPackageCore(
source,
apiKey,
nupkgToPush,
noServiceEndpoint,
skipDuplicate,
requestTimeout,
logErrorForHttpSources,
allowInsecureConnections,
log,
token);
// Push corresponding symbols, if successful.
if (packageWasPushed && !string.IsNullOrEmpty(symbolSource))
{
string symbolPackagePath = GetSymbolsPath(nupkgToPush, isSnupkg: allowSnupkg);
// There may not be a snupkg with the same filename. Ignore it since this isn't an explicit snupkg push.
if (!File.Exists(symbolPackagePath))
{
continue;
}
if (!alreadyWarnedSymbolServerNotConfigured)
{
Uri symbolSourceUri = UriUtility.CreateSourceUri(symbolSource);
// See if the api key exists
if (string.IsNullOrEmpty(apiKey) && !symbolSourceUri.IsFile)
{
log.LogWarning(string.Format(CultureInfo.CurrentCulture,
Strings.Warning_SymbolServerNotConfigured,
Path.GetFileName(symbolPackagePath),
Strings.DefaultSymbolServer));
alreadyWarnedSymbolServerNotConfigured = true;
}
}
string symbolApiKey = getSymbolApiKey(symbolSource);
await PushPackageCore(
symbolSource,
symbolApiKey,
symbolPackagePath,
noServiceEndpoint,
skipDuplicate,
requestTimeout,
logErrorForHttpSources,
allowInsecureConnections,
log,
token);
}
logErrorForHttpSources = false;
}
}
private async Task<bool> PushPackageCore(string source,
string apiKey,
string packageToPush,
bool noServiceEndpoint,
bool skipDuplicate,
TimeSpan requestTimeout,
bool logErrorForHttpSources,
bool allowInsecureConnections,
ILogger log,
CancellationToken token)
{
var sourceUri = UriUtility.CreateSourceUri(source);
var sourceName = GetSourceDisplayName(source);
log.LogInformation(string.Format(CultureInfo.CurrentCulture,
Strings.PushCommandPushingPackage,
Path.GetFileName(packageToPush),
sourceName));
bool wasPackagePushed = true;
if (sourceUri.IsFile)
{
await PushPackageToFileSystem(sourceUri, packageToPush, skipDuplicate, log, token);
}
else
{
wasPackagePushed = await PushPackageToServer(source, apiKey, packageToPush, noServiceEndpoint, skipDuplicate,
requestTimeout, logErrorForHttpSources, allowInsecureConnections, log, token);
}
if (wasPackagePushed)
{
log.LogInformation(Strings.PushCommandPackagePushed);
}
return wasPackagePushed;
}
private static string GetSourceDisplayName(string source)
{
if (string.IsNullOrEmpty(source) || source.Equals(NuGetConstants.DefaultGalleryServerUrl, StringComparison.OrdinalIgnoreCase))
{
return Strings.LiveFeed + " (" + NuGetConstants.DefaultGalleryServerUrl + ")";
}
return "'" + source + "'";
}
// Indicates whether the specified source is a file source, such as: \\a\b, c:\temp, etc.
private bool IsFileSource()
{
//we leverage the detection already done at resource provider level.
//that for file system, the "httpSource" is null.
return _httpSource == null;
}
/// <summary>
/// Get the symbols package from the original package. Removes the .nupkg and adds .snupkg or .symbols.nupkg.
/// </summary>
private static string GetSymbolsPath(string packagePath, bool isSnupkg)
{
var symbolPath = Path.GetFileNameWithoutExtension(packagePath) + (isSnupkg ? NuGetConstants.SnupkgExtension : NuGetConstants.SymbolsExtension);
var packageDir = Path.GetDirectoryName(packagePath);
return Path.Combine(packageDir, symbolPath);
}
/// <summary>
/// Pushes a package to the Http server.
/// </summary>
/// <returns>Indicator of whether to show PushCommandPackagePushed message.</returns>
private async Task<bool> PushPackageToServer(string source,
string apiKey,
string pathToPackage,
bool noServiceEndpoint,
bool skipDuplicate,
TimeSpan requestTimeout,
bool logErrorForHttpSources,
bool allowInsecureConnections,
ILogger logger,
CancellationToken token)
{
Uri serviceEndpointUrl = GetServiceEndpointUrl(source, string.Empty, noServiceEndpoint);
bool useTempApiKey = IsSourceNuGetSymbolServer(source);
HttpStatusCode? codeNotToThrow = ConvertSkipDuplicateParamToHttpStatusCode(skipDuplicate);
bool showPushCommandPackagePushed = true;
if (logErrorForHttpSources && serviceEndpointUrl.Scheme == Uri.UriSchemeHttp && !allowInsecureConnections)
{
logger.LogError(string.Format(CultureInfo.CurrentCulture, Strings.Error_HttpServerUsage, "push", serviceEndpointUrl));
return false;
}
if (useTempApiKey)
{
var maxTries = 3;
using (var packageReader = new PackageArchiveReader(pathToPackage))
{
PackageIdentity packageIdentity = packageReader.GetIdentity();
var success = false;
var retry = 0;
while (retry < maxTries && !success)
{
try
{
retry++;
success = true;
// If user push to https://nuget.smbsrc.net/, use temp api key.
string tmpApiKey = await GetSecureApiKey(packageIdentity, apiKey, noServiceEndpoint, requestTimeout, logger, token);
await _httpSource.ProcessResponseAsync(
new HttpSourceRequest(() => CreateRequest(serviceEndpointUrl, pathToPackage, tmpApiKey, logger))
{
RequestTimeout = requestTimeout,
MaxTries = 1
},
response =>
{
HttpStatusCode? responseStatusCode = EnsureSuccessStatusCode(response, codeNotToThrow, logger);
bool logOccurred = DetectAndLogSkippedErrorOccurrence(responseStatusCode, source, pathToPackage, response.ReasonPhrase, logger);
showPushCommandPackagePushed = !logOccurred;
return TaskResult.Zero;
},
logger,
token);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception e)
{
if (retry == maxTries)
{
throw;
}
success = false;
logger.LogInformation(string.Format(
CultureInfo.CurrentCulture,
Strings.Log_RetryingHttp,
HttpMethod.Put,
source)
+ Environment.NewLine
+ ExceptionUtilities.DisplayMessage(e));
}
}
}
}
else
{
await _httpSource.ProcessResponseAsync(
new HttpSourceRequest(() => CreateRequest(serviceEndpointUrl, pathToPackage, apiKey, logger))
{
RequestTimeout = requestTimeout
},
response =>
{
HttpStatusCode? responseStatusCode = EnsureSuccessStatusCode(response, codeNotToThrow, logger);
bool logOccurred = DetectAndLogSkippedErrorOccurrence(responseStatusCode, source, pathToPackage, response.ReasonPhrase, logger);
showPushCommandPackagePushed = !logOccurred;
return TaskResult.Zero;
},
logger,
token);
}
return showPushCommandPackagePushed;
}
/// <summary>
/// Ensures a Success HTTP Status Code is returned unless a specified exclusion occurred. If CodeNotToThrow is provided and the response contains
/// this code, do not EnsureSuccess and instead return the exception code gracefully.
/// </summary>
/// <param name="response"></param>
/// <param name="codeNotToThrow"></param>
/// <param name="logger"></param>
/// <returns>Response StatusCode</returns>
private static HttpStatusCode? EnsureSuccessStatusCode(HttpResponseMessage response, HttpStatusCode? codeNotToThrow, ILogger logger)
{
//If this status code is to be excluded.
if (codeNotToThrow != null && codeNotToThrow == response.StatusCode)
{
return response.StatusCode;
}
else
{
AdvertiseAvailableOptionToIgnore(response.StatusCode, logger);
}
//No exception to the rule specified.
response.EnsureSuccessStatusCode();
return null;
}
/// <summary>
/// Gently log any specified Skipped status code without throwing.
/// </summary>
/// <param name="skippedErrorStatusCode">If provided, it indicates that this StatusCode occurred but was flagged as to be Skipped.</param>
/// <param name="source"></param>
/// <param name="packageIdentity"></param>
/// <param name="reasonMessage"></param>
/// <param name="logger"></param>
/// <returns>Indication of whether the log occurred.</returns>
private static bool DetectAndLogSkippedErrorOccurrence(HttpStatusCode? skippedErrorStatusCode, string source, string packageIdentity,
string reasonMessage, ILogger logger)
{
bool skippedErrorOccurred = false;
if (skippedErrorStatusCode != null)
{
string messageToLog = null;
string messageToLogVerbose = null;
switch (skippedErrorStatusCode.Value)
{
case HttpStatusCode.Conflict:
messageToLog = string.Format(
CultureInfo.CurrentCulture,
Strings.AddPackage_PackageAlreadyExists,
packageIdentity,
source);
messageToLogVerbose = reasonMessage;
skippedErrorOccurred = true;
break;
case HttpStatusCode.BadRequest:
messageToLog = Strings.NupkgPath_Invalid;
skippedErrorOccurred = true;
break;
default: break; //Not a skippable response code.
}
if (messageToLog != null)
{
logger?.LogMinimal(messageToLog);
}
if (messageToLogVerbose != null)
{
logger?.LogVerbose(messageToLogVerbose);
}
}
return skippedErrorOccurred;
}
/// <summary>
/// If we provide such option, output a help message that explains that the error that occurred can be ignored by using it.
/// </summary>
/// <param name="errorCodeThatOccurred">Error to check for a relevant option to advertise to the user. </param>
/// <param name="logger"></param>
private static void AdvertiseAvailableOptionToIgnore(HttpStatusCode errorCodeThatOccurred, ILogger logger)
{
string advertiseDescription = null;
switch (errorCodeThatOccurred)
{
case HttpStatusCode.Conflict:
#if IS_DESKTOP
advertiseDescription = Strings.PushCommandSkipDuplicateAdvertiseNuGetExe;
#else
advertiseDescription = Strings.PushCommandSkipDuplicateAdvertiseDotnetExe;
#endif
break;
default: break; //Not a supported response code.
}
if (advertiseDescription != null)
{
logger?.LogInformation(advertiseDescription);
}
}
private HttpStatusCode? ConvertSkipDuplicateParamToHttpStatusCode(bool skipDuplicate)
{
if (skipDuplicate)
{
return HttpStatusCode.Conflict;
}
return null;
}
private HttpRequestMessage CreateRequest(
Uri serviceEndpointUrl,
string pathToPackage,
string apiKey,
ILogger log)
{
var fileStream = new FileStream(pathToPackage, FileMode.Open, FileAccess.Read, FileShare.Read);
var hasApiKey = !string.IsNullOrEmpty(apiKey);
var request = HttpRequestMessageFactory.Create(
HttpMethod.Put,
serviceEndpointUrl,
new HttpRequestMessageConfiguration(
logger: log,
promptOn403: !hasApiKey)); // Receiving an HTTP 403 when providing an API key typically indicates
// an invalid API key, so prompting for credentials does not help.
var content = new MultipartFormDataContent();
var packageContent = new StreamContent(fileStream);
packageContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");
//"package" and "package.nupkg" are random names for content deserializing
//not tied to actual package name.
content.Add(packageContent, "package", "package.nupkg");
request.Content = content;
// Send the data in chunks so that it can be canceled if auth fails.
// Otherwise the whole package needs to be sent to the server before the PUT fails.
request.Headers.TransferEncodingChunked = true;
if (hasApiKey)
{
request.Headers.Add(ProtocolConstants.ApiKeyHeader, apiKey);
}
return request;
}
private async Task PushPackageToFileSystem(Uri sourceUri,
string pathToPackage,
bool skipDuplicate,
ILogger log,
CancellationToken token)
{
var root = sourceUri.LocalPath;
PackageIdentity packageIdentity = null;
using (var reader = new PackageArchiveReader(pathToPackage))
{
packageIdentity = reader.GetIdentity();
}
if (IsV2LocalRepository(root))
{
var pathResolver = new PackagePathResolver(sourceUri.AbsolutePath, useSideBySidePaths: true);
var packageFileName = pathResolver.GetPackageFileName(packageIdentity);
var fullPath = Path.Combine(root, packageFileName);
File.Copy(pathToPackage, fullPath, overwrite: true);
//Indicate that SkipDuplicate is currently not supported in this scenario.
if (skipDuplicate)
{
log?.LogWarning(Strings.PushCommandSkipDuplicateNotImplemented);
}
}
else
{
var packageExtractionContext = new PackageExtractionContext(
PackageSaveMode.Defaultv3,
PackageExtractionBehavior.XmlDocFileSaveMode,
ClientPolicyContext.GetClientPolicy(Settings, log),
log);
var context = new OfflineFeedAddContext(pathToPackage,
root,
log,
throwIfSourcePackageIsInvalid: true,
throwIfPackageExistsAndInvalid: !skipDuplicate,
throwIfPackageExists: !skipDuplicate,
extractionContext: packageExtractionContext);
await OfflineFeedUtility.AddPackageToSource(context, token);
}
}
// Deletes a package from a Http server or file system
private async Task DeletePackage(string source,
string apiKey,
string packageId,
string packageVersion,
bool noServiceEndpoint,
bool allowInsecureConnections,
ILogger logger,
CancellationToken token)
{
var sourceUri = GetServiceEndpointUrl(source, string.Empty, noServiceEndpoint);
if (sourceUri.IsFile)
{
DeletePackageFromFileSystem(source, packageId, packageVersion);
}
else
{
if (sourceUri.Scheme == Uri.UriSchemeHttp && !allowInsecureConnections)
{
logger.LogError(string.Format(CultureInfo.CurrentCulture, Strings.Error_HttpServerUsage, "delete", sourceUri));
return;
}
await DeletePackageFromServer(source, apiKey, packageId, packageVersion, noServiceEndpoint, logger, token);
}
}
// Deletes a package from a Http server
private async Task DeletePackageFromServer(string source,
string apiKey,
string packageId,
string packageVersion,
bool noServiceEndpoint,
ILogger logger,
CancellationToken token)
{
var url = string.Join("/", packageId, packageVersion);
var serviceEndpointUrl = GetServiceEndpointUrl(source, url, noServiceEndpoint);
await _httpSource.ProcessResponseAsync(
new HttpSourceRequest(
() =>
{
// Review: Do these values need to be encoded in any way?
var hasApiKey = !string.IsNullOrEmpty(apiKey);
var request = HttpRequestMessageFactory.Create(
HttpMethod.Delete,
serviceEndpointUrl,
new HttpRequestMessageConfiguration(
logger: logger,
promptOn403: !hasApiKey)); // Receiving an HTTP 403 when providing an API key typically
// indicates an invalid API key, so prompting for credentials
// does not help.
if (hasApiKey)
{
request.Headers.Add(ProtocolConstants.ApiKeyHeader, apiKey);
}
return request;
}),
response =>
{
response.EnsureSuccessStatusCode();
return TaskResult.Zero;
},
logger,
token);
}
// Deletes a package from a FileSystem.
private void DeletePackageFromFileSystem(string source, string packageId, string packageVersion)
{
var sourceuri = UriUtility.CreateSourceUri(source);
var root = sourceuri.LocalPath;
var resolver = new PackagePathResolver(sourceuri.AbsolutePath, useSideBySidePaths: true);
resolver.GetPackageFileName(new Packaging.Core.PackageIdentity(packageId, new NuGetVersion(packageVersion)));
var packageIdentity = new PackageIdentity(packageId, new NuGetVersion(packageVersion));
if (IsV2LocalRepository(root))
{
var packageFileName = resolver.GetPackageFileName(packageIdentity);
var nupkgFilePath = Path.Combine(root, packageFileName);
if (!File.Exists(nupkgFilePath))
{
throw new ArgumentException(Strings.DeletePackage_NotFound);
}
ForceDeleteFile(nupkgFilePath);
}
else
{
var packageDirectory = OfflineFeedUtility.GetPackageDirectory(packageIdentity, root);
if (!Directory.Exists(packageDirectory))
{
throw new ArgumentException(Strings.DeletePackage_NotFound);
}
ForceDeleteDirectory(packageDirectory);
}
}
// Remove the read-only flag and delete
private void ForceDeleteFile(string fullPath)
{
var attributes = File.GetAttributes(fullPath);
if (attributes.HasFlag(FileAttributes.ReadOnly))
{
File.SetAttributes(fullPath, attributes & ~FileAttributes.ReadOnly);
}
File.Delete(fullPath);
}
//Remove read-only flags from all files under a folder and delete
public static void ForceDeleteDirectory(string path)
{
var directory = new DirectoryInfo(path) { Attributes = FileAttributes.Normal };
foreach (var info in directory.GetFileSystemInfos("*", SearchOption.AllDirectories))
{
info.Attributes = FileAttributes.Normal;
}
directory.Delete(true);
}
// Calculates the URL to the package to.
private Uri GetServiceEndpointUrl(string source, string path, bool noServiceEndpoint)
{
var baseUri = EnsureTrailingSlash(source);
Uri requestUri;
if (string.IsNullOrEmpty(baseUri.AbsolutePath.TrimStart('/')) && !noServiceEndpoint)
{
// If there's no host portion specified, append the url to the client.
requestUri = new Uri(baseUri, ServiceEndpoint + '/' + path);
}
else
{
requestUri = new Uri(baseUri, path);
}
return requestUri;
}
// Ensure a trailing slash at the end
private static Uri EnsureTrailingSlash(string value)
{
if (!value.EndsWith("/", StringComparison.OrdinalIgnoreCase))
{
value += "/";
}
return UriUtility.CreateSourceUri(value);
}
private bool IsV2LocalRepository(string root)
{
if (!Directory.Exists(root) ||
Directory.GetFiles(root, "*.nupkg").Any())
{
// If the repository does not exist or if there are .nupkg in the path, this is a v2-style repository.
return true;
}
foreach (var idDirectory in Directory.GetDirectories(root))
{
if (Directory.GetFiles(idDirectory, "*.nupkg").Any() ||
Directory.GetFiles(idDirectory, "*.nuspec").Any())
{
// ~/Foo/Foo.1.0.0.nupkg (LocalPackageRepository with PackageSaveModes.Nupkg) or
// ~/Foo/Foo.1.0.0.nuspec (LocalPackageRepository with PackageSaveMode.Nuspec)
return true;
}
var idDirectoryName = Path.GetFileName(idDirectory);
foreach (var versionDirectoryPath in Directory.GetDirectories(idDirectory))
{
if (Directory.GetFiles(versionDirectoryPath, idDirectoryName + NuGetConstants.ManifestExtension).Any())
{
// If we have files in the format {packageId}/{version}/{packageId}.nuspec, assume it's an expanded package repository.
return false;
}
}
}
return true;
}
// Get a temp API key from nuget.org for pushing to https://nuget.smbsrc.net/
private async Task<string> GetSecureApiKey(
PackageIdentity packageIdentity,
string apiKey,
bool noServiceEndpoint,
TimeSpan requestTimeout,
ILogger logger,
CancellationToken token)
{
if (string.IsNullOrEmpty(apiKey))
{
return apiKey;
}
var serviceEndpointUrl = GetServiceEndpointUrl(NuGetConstants.DefaultGalleryServerUrl,
string.Format(CultureInfo.InvariantCulture, TempApiKeyServiceEndpoint, packageIdentity.Id, packageIdentity.Version), noServiceEndpoint);
try
{
var result = await _httpSource.GetJObjectAsync(
new HttpSourceRequest(
() =>
{
var request = HttpRequestMessageFactory.Create(
HttpMethod.Post,
serviceEndpointUrl,
new HttpRequestMessageConfiguration(
logger: logger,
promptOn403: false));
request.Headers.Add(ApiKeyHeader, apiKey);
return request;
})
{
RequestTimeout = requestTimeout,
MaxTries = 1
},
logger,
token);
return result.Value<string>("Key") ?? InvalidApiKey;
}
catch (HttpRequestException ex)
{
#if NETCOREAPP
if (ex.Message.Contains("Response status code does not indicate success: 403 (Forbidden).", StringComparison.OrdinalIgnoreCase))
#else
if (ex.Message.Contains("Response status code does not indicate success: 403 (Forbidden)."))
#endif
{
return InvalidApiKey;
}
throw;
}
}
private bool IsSourceNuGetSymbolServer(string source)
{
var sourceUri = UriUtility.CreateSourceUri(source);
return sourceUri.Host.Equals(NuGetConstants.NuGetSymbolHostName, StringComparison.OrdinalIgnoreCase);
}
}
}
|