From e4c78d4d5fc74b1c8b3c375901655623acf7a898 Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Wed, 28 Oct 2020 16:34:46 -0700 Subject: [PATCH 1/5] TODO --- SourceLink.sln | 7 +++++++ eng/Versions.props | 3 +++ 2 files changed, 10 insertions(+) diff --git a/SourceLink.sln b/SourceLink.sln index 49104dbd28..2264fe97bf 100644 --- a/SourceLink.sln +++ b/SourceLink.sln @@ -68,8 +68,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.SourceLink.Gitea" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.SourceLink.Gitea.UnitTests", "src\SourceLink.Gitea.UnitTests\Microsoft.SourceLink.Gitea.UnitTests.csproj", "{04C95AC8-E3A4-4A2B-94E6-4C62E910FD8A}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-sourcelink", "src\dotnet-sourcelink\dotnet-sourcelink.csproj", "{4376B613-CD5B-4274-9071-30989769B0B2}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution + src\SourceLink.Tools\Microsoft.SourceLink.Tools.projitems*{4376b613-cd5b-4274-9071-30989769b0b2}*SharedItemsImports = 5 src\SourceLink.Tools\Microsoft.SourceLink.Tools.projitems*{5df76cc2-5f0e-45a6-ad56-6bbbccbc1a78}*SharedItemsImports = 13 src\SourceLink.Tools\Microsoft.SourceLink.Tools.projitems*{99d113a9-24ec-471d-9f74-d2ac2f16220b}*SharedItemsImports = 5 EndGlobalSection @@ -178,6 +181,10 @@ Global {04C95AC8-E3A4-4A2B-94E6-4C62E910FD8A}.Debug|Any CPU.Build.0 = Debug|Any CPU {04C95AC8-E3A4-4A2B-94E6-4C62E910FD8A}.Release|Any CPU.ActiveCfg = Release|Any CPU {04C95AC8-E3A4-4A2B-94E6-4C62E910FD8A}.Release|Any CPU.Build.0 = Release|Any CPU + {4376B613-CD5B-4274-9071-30989769B0B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4376B613-CD5B-4274-9071-30989769B0B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4376B613-CD5B-4274-9071-30989769B0B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4376B613-CD5B-4274-9071-30989769B0B2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/eng/Versions.props b/eng/Versions.props index b6f079e418..635cfc0b1f 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -15,6 +15,9 @@ 3.1.6 5.7.0 5.7.0 + 2.0.0-beta1.20371.2 + 0.3.0-alpha.20371.2 + 1.8.1 4.5.0 4.7.2 From cd37fcdea6666424956ef058c74c60fbe113a642 Mon Sep 17 00:00:00 2001 From: Tomas Matousek Date: Wed, 2 Dec 2020 09:38:40 -0800 Subject: [PATCH 2/5] Add dotnet-sourcelink --- .../AuthenticationHeaderProvider.cs | 30 + src/dotnet-sourcelink/HashAlgorithmGuids.cs | 25 + src/dotnet-sourcelink/LanguageGuids.cs | 21 + src/dotnet-sourcelink/Program.cs | 562 ++++++++++++++++++ .../dotnet-sourcelink.csproj | 26 + .../runtimeconfig.template.json | 3 + 6 files changed, 667 insertions(+) create mode 100644 src/dotnet-sourcelink/AuthenticationHeaderProvider.cs create mode 100644 src/dotnet-sourcelink/HashAlgorithmGuids.cs create mode 100644 src/dotnet-sourcelink/LanguageGuids.cs create mode 100644 src/dotnet-sourcelink/Program.cs create mode 100644 src/dotnet-sourcelink/dotnet-sourcelink.csproj create mode 100644 src/dotnet-sourcelink/runtimeconfig.template.json diff --git a/src/dotnet-sourcelink/AuthenticationHeaderProvider.cs b/src/dotnet-sourcelink/AuthenticationHeaderProvider.cs new file mode 100644 index 0000000000..9065b83276 --- /dev/null +++ b/src/dotnet-sourcelink/AuthenticationHeaderProvider.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net.Http.Headers; +using System.Text; + +namespace Microsoft.SourceLink.Tools +{ + internal interface IAuthenticationHeaderValueProvider + { + AuthenticationHeaderValue GetValue(); + } + + internal sealed class BasicAuthenticationHeaderValueProvider : IAuthenticationHeaderValueProvider + { + private readonly string _username; + private readonly string _password; + private readonly Encoding _encoding; + + public BasicAuthenticationHeaderValueProvider(string username, string password, Encoding encoding) + { + _username = username; + _password = password; + _encoding = encoding; + } + + public AuthenticationHeaderValue GetValue() + => new("Basic", Convert.ToBase64String(_encoding.GetBytes($"{_username}:{_password}"))); + } +} diff --git a/src/dotnet-sourcelink/HashAlgorithmGuids.cs b/src/dotnet-sourcelink/HashAlgorithmGuids.cs new file mode 100644 index 0000000000..087cdca983 --- /dev/null +++ b/src/dotnet-sourcelink/HashAlgorithmGuids.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Security.Cryptography; + +namespace Microsoft.SourceLink.Tools +{ + internal static class HashAlgorithmGuids + { + public static readonly Guid MD5 = new Guid("406ea660-64cf-4c82-b6f0-42d48172a799"); + public static readonly Guid Sha1 = new("ff1816ec-aa5e-4d10-87f7-6f4963833460"); + public static readonly Guid Sha256 = new("8829d00f-11b8-4213-878b-770e8597ac16"); + + public static HashAlgorithmName? TryGetName(Guid guid) + { + if (guid == MD5) return new HashAlgorithmName("MD5"); + if (guid == Sha1) return new HashAlgorithmName("SHA1"); + if (guid == Sha256) return new HashAlgorithmName("SHA256"); + return null; + } + + public static HashAlgorithmName GetName(Guid guid) + => TryGetName(guid) ?? throw new CryptographicException("unknown HashAlgorithm " + guid); + } +} diff --git a/src/dotnet-sourcelink/LanguageGuids.cs b/src/dotnet-sourcelink/LanguageGuids.cs new file mode 100644 index 0000000000..cb66c92352 --- /dev/null +++ b/src/dotnet-sourcelink/LanguageGuids.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.SourceLink.Tools +{ + internal static class LanguageGuids + { + public static readonly Guid CSharp = new("3f5162f8-07c6-11d3-9053-00c04fa302a1"); + public static readonly Guid FSharp = new("ab4f38c9-b6e6-43ba-be3b-58080b2ccce3"); + public static readonly Guid VisualBasic = new("3a12d0b8-c26c-11d0-b442-00a0244a1dd2"); + + public static string GetName(Guid guid) + { + if (guid == CSharp) return "C#"; + if (guid == FSharp) return "F#"; + if (guid == VisualBasic) return "VB"; + return guid.ToString(); + } + } +} diff --git a/src/dotnet-sourcelink/Program.cs b/src/dotnet-sourcelink/Program.cs new file mode 100644 index 0000000000..42cc697897 --- /dev/null +++ b/src/dotnet-sourcelink/Program.cs @@ -0,0 +1,562 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.CommandLine; +using System.CommandLine.Invocation; +using System.CommandLine.Parsing; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Packaging; + +namespace Microsoft.SourceLink.Tools +{ + internal sealed class Program + { + private static readonly Guid s_sourceLinkCustomDebugInformationId = new("CC110556-A091-4D38-9FEC-25AB9A351A6A"); + private static readonly Guid s_embeddedSourceCustomDebugInformationId = new("0E8A571B-6926-466E-B4AD-8AB04611F5FE"); + private static readonly byte[] s_crlfBytes = { (byte)'\r', (byte)'\n' }; + private static readonly ProductInfoHeaderValue s_sourceLinkProductHeaderValue = new("SourceLink", GetSourceLinkVersion()); + + private static class AuthenticationMethod + { + public const string Basic = "basic"; + } + + private record DocumentInfo( + string ContainingFile, + string Name, + string? Uri, + bool IsEmbedded, + ImmutableArray Hash, + Guid HashAlgorithm); + + private readonly IConsole _console; + private bool _errorReported; + + public Program(IConsole console) + { + _console = console; + } + + public static async Task Main(string[] args) + { + var rootCommand = GetRootCommand(); + return await rootCommand.InvokeAsync(args); + } + + private static string GetSourceLinkVersion() + { + var attribute = (AssemblyInformationalVersionAttribute)typeof(Program).Assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false).Single(); + return attribute.InformationalVersion.Split('+').First(); + } + + private static RootCommand GetRootCommand() + { + var authEncodingArg = new Argument( + name: "encoding-name", + parse: arg => Encoding.GetEncoding(arg.Tokens.Single().Value)) + { + Arity = ArgumentArity.ExactlyOne + }; + + authEncodingArg.AddValidator(arg => + { + var name = arg.Tokens.Single().Value; + + try + { + _ = Encoding.GetEncoding(name); + return null; + } + catch + { + return $"Encoding '{name}' not supported"; + } + }); + + var test = new Command("test", "TODO") + { + new Argument("path", "Path to an assembly or .pdb"), + new Option(new[] { "--auth", "-a" }, "Authentication method") + { + Argument = new Argument(name: "method", () => AuthenticationMethod.Basic) { Arity = ArgumentArity.ExactlyOne }.FromAmong(AuthenticationMethod.Basic) + }, + new Option(new[] { "--auth-encoding", "-e" }, "Encoding to use for authentication value") + { + Argument = authEncodingArg, + }, + new Option(new[] { "--user", "-u" }, "Username to use to authenticate") + { + Argument = new Argument(name: "user-name") { Arity = ArgumentArity.ExactlyOne } + }, + new Option(new[] { "--password", "-p" }, "Password to use to authenticate") + { + Argument = new Argument() { Arity = ArgumentArity.ExactlyOne } + }, + }; + test.Handler = CommandHandler.Create(TestAsync); + + var printJson = new Command("print-json", "Print Source Link JSON stored in the PDB") + { + new Argument("path", "Path to an assembly or .pdb"), + }; + printJson.Handler = CommandHandler.Create(PrintJsonAsync); + + var printDocuments = new Command("print-documents", "TODO") + { + new Argument("path", "Path to an assembly or .pdb"), + }; + printDocuments.Handler = CommandHandler.Create(PrintDocumentsAsync); + + var printUrls = new Command("print-urls", "TODO") + { + new Argument("path", "Path to an assembly or .pdb"), + }; + printUrls.Handler = CommandHandler.Create(PrintUrlsAsync); + + var root = new RootCommand() + { + test, + printJson, + printDocuments, + printUrls, + }; + + root.Description = "dotnet-sourcelink"; + + root.AddValidator(commandResult => + { + if (commandResult.OptionResult("--auth") != null) + { + if (commandResult.OptionResult("--user") == null || commandResult.OptionResult("--password") == null) + { + return "Specify --user and --password options"; + } + } + + return null; + }); + + return root; + } + + private void ReportError(string message) + { + _console.Error.Write(message); + _console.Error.Write(Environment.NewLine); + _errorReported = true; + } + + private void WriteOutputLine(string message) + { + _console.Out.Write(message); + _console.Out.Write(Environment.NewLine); + } + + private static async Task TestAsync( + string path, + string? authMethod, + Encoding? authEncoding, + string? user, + string? password, + IConsole console) + { + var authenticationHeader = (authMethod != null) ? GetAuthenticationHeader(authMethod, authEncoding ?? Encoding.ASCII, user!, password!) : null; + + var cancellationSource = new CancellationTokenSource(); + Console.CancelKeyPress += (sender, e) => + { + e.Cancel = true; + cancellationSource.Cancel(); + }; + + try + { + return await new Program(console).TestAsync(path, authenticationHeader, cancellationSource.Token).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + console.Error.Write("Operation canceled."); + console.Error.Write(Environment.NewLine); + return -1; + } + } + + private async Task TestAsync(string path, AuthenticationHeaderValue? authenticationHeader, CancellationToken cancellationToken) + { + var documents = new List(); + if (string.Equals(Path.GetExtension(path), ".nupkg", StringComparison.OrdinalIgnoreCase)) + { + await ReadAndResolveDocumentsFromPackageAsync(path, documents, cancellationToken).ConfigureAwait(false); + } + else + { + ReadAndResolveDocuments(path, documents); + } + + if (documents.Count == 0) + { + return _errorReported ? 1 : 0; + } + + var handler = new HttpClientHandler(); + if (handler.SupportsAutomaticDecompression) + handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + + using var client = new HttpClient(handler); + client.DefaultRequestHeaders.UserAgent.Add(s_sourceLinkProductHeaderValue); + client.DefaultRequestHeaders.Authorization = authenticationHeader; + + var outputLock = new object(); + + var errorReporter = new Action(message => + { + lock (outputLock) + { + ReportError(message); + } + }); + + var tasks = documents.Where(document => document.Uri != null).Select(document => DownloadAndValidateDocumentAsync(client, document, errorReporter, cancellationToken)); + + _ = await Task.WhenAll(tasks).ConfigureAwait(false); + + if (_errorReported) + { + return 1; + } + + WriteOutputLine($"File '{path}' validated."); + return 0; + } + + private static async Task DownloadAndValidateDocumentAsync(HttpClient client, DocumentInfo document, Action reportError, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + using var request = new HttpRequestMessage(HttpMethod.Get, document.Uri); + using var response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false); + + if (!response.IsSuccessStatusCode) + { + reportError($"Failed to download '{document.Uri}': {response.ReasonPhrase} ({response.StatusCode})"); + return false; + } + + var algorithmName = HashAlgorithmGuids.GetName(document.HashAlgorithm); + + // TODO: consider reusing buffers and IncrementalHash instances + + var content = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false); + + // When git core.autocrfl option is true git replaces LF with CRLF on checkout, but only if the file has consistent line endings. + // Line endings in files with mixed line endings are left unchanged. + // The checksums stored in the PDB reflect the content of the checked out file on a build server, + // hence they are calculated with the line endings changed. + // First, check if the raw file checksum matches the PDB then check if file with LF converted to CRLF matches. + + cancellationToken.ThrowIfCancellationRequested(); + + using var incrementalHash = IncrementalHash.CreateHash(algorithmName); + + incrementalHash.AppendData(content); + var rawHash = incrementalHash.GetHashAndReset(); + if (document.Hash.SequenceEqual(rawHash)) + { + return true; + } + + cancellationToken.ThrowIfCancellationRequested(); + + var crlfHash = TryCalculateHashWithLineBreakSubstituted(content, incrementalHash); + if (crlfHash != null && document.Hash.SequenceEqual(crlfHash)) + { + return true; + } + + reportError($"Checksum validation failed for '{document.Uri}'."); + return false; + } + + private static byte[]? TryCalculateHashWithLineBreakSubstituted(byte[] content, IncrementalHash incrementalHash) + { + int index = 0; + while (true) + { + int lf = Array.IndexOf(content, (byte)'\r', index); + if (lf < 0) + { + incrementalHash.AppendData(content, index, content.Length - index); + return incrementalHash.GetHashAndReset(); + } + + if (index + 1 < content.Length && content[index + 1] == (byte)'\n') + { + // The file either has CRLF line endings or mixed line endings. + // In either case there is no need to substitute LF to CRLF. + _ = incrementalHash.GetHashAndReset(); + return null; + } + + incrementalHash.AppendData(content, index, lf - index); + incrementalHash.AppendData(s_crlfBytes); + index = lf + 1; + } + } + + private static Task PrintJsonAsync(string path, IConsole console) + => Task.FromResult(new Program(console).PrintJson(path)); + + private int PrintJson(string path) + { + ReadPdbMetadata(path, (filePath, metadataReader) => + { + var sourceLink = ReadSourceLink(metadataReader); + + if (sourceLink == null) + { + ReportError($"Source Link record not found in {filePath}."); + } + else + { + WriteOutputLine(sourceLink); + } + }); + + return _errorReported ? 1 : 0; + } + + private static Task PrintDocumentsAsync(string path, IConsole console) + => Task.FromResult(new Program(console).PrintDocuments(path)); + + public static string ToHex(byte[] bytes) + => BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant(); + + private int PrintDocuments(string path) + { + ReadPdbMetadata(path, (_, metadataReader) => + { + foreach (var documentHandle in metadataReader.Documents) + { + var document = metadataReader.GetDocument(documentHandle); + var hash = metadataReader.GetBlobBytes(document.Hash); + var hashAlgorithm = metadataReader.GetGuid(document.HashAlgorithm); + var language = metadataReader.GetGuid(document.Language); + var name = metadataReader.GetString(document.Name); + + WriteOutputLine($"'{name}' {ToHex(hash)} {HashAlgorithmGuids.TryGetName(hashAlgorithm)?.Name ?? hashAlgorithm.ToString()} {LanguageGuids.GetName(language)}"); + } + }); + + return _errorReported ? 1 : 0; + } + + private static Task PrintUrlsAsync(string path,IConsole console) + => Task.FromResult(new Program(console).PrintUrls(path)); + + private int PrintUrls(string path) + { + var resolvedDocuments = new List(); + ReadAndResolveDocuments(path, resolvedDocuments); + + int unresolvedCount = 0; + foreach (var document in resolvedDocuments) + { + if (document.IsEmbedded) + { + WriteOutputLine($"'{document.Name}': embedded"); + } + else if (document.Uri != null) + { + WriteOutputLine($"'{document.Name}': '{document.Uri}'"); + } + else + { + unresolvedCount++; + } + } + + if (unresolvedCount > 0) + { + ReportError($"Unable to resolve URL for {unresolvedCount} document(s):"); + } + + foreach (var document in resolvedDocuments) + { + if (!document.IsEmbedded && document.Uri == null) + { + WriteOutputLine(document.Name); + } + } + + return _errorReported ? 1 : 0; + } + + private bool ReadPdbMetadata(string path, Action reader) + { + var filePath = path; + + try + { + if (string.Equals(Path.GetExtension(path), ".pdb", StringComparison.OrdinalIgnoreCase)) + { + using var provider = MetadataReaderProvider.FromPortablePdbStream(File.OpenRead(path)); + reader(filePath, provider.GetMetadataReader()); + return true; + } + + using var peReader = new PEReader(File.OpenRead(path)); + if (peReader.TryOpenAssociatedPortablePdb(path, pdbFileStreamProvider: File.OpenRead, out var pdbReaderProvider, out filePath)) + { + using (pdbReaderProvider) + { + reader(filePath ?? path, pdbReaderProvider!.GetMetadataReader()); + } + + return true; + } + + return false; + } + catch (Exception e) + { + ReportError($"Error reading '{filePath}': {e.Message}"); + return false; + } + } + + private void ReadAndResolveDocuments(string path, List resolvedDocuments) + { + if (!ReadPdbMetadata(path, (filePath, metadataReader) => + { + var documents = new List<(string name, ImmutableArray hash, Guid hashAlgorithm, bool isEmbedded)>(); + bool hasUnembeddedDocument = false; + + foreach (var documentHandle in metadataReader.Documents) + { + var document = metadataReader.GetDocument(documentHandle); + var name = metadataReader.GetString(document.Name); + var isEmbedded = HasCustomDebugInformation(metadataReader, documentHandle, s_embeddedSourceCustomDebugInformationId); + var hash = metadataReader.GetBlobContent(document.Hash); + var hashAlgorithm = metadataReader.GetGuid(document.HashAlgorithm); + + documents.Add((name, hash, hashAlgorithm, isEmbedded)); + + if (!isEmbedded) + { + hasUnembeddedDocument = true; + } + } + + SourceLinkMap sourceLinkMap = default; + if (hasUnembeddedDocument) + { + var sourceLink = ReadSourceLink(metadataReader); + if (sourceLink == null) + { + ReportError($"Source Link record not found."); + return; + } + + try + { + sourceLinkMap = SourceLinkMap.Parse(sourceLink); + } + catch (Exception e) + { + ReportError($"Error reading SourceLink: {e.Message}"); + return; + } + } + + foreach (var (name, hash, hashAlgorithm, isEmbedded) in documents) + { + string? uri = isEmbedded ? null : sourceLinkMap.TryGetUri(name, out var mappedUri) ? mappedUri : null; + resolvedDocuments.Add(new DocumentInfo(filePath, name, uri, isEmbedded, hash, hashAlgorithm)); + } + })) + { + ReportError($"Symbol information not found for '{path}'."); + }; + } + + private async Task ReadAndResolveDocumentsFromPackageAsync(string packagePath, List documents, CancellationToken cancellationToken) + { + using var packageReader = new PackageArchiveReader(File.OpenRead(packagePath)); + + var files = await packageReader.GetFilesAsync(cancellationToken).ConfigureAwait(false); + + foreach (var file in files) + { + var extension = Path.GetExtension(file); + if (string.Equals(extension, ".dll", StringComparison.OrdinalIgnoreCase) && !file.EndsWith(".resources.dll", StringComparison.OrdinalIgnoreCase) || + string.Equals(extension, ".exe", StringComparison.OrdinalIgnoreCase) || + string.Equals(extension, ".pdb", StringComparison.OrdinalIgnoreCase)) + { + // TODO: + ReadAndResolveDocuments(file, documents); + } + + cancellationToken.ThrowIfCancellationRequested(); + } + } + + private static bool HasCustomDebugInformation(MetadataReader metadataReader, EntityHandle handle, Guid kind) + { + foreach (var cdiHandle in metadataReader.GetCustomDebugInformation(handle)) + { + var cdi = metadataReader.GetCustomDebugInformation(cdiHandle); + if (metadataReader.GetGuid(cdi.Kind) == kind) + { + return true; + } + } + + return false; + } + + private static BlobReader GetCustomDebugInformationReader(MetadataReader metadataReader, EntityHandle handle, Guid kind) + { + foreach (var cdiHandle in metadataReader.GetCustomDebugInformation(handle)) + { + var cdi = metadataReader.GetCustomDebugInformation(cdiHandle); + if (metadataReader.GetGuid(cdi.Kind) == kind) + { + return metadataReader.GetBlobReader(cdi.Value); + } + } + + return default; + } + + private static string? ReadSourceLink(MetadataReader metadataReader) + { + var blobReader = GetCustomDebugInformationReader(metadataReader, EntityHandle.ModuleDefinition, s_sourceLinkCustomDebugInformationId); + return blobReader.Length > 0 ? blobReader.ReadUTF8(blobReader.Length) : null; + } + + private static AuthenticationHeaderValue GetAuthenticationHeader(string method, Encoding encoding, string username, string password) + { + return (method.ToLowerInvariant()) switch + { + AuthenticationMethod.Basic => new AuthenticationHeaderValue( + scheme: AuthenticationMethod.Basic, + parameter: Convert.ToBase64String(encoding.GetBytes($"{username}:{password}"))), + + _ => throw new InvalidOperationException(), + }; + } + } +} \ No newline at end of file diff --git a/src/dotnet-sourcelink/dotnet-sourcelink.csproj b/src/dotnet-sourcelink/dotnet-sourcelink.csproj new file mode 100644 index 0000000000..22fa035249 --- /dev/null +++ b/src/dotnet-sourcelink/dotnet-sourcelink.csproj @@ -0,0 +1,26 @@ + + + Exe + net5.0 + + + sourcelink + True + Command line tool for SourceLink testing. + true + + + win-x64;win-x86;osx-x64 + + + + + + + + + + \ No newline at end of file diff --git a/src/dotnet-sourcelink/runtimeconfig.template.json b/src/dotnet-sourcelink/runtimeconfig.template.json new file mode 100644 index 0000000000..2c73f39890 --- /dev/null +++ b/src/dotnet-sourcelink/runtimeconfig.template.json @@ -0,0 +1,3 @@ +{ + "rollForwardOnNoCandidateFx": 2 +} \ No newline at end of file From 3a300d854018fa7b2f1cc872391c6e57933266ff Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 18 Jan 2021 19:28:10 -0800 Subject: [PATCH 3/5] Fix hash calculation --- src/dotnet-sourcelink/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotnet-sourcelink/Program.cs b/src/dotnet-sourcelink/Program.cs index 42cc697897..102edab4a0 100644 --- a/src/dotnet-sourcelink/Program.cs +++ b/src/dotnet-sourcelink/Program.cs @@ -295,14 +295,14 @@ private static async Task DownloadAndValidateDocumentAsync(HttpClient clie int index = 0; while (true) { - int lf = Array.IndexOf(content, (byte)'\r', index); + int lf = Array.IndexOf(content, (byte)'\n', index); if (lf < 0) { incrementalHash.AppendData(content, index, content.Length - index); return incrementalHash.GetHashAndReset(); } - if (index + 1 < content.Length && content[index + 1] == (byte)'\n') + if (index - 1 >= 0 && content[index - 1] == (byte)'\r') { // The file either has CRLF line endings or mixed line endings. // In either case there is no need to substitute LF to CRLF. From e5b00e383145c8c8ded2d581a584334e591d3c3a Mon Sep 17 00:00:00 2001 From: tmat Date: Fri, 14 Oct 2022 13:43:50 -0700 Subject: [PATCH 4/5] Pack --- src/dotnet-sourcelink/dotnet-sourcelink.csproj | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/dotnet-sourcelink/dotnet-sourcelink.csproj b/src/dotnet-sourcelink/dotnet-sourcelink.csproj index 22fa035249..f864fadd5b 100644 --- a/src/dotnet-sourcelink/dotnet-sourcelink.csproj +++ b/src/dotnet-sourcelink/dotnet-sourcelink.csproj @@ -1,18 +1,14 @@  Exe - net5.0 + net7.0 - sourcelink + true True + sourcelink Command line tool for SourceLink testing. true - - win-x64;win-x86;osx-x64 From d74d9dd3aad29f6788de768bb1861b77b5123299 Mon Sep 17 00:00:00 2001 From: tmat Date: Thu, 27 Oct 2022 14:08:17 -0700 Subject: [PATCH 5/5] Remove nuget dependency --- src/dotnet-sourcelink/Program.cs | 31 +------------------ .../dotnet-sourcelink.csproj | 1 - 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/src/dotnet-sourcelink/Program.cs b/src/dotnet-sourcelink/Program.cs index 102edab4a0..4a130f62d1 100644 --- a/src/dotnet-sourcelink/Program.cs +++ b/src/dotnet-sourcelink/Program.cs @@ -18,7 +18,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using NuGet.Packaging; namespace Microsoft.SourceLink.Tools { @@ -197,14 +196,7 @@ private static async Task TestAsync( private async Task TestAsync(string path, AuthenticationHeaderValue? authenticationHeader, CancellationToken cancellationToken) { var documents = new List(); - if (string.Equals(Path.GetExtension(path), ".nupkg", StringComparison.OrdinalIgnoreCase)) - { - await ReadAndResolveDocumentsFromPackageAsync(path, documents, cancellationToken).ConfigureAwait(false); - } - else - { - ReadAndResolveDocuments(path, documents); - } + ReadAndResolveDocuments(path, documents); if (documents.Count == 0) { @@ -492,27 +484,6 @@ private void ReadAndResolveDocuments(string path, List resolvedDoc }; } - private async Task ReadAndResolveDocumentsFromPackageAsync(string packagePath, List documents, CancellationToken cancellationToken) - { - using var packageReader = new PackageArchiveReader(File.OpenRead(packagePath)); - - var files = await packageReader.GetFilesAsync(cancellationToken).ConfigureAwait(false); - - foreach (var file in files) - { - var extension = Path.GetExtension(file); - if (string.Equals(extension, ".dll", StringComparison.OrdinalIgnoreCase) && !file.EndsWith(".resources.dll", StringComparison.OrdinalIgnoreCase) || - string.Equals(extension, ".exe", StringComparison.OrdinalIgnoreCase) || - string.Equals(extension, ".pdb", StringComparison.OrdinalIgnoreCase)) - { - // TODO: - ReadAndResolveDocuments(file, documents); - } - - cancellationToken.ThrowIfCancellationRequested(); - } - } - private static bool HasCustomDebugInformation(MetadataReader metadataReader, EntityHandle handle, Guid kind) { foreach (var cdiHandle in metadataReader.GetCustomDebugInformation(handle)) diff --git a/src/dotnet-sourcelink/dotnet-sourcelink.csproj b/src/dotnet-sourcelink/dotnet-sourcelink.csproj index f864fadd5b..9a604a7098 100644 --- a/src/dotnet-sourcelink/dotnet-sourcelink.csproj +++ b/src/dotnet-sourcelink/dotnet-sourcelink.csproj @@ -14,7 +14,6 @@ -