Create MS.SqlTools.Credentials project (#249)

* Initial credential service files

* Clean-up hostloader

* Update build scripts to build credentials archive

* Move hosting files to new assembly

* Add credentials files to MS.SqlTools.Credentials

* Remove duplicate files

* Update namespace in program.cs

* Fix test build breaks

* Update extensions visibility.

* Remove unused resource strings

* Add xproj files to SLN for appveyor builds

* Fix appveyor build break in test project

* Fix extensibility tests

* Fix various typos in latest iteration

* Add settings for Integration build

* Fix codecoverage.bat to use full pdb for new projects

* Fix bug when packing in folder with native images

* Fix typos in xproj

* Reset XLF to fix build.cmd
This commit is contained in:
Karl Burtram
2017-02-23 16:09:58 -08:00
committed by GitHub
parent e79a37bdfe
commit 0af7bef66d
112 changed files with 4887 additions and 1870 deletions

View File

@@ -1,124 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Contracts
{
/// <summary>
/// A Credential containing information needed to log into a resource. This is primarily
/// defined as a unique <see cref="CredentialId"/> with an associated <see cref="Password"/>
/// that's linked to it.
/// </summary>
public class Credential
{
/// <summary>
/// A unique ID to identify the credential being saved.
/// </summary>
public string CredentialId { get; set; }
/// <summary>
/// The Password stored for this credential.
/// </summary>
public string Password { get; set; }
/// <summary>
/// Default Constructor
/// </summary>
public Credential()
{
}
/// <summary>
/// Constructor used when only <paramref name="credentialId"/> is known
/// </summary>
/// <param name="credentialId"><see cref="CredentialId"/></param>
public Credential(string credentialId)
: this(credentialId, null)
{
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="credentialId"><see cref="CredentialId"/></param>
/// <param name="password"><see cref="Password"/></param>
public Credential(string credentialId, string password)
{
CredentialId = credentialId;
Password = password;
}
internal static Credential Copy(Credential credential)
{
return new Credential
{
CredentialId = credential.CredentialId,
Password = credential.Password
};
}
/// <summary>
/// Validates the credential has all the properties needed to look up the password
/// </summary>
public static void ValidateForLookup(Credential credential)
{
Validate.IsNotNull("credential", credential);
Validate.IsNotNullOrEmptyString("credential.CredentialId", credential.CredentialId);
}
/// <summary>
/// Validates the credential has all the properties needed to save a password
/// </summary>
public static void ValidateForSave(Credential credential)
{
ValidateForLookup(credential);
Validate.IsNotNullOrEmptyString("credential.Password", credential.Password);
}
}
/// <summary>
/// Read Credential request mapping entry. Expects a Credential with CredentialId,
/// and responds with the <see cref="Credential.Password"/> filled in if found
/// </summary>
public class ReadCredentialRequest
{
/// <summary>
/// Request definition
/// </summary>
public static readonly
RequestType<Credential, Credential> Type =
RequestType<Credential, Credential>.Create("credential/read");
}
/// <summary>
/// Save Credential request mapping entry
/// </summary>
public class SaveCredentialRequest
{
/// <summary>
/// Request definition
/// </summary>
public static readonly
RequestType<Credential, bool> Type =
RequestType<Credential, bool>.Create("credential/save");
}
/// <summary>
/// Delete Credential request mapping entry
/// </summary>
public class DeleteCredentialRequest
{
/// <summary>
/// Request definition
/// </summary>
public static readonly
RequestType<Credential, bool> Type =
RequestType<Credential, bool>.Create("credential/delete");
}
}

View File

@@ -1,182 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
using Microsoft.SqlTools.ServiceLayer.Credentials.Linux;
using Microsoft.SqlTools.ServiceLayer.Credentials.OSX;
using Microsoft.SqlTools.ServiceLayer.Credentials.Win32;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Credentials
{
/// <summary>
/// Service responsible for securing credentials in a platform-neutral manner. This provides
/// a generic API for read, save and delete credentials
/// </summary>
public class CredentialService
{
internal static string DefaultSecretsFolder = ".sqlsecrets";
internal const string DefaultSecretsFile = "sqlsecrets.json";
/// <summary>
/// Singleton service instance
/// </summary>
private static Lazy<CredentialService> instance
= new Lazy<CredentialService>(() => new CredentialService());
/// <summary>
/// Gets the singleton service instance
/// </summary>
public static CredentialService Instance
{
get
{
return instance.Value;
}
}
private ICredentialStore credStore;
/// <summary>
/// Default constructor is private since it's a singleton class
/// </summary>
private CredentialService()
: this(null, new StoreConfig()
{ CredentialFolder = DefaultSecretsFolder, CredentialFile = DefaultSecretsFile, IsRelativeToUserHomeDir = true})
{
}
/// <summary>
/// Internal for testing purposes only
/// </summary>
internal CredentialService(ICredentialStore store, StoreConfig config)
{
credStore = store != null ? store : GetStoreForOS(config);
}
/// <summary>
/// Internal for testing purposes only
/// </summary>
internal static ICredentialStore GetStoreForOS(StoreConfig config)
{
if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return new Win32CredentialStore();
}
#if !WINDOWS_ONLY_BUILD
else if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return new OSXCredentialStore();
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return new LinuxCredentialStore(config);
}
#endif
throw new InvalidOperationException("Platform not currently supported");
}
public void InitializeService(IProtocolEndpoint serviceHost)
{
// Register request and event handlers with the Service Host
serviceHost.SetRequestHandler(ReadCredentialRequest.Type, HandleReadCredentialRequest);
serviceHost.SetRequestHandler(SaveCredentialRequest.Type, HandleSaveCredentialRequest);
serviceHost.SetRequestHandler(DeleteCredentialRequest.Type, HandleDeleteCredentialRequest);
}
public async Task HandleReadCredentialRequest(Credential credential, RequestContext<Credential> requestContext)
{
Func<Task<Credential>> doRead = () =>
{
return ReadCredentialAsync(credential);
};
await HandleRequest(doRead, requestContext, "HandleReadCredentialRequest");
}
public async Task<Credential> ReadCredentialAsync(Credential credential)
{
return await Task.Factory.StartNew(() =>
{
return ReadCredential(credential);
});
}
public Credential ReadCredential(Credential credential)
{
Credential.ValidateForLookup(credential);
Credential result = Credential.Copy(credential);
string password;
if (credStore.TryGetPassword(credential.CredentialId, out password))
{
result.Password = password;
}
return result;
}
public async Task HandleSaveCredentialRequest(Credential credential, RequestContext<bool> requestContext)
{
Func<Task<bool>> doSave = () =>
{
return SaveCredentialAsync(credential);
};
await HandleRequest(doSave, requestContext, "HandleSaveCredentialRequest");
}
public async Task<bool> SaveCredentialAsync(Credential credential)
{
return await Task.Factory.StartNew(() =>
{
return SaveCredential(credential);
});
}
public bool SaveCredential(Credential credential)
{
Credential.ValidateForSave(credential);
return credStore.Save(credential);
}
public async Task HandleDeleteCredentialRequest(Credential credential, RequestContext<bool> requestContext)
{
Func<Task<bool>> doDelete = () =>
{
return DeletePasswordAsync(credential);
};
await HandleRequest(doDelete, requestContext, "HandleDeleteCredentialRequest");
}
private async Task<bool> DeletePasswordAsync(Credential credential)
{
return await Task.Factory.StartNew(() =>
{
Credential.ValidateForLookup(credential);
return credStore.DeletePassword(credential.CredentialId);
});
}
private async Task HandleRequest<T>(Func<Task<T>> handler, RequestContext<T> requestContext, string requestType)
{
Logger.Write(LogLevel.Verbose, requestType);
try
{
T result = await handler();
await requestContext.SendResult(result);
}
catch (Exception ex)
{
await requestContext.SendError(ex.ToString());
}
}
}
}

View File

@@ -1,41 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Credentials
{
/// <summary>
/// An <see cref="ICredentialStore"/> support securely saving and retrieving passwords
/// </summary>
public interface ICredentialStore
{
/// <summary>
/// Saves a Password linked to a given Credential
/// </summary>
/// <param name="credential">
/// A <see cref="Credential"/> to be saved.
/// <see cref="Credential.CredentialId"/> and <see cref="Credential.Password"/> are required
/// </param>
/// <returns>True if successful, false otherwise</returns>
bool Save(Credential credential);
/// <summary>
/// Gets a Password and sets it into a <see cref="Credential"/> object
/// </summary>
/// <param name="credentialId">The name of the credential to find the password for. This is required</param>
/// <param name="password">Out value</param>
/// <returns>true if password was found, false otherwise</returns>
bool TryGetPassword(string credentialId, out string password);
/// <summary>
/// Deletes a password linked to a given credential
/// </summary>
/// <param name="credentialId">The name of the credential to find the password for. This is required</param>
/// <returns>True if password existed and was deleted, false otherwise</returns>
bool DeletePassword(string credentialId);
}
}

View File

@@ -1,36 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Microsoft.SqlTools.ServiceLayer.Credentials
{
internal static class InteropUtils
{
/// <summary>
/// Gets the length in bytes for a Unicode string, for use in interop where length must be defined
/// </summary>
public static UInt32 GetLengthInBytes(string value)
{
return Convert.ToUInt32( (value != null ? Encoding.Unicode.GetByteCount(value) : 0) );
}
public static string CopyToString(IntPtr ptr, int length)
{
if (ptr == IntPtr.Zero || length == 0)
{
return null;
}
byte[] pwdBytes = new byte[length];
Marshal.Copy(ptr, pwdBytes, 0, (int)length);
return Encoding.Unicode.GetString(pwdBytes, 0, (int)length);
}
}
}

View File

@@ -1,18 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
{
/// <summary>
/// Simplified class to enable writing a set of credentials to/from disk
/// </summary>
public class CredentialsWrapper
{
public List<Credential> Credentials { get; set; }
}
}

View File

@@ -1,93 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Utility;
using Newtonsoft.Json;
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
{
#if !WINDOWS_ONLY_BUILD
public class FileTokenStorage
{
private const int OwnerAccessMode = 384; // Permission 0600 - owner read/write, nobody else has access
private object lockObject = new object();
private string fileName;
public FileTokenStorage(string fileName)
{
Validate.IsNotNullOrEmptyString("fileName", fileName);
this.fileName = fileName;
}
public void AddEntries(IEnumerable<Credential> newEntries, IEnumerable<Credential> existingEntries)
{
var allEntries = existingEntries.Concat(newEntries);
this.SaveEntries(allEntries);
}
public void Clear()
{
this.SaveEntries(new List<Credential>());
}
public IEnumerable<Credential> LoadEntries()
{
if(!File.Exists(this.fileName))
{
return Enumerable.Empty<Credential>();
}
string serializedCreds;
lock (lockObject)
{
serializedCreds = File.ReadAllText(this.fileName);
}
CredentialsWrapper creds = JsonConvert.DeserializeObject<CredentialsWrapper>(serializedCreds, Constants.JsonSerializerSettings);
if(creds != null)
{
return creds.Credentials;
}
return Enumerable.Empty<Credential>();
}
public void SaveEntries(IEnumerable<Credential> entries)
{
CredentialsWrapper credentials = new CredentialsWrapper() { Credentials = entries.ToList() };
string serializedCreds = JsonConvert.SerializeObject(credentials, Constants.JsonSerializerSettings);
lock(lockObject)
{
WriteToFile(this.fileName, serializedCreds);
}
}
private static void WriteToFile(string filePath, string fileContents)
{
string dir = Path.GetDirectoryName(filePath);
if(!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
// Overwrite file, then use ChMod to ensure we have
File.WriteAllText(filePath, fileContents);
// set appropriate permissions so only current user can read/write
Interop.Sys.ChMod(filePath, OwnerAccessMode);
}
}
#endif
}

View File

@@ -1,226 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Runtime.InteropServices;
namespace Microsoft.SqlTools.ServiceLayer.Credentials
{
#if !WINDOWS_ONLY_BUILD
internal static partial class Interop
{
/// <summary>Common Unix errno error codes.</summary>
internal enum Error
{
// These values were defined in src/Native/System.Native/fxerrno.h
//
// They compare against values obtained via Interop.Sys.GetLastError() not Marshal.GetLastWin32Error()
// which obtains the raw errno that varies between unixes. The strong typing as an enum is meant to
// prevent confusing the two. Casting to or from int is suspect. Use GetLastErrorInfo() if you need to
// correlate these to the underlying platform values or obtain the corresponding error message.
//
SUCCESS = 0,
E2BIG = 0x10001, // Argument list too long.
EACCES = 0x10002, // Permission denied.
EADDRINUSE = 0x10003, // Address in use.
EADDRNOTAVAIL = 0x10004, // Address not available.
EAFNOSUPPORT = 0x10005, // Address family not supported.
EAGAIN = 0x10006, // Resource unavailable, try again (same value as EWOULDBLOCK),
EALREADY = 0x10007, // Connection already in progress.
EBADF = 0x10008, // Bad file descriptor.
EBADMSG = 0x10009, // Bad message.
EBUSY = 0x1000A, // Device or resource busy.
ECANCELED = 0x1000B, // Operation canceled.
ECHILD = 0x1000C, // No child processes.
ECONNABORTED = 0x1000D, // Connection aborted.
ECONNREFUSED = 0x1000E, // Connection refused.
ECONNRESET = 0x1000F, // Connection reset.
EDEADLK = 0x10010, // Resource deadlock would occur.
EDESTADDRREQ = 0x10011, // Destination address required.
EDOM = 0x10012, // Mathematics argument out of domain of function.
EDQUOT = 0x10013, // Reserved.
EEXIST = 0x10014, // File exists.
EFAULT = 0x10015, // Bad address.
EFBIG = 0x10016, // File too large.
EHOSTUNREACH = 0x10017, // Host is unreachable.
EIDRM = 0x10018, // Identifier removed.
EILSEQ = 0x10019, // Illegal byte sequence.
EINPROGRESS = 0x1001A, // Operation in progress.
EINTR = 0x1001B, // Interrupted function.
EINVAL = 0x1001C, // Invalid argument.
EIO = 0x1001D, // I/O error.
EISCONN = 0x1001E, // Socket is connected.
EISDIR = 0x1001F, // Is a directory.
ELOOP = 0x10020, // Too many levels of symbolic links.
EMFILE = 0x10021, // File descriptor value too large.
EMLINK = 0x10022, // Too many links.
EMSGSIZE = 0x10023, // Message too large.
EMULTIHOP = 0x10024, // Reserved.
ENAMETOOLONG = 0x10025, // Filename too long.
ENETDOWN = 0x10026, // Network is down.
ENETRESET = 0x10027, // Connection aborted by network.
ENETUNREACH = 0x10028, // Network unreachable.
ENFILE = 0x10029, // Too many files open in system.
ENOBUFS = 0x1002A, // No buffer space available.
ENODEV = 0x1002C, // No such device.
ENOENT = 0x1002D, // No such file or directory.
ENOEXEC = 0x1002E, // Executable file format error.
ENOLCK = 0x1002F, // No locks available.
ENOLINK = 0x10030, // Reserved.
ENOMEM = 0x10031, // Not enough space.
ENOMSG = 0x10032, // No message of the desired type.
ENOPROTOOPT = 0x10033, // Protocol not available.
ENOSPC = 0x10034, // No space left on device.
ENOSYS = 0x10037, // Function not supported.
ENOTCONN = 0x10038, // The socket is not connected.
ENOTDIR = 0x10039, // Not a directory or a symbolic link to a directory.
ENOTEMPTY = 0x1003A, // Directory not empty.
ENOTSOCK = 0x1003C, // Not a socket.
ENOTSUP = 0x1003D, // Not supported (same value as EOPNOTSUP).
ENOTTY = 0x1003E, // Inappropriate I/O control operation.
ENXIO = 0x1003F, // No such device or address.
EOVERFLOW = 0x10040, // Value too large to be stored in data type.
EPERM = 0x10042, // Operation not permitted.
EPIPE = 0x10043, // Broken pipe.
EPROTO = 0x10044, // Protocol error.
EPROTONOSUPPORT = 0x10045, // Protocol not supported.
EPROTOTYPE = 0x10046, // Protocol wrong type for socket.
ERANGE = 0x10047, // Result too large.
EROFS = 0x10048, // Read-only file system.
ESPIPE = 0x10049, // Invalid seek.
ESRCH = 0x1004A, // No such process.
ESTALE = 0x1004B, // Reserved.
ETIMEDOUT = 0x1004D, // Connection timed out.
ETXTBSY = 0x1004E, // Text file busy.
EXDEV = 0x1004F, // Cross-device link.
ESOCKTNOSUPPORT = 0x1005E, // Socket type not supported.
EPFNOSUPPORT = 0x10060, // Protocol family not supported.
ESHUTDOWN = 0x1006C, // Socket shutdown.
EHOSTDOWN = 0x10070, // Host is down.
ENODATA = 0x10071, // No data available.
// POSIX permits these to have the same value and we make them always equal so
// that CoreFX cannot introduce a dependency on distinguishing between them that
// would not work on all platforms.
EOPNOTSUPP = ENOTSUP, // Operation not supported on socket.
EWOULDBLOCK = EAGAIN, // Operation would block.
}
// Represents a platform-agnostic Error and underlying platform-specific errno
internal struct ErrorInfo
{
private Error _error;
private int _rawErrno;
internal ErrorInfo(int errno)
{
_error = Interop.Sys.ConvertErrorPlatformToPal(errno);
_rawErrno = errno;
}
internal ErrorInfo(Error error)
{
_error = error;
_rawErrno = -1;
}
internal Error Error
{
get { return _error; }
}
internal int RawErrno
{
get { return _rawErrno == -1 ? (_rawErrno = Interop.Sys.ConvertErrorPalToPlatform(_error)) : _rawErrno; }
}
internal string GetErrorMessage()
{
return Interop.Sys.StrError(RawErrno);
}
public override string ToString()
{
return string.Format(
"RawErrno: {0} Error: {1} GetErrorMessage: {2}", // No localization required; text is member names used for debugging purposes
RawErrno, Error, GetErrorMessage());
}
}
internal partial class Sys
{
internal static Error GetLastError()
{
return ConvertErrorPlatformToPal(Marshal.GetLastWin32Error());
}
internal static ErrorInfo GetLastErrorInfo()
{
return new ErrorInfo(Marshal.GetLastWin32Error());
}
internal static string StrError(int platformErrno)
{
int maxBufferLength = 1024; // should be long enough for most any UNIX error
IntPtr buffer = Marshal.AllocHGlobal(maxBufferLength);
try
{
IntPtr message = StrErrorR(platformErrno, buffer, maxBufferLength);
if (message == IntPtr.Zero)
{
// This means the buffer was not large enough, but still contains
// as much of the error message as possible and is guaranteed to
// be null-terminated. We're not currently resizing/retrying because
// maxBufferLength is large enough in practice, but we could do
// so here in the future if necessary.
message = buffer;
}
string returnMsg = Marshal.PtrToStringAnsi(message);
return returnMsg;
}
finally
{
// Deallocate the buffer we created
Marshal.FreeHGlobal(buffer);
}
}
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ConvertErrorPlatformToPal")]
internal static extern Error ConvertErrorPlatformToPal(int platformErrno);
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ConvertErrorPalToPlatform")]
internal static extern int ConvertErrorPalToPlatform(Error error);
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_StrErrorR")]
private static extern IntPtr StrErrorR(int platformErrno, IntPtr buffer, int bufferSize);
}
}
// NOTE: extension method can't be nested inside Interop class.
internal static class InteropErrorExtensions
{
// Intended usage is e.g. Interop.Error.EFAIL.Info() for brevity
// vs. new Interop.ErrorInfo(Interop.Error.EFAIL) for synthesizing
// errors. Errors originated from the system should be obtained
// via GetLastErrorInfo(), not GetLastError().Info() as that will
// convert twice, which is not only inefficient but also lossy if
// we ever encounter a raw errno that no equivalent in the Error
// enum.
public static Interop.ErrorInfo Info(this Interop.Error error)
{
return new Interop.ErrorInfo(error);
}
}
#endif
}

View File

@@ -1,47 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Runtime.InteropServices;
namespace Microsoft.SqlTools.ServiceLayer.Credentials
{
#if !WINDOWS_ONLY_BUILD
internal static partial class Interop
{
internal static partial class Sys
{
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ChMod", SetLastError = true)]
internal static extern int ChMod(string path, int mode);
internal struct Passwd
{
internal IntPtr Name; // char*
internal IntPtr Password; // char*
internal uint UserId;
internal uint GroupId;
internal IntPtr UserInfo; // char*
internal IntPtr HomeDirectory; // char*
internal IntPtr Shell; // char*
};
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetPwUidR", SetLastError = false)]
internal static extern int GetPwUidR(uint uid, out Passwd pwd, IntPtr buf, int bufLen);
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetEUid")]
internal static extern uint GetEUid();
private static partial class Libraries
{
internal const string SystemNative = "System.Native";
}
}
}
#endif
}

View File

@@ -1,237 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Linux
{
/// <summary>
/// Store configuration struct
/// </summary>
internal struct StoreConfig
{
public string CredentialFolder { get; set; }
public string CredentialFile { get; set; }
public bool IsRelativeToUserHomeDir { get; set; }
}
#if !WINDOWS_ONLY_BUILD
/// <summary>
/// Linux implementation of the credential store.
///
/// <remarks>
/// This entire implementation may need to be revised to support encryption of
/// passwords and protection of them when loaded into memory.
/// </remarks>
/// </summary>
internal class LinuxCredentialStore : ICredentialStore
{
private string credentialFolderPath;
private string credentialFileName;
private FileTokenStorage storage;
public LinuxCredentialStore(StoreConfig config)
{
Validate.IsNotNull("config", config);
Validate.IsNotNullOrEmptyString("credentialFolder", config.CredentialFolder);
Validate.IsNotNullOrEmptyString("credentialFileName", config.CredentialFile);
this.credentialFolderPath = config.IsRelativeToUserHomeDir ? GetUserScopedDirectory(config.CredentialFolder) : config.CredentialFolder;
this.credentialFileName = config.CredentialFile;
string combinedPath = Path.Combine(this.credentialFolderPath, this.credentialFileName);
storage = new FileTokenStorage(combinedPath);
}
public bool DeletePassword(string credentialId)
{
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
IEnumerable<Credential> creds;
if (LoadCredentialsAndFilterById(credentialId, out creds))
{
storage.SaveEntries(creds);
return true;
}
return false;
}
/// <summary>
/// Gets filtered credentials with a specific ID filtered out
/// </summary>
/// <returns>True if the credential to filter was removed, false if it was not found</returns>
private bool LoadCredentialsAndFilterById(string idToFilter, out IEnumerable<Credential> creds)
{
bool didRemove = false;
creds = storage.LoadEntries().Where(cred =>
{
if (IsCredentialMatch(idToFilter, cred))
{
didRemove = true;
return false; // filter this out
}
return true;
}).ToList(); // Call ToList ensures Where clause is executed so didRemove can be evaluated
return didRemove;
}
private static bool IsCredentialMatch(string credentialId, Credential cred)
{
return string.Equals(credentialId, cred.CredentialId, StringComparison.Ordinal);
}
public bool TryGetPassword(string credentialId, out string password)
{
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
Credential cred = storage.LoadEntries().FirstOrDefault(c => IsCredentialMatch(credentialId, c));
if (cred != null)
{
password = cred.Password;
return true;
}
// Else this was not found in the list
password = null;
return false;
}
public bool Save(Credential credential)
{
Credential.ValidateForSave(credential);
// Load the credentials, removing the existing Cred for this
IEnumerable<Credential> creds;
LoadCredentialsAndFilterById(credential.CredentialId, out creds);
storage.SaveEntries(creds.Append(credential));
return true;
}
/// <summary>
/// Internal for testing purposes only
/// </summary>
internal string CredentialFolderPath
{
get { return this.credentialFolderPath; }
}
/// <summary>
/// Concatenates a directory to the user home directory's path
/// </summary>
internal static string GetUserScopedDirectory(string userPath)
{
string homeDir = GetHomeDirectory() ?? string.Empty;
return Path.Combine(homeDir, userPath);
}
/// <summary>Gets the current user's home directory.</summary>
/// <returns>The path to the home directory, or null if it could not be determined.</returns>
internal static string GetHomeDirectory()
{
// First try to get the user's home directory from the HOME environment variable.
// This should work in most cases.
string userHomeDirectory = Environment.GetEnvironmentVariable("HOME");
if (!string.IsNullOrEmpty(userHomeDirectory))
{
return userHomeDirectory;
}
// In initialization conditions, however, the "HOME" environment variable may
// not yet be set. For such cases, consult with the password entry.
// First try with a buffer that should suffice for 99% of cases.
// Note that, theoretically, userHomeDirectory may be null in the success case
// if we simply couldn't find a home directory for the current user.
// In that case, we pass back the null value and let the caller decide
// what to do.
return GetHomeDirectoryFromPw();
}
internal static string GetHomeDirectoryFromPw()
{
string userHomeDirectory = null;
const int BufLen = 1024;
if (TryGetHomeDirectoryFromPasswd(BufLen, out userHomeDirectory))
{
return userHomeDirectory;
}
// Fallback to heap allocations if necessary, growing the buffer until
// we succeed. TryGetHomeDirectory will throw if there's an unexpected error.
int lastBufLen = BufLen;
while (true)
{
lastBufLen *= 2;
if (TryGetHomeDirectoryFromPasswd(lastBufLen, out userHomeDirectory))
{
return userHomeDirectory;
}
}
}
/// <summary>Wrapper for getpwuid_r.</summary>
/// <param name="bufLen">The length of the buffer to use when storing the password result.</param>
/// <param name="path">The resulting path; null if the user didn't have an entry.</param>
/// <returns>true if the call was successful (path may still be null); false is a larger buffer is needed.</returns>
private static bool TryGetHomeDirectoryFromPasswd(int bufLen, out string path)
{
// Call getpwuid_r to get the passwd struct
Interop.Sys.Passwd passwd;
IntPtr buffer = Marshal.AllocHGlobal(bufLen);
try
{
int error = Interop.Sys.GetPwUidR(Interop.Sys.GetEUid(), out passwd, buffer, bufLen);
// If the call succeeds, give back the home directory path retrieved
if (error == 0)
{
Debug.Assert(passwd.HomeDirectory != IntPtr.Zero);
path = Marshal.PtrToStringAnsi(passwd.HomeDirectory);
return true;
}
// If the current user's entry could not be found, give back null
// path, but still return true as false indicates the buffer was
// too small.
if (error == -1)
{
path = null;
return true;
}
var errorInfo = new Interop.ErrorInfo(error);
// If the call failed because the buffer was too small, return false to
// indicate the caller should try again with a larger buffer.
if (errorInfo.Error == Interop.Error.ERANGE)
{
path = null;
return false;
}
// Otherwise, fail.
throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno);
}
finally
{
// Deallocate the buffer we created
Marshal.FreeHGlobal(buffer);
}
}
}
#endif
}

View File

@@ -1,105 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
namespace Microsoft.SqlTools.ServiceLayer.Credentials
{
internal static partial class Interop
{
internal static partial class CoreFoundation
{
/// <summary>
/// Tells the OS what encoding the passed in String is in. These come from the CFString.h header file in the CoreFoundation framework.
/// </summary>
private enum CFStringBuiltInEncodings : uint
{
kCFStringEncodingMacRoman = 0,
kCFStringEncodingWindowsLatin1 = 0x0500,
kCFStringEncodingISOLatin1 = 0x0201,
kCFStringEncodingNextStepLatin = 0x0B01,
kCFStringEncodingASCII = 0x0600,
kCFStringEncodingUnicode = 0x0100,
kCFStringEncodingUTF8 = 0x08000100,
kCFStringEncodingNonLossyASCII = 0x0BFF,
kCFStringEncodingUTF16 = 0x0100,
kCFStringEncodingUTF16BE = 0x10000100,
kCFStringEncodingUTF16LE = 0x14000100,
kCFStringEncodingUTF32 = 0x0c000100,
kCFStringEncodingUTF32BE = 0x18000100,
kCFStringEncodingUTF32LE = 0x1c000100
}
/// <summary>
/// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it.
/// </summary>
/// <param name="allocator">Should be IntPtr.Zero</param>
/// <param name="str">The string to get a CFStringRef for</param>
/// <param name="encoding">The encoding of the str variable. This should be UTF 8 for OS X</param>
/// <returns>Returns a pointer to a CFString on success; otherwise, returns IntPtr.Zero</returns>
/// <remarks>For *nix systems, the CLR maps ANSI to UTF-8, so be explicit about that</remarks>
[DllImport(Interop.Libraries.CoreFoundationLibrary, CharSet = CharSet.Ansi)]
private static extern SafeCreateHandle CFStringCreateWithCString(
IntPtr allocator,
string str,
CFStringBuiltInEncodings encoding);
/// <summary>
/// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it.
/// </summary>
/// <param name="str">The string to get a CFStringRef for</param>
/// <returns>Returns a valid SafeCreateHandle to a CFString on success; otherwise, returns an invalid SafeCreateHandle</returns>
internal static SafeCreateHandle CFStringCreateWithCString(string str)
{
return CFStringCreateWithCString(IntPtr.Zero, str, CFStringBuiltInEncodings.kCFStringEncodingUTF8);
}
/// <summary>
/// Creates a pointer to an unmanaged CFArray containing the input values. Follows the "Create Rule" where if you create it, you delete it.
/// </summary>
/// <param name="allocator">Should be IntPtr.Zero</param>
/// <param name="values">The values to put in the array</param>
/// <param name="numValues">The number of values in the array</param>
/// <param name="callbacks">Should be IntPtr.Zero</param>
/// <returns>Returns a pointer to a CFArray on success; otherwise, returns IntPtr.Zero</returns>
[DllImport(Interop.Libraries.CoreFoundationLibrary)]
private static extern SafeCreateHandle CFArrayCreate(
IntPtr allocator,
[MarshalAs(UnmanagedType.LPArray)]
IntPtr[] values,
ulong numValues,
IntPtr callbacks);
/// <summary>
/// Creates a pointer to an unmanaged CFArray containing the input values. Follows the "Create Rule" where if you create it, you delete it.
/// </summary>
/// <param name="values">The values to put in the array</param>
/// <param name="numValues">The number of values in the array</param>
/// <returns>Returns a valid SafeCreateHandle to a CFArray on success; otherwise, returns an invalid SafeCreateHandle</returns>
internal static SafeCreateHandle CFArrayCreate(IntPtr[] values, ulong numValues)
{
return CFArrayCreate(IntPtr.Zero, values, numValues, IntPtr.Zero);
}
/// <summary>
/// You should retain a Core Foundation object when you receive it from elsewhere
/// (that is, you did not create or copy it) and you want it to persist. If you
/// retain a Core Foundation object you are responsible for releasing it
/// </summary>
/// <param name="ptr">The CFType object to retain. This value must not be NULL</param>
/// <returns>The input value</param>
[DllImport(Interop.Libraries.CoreFoundationLibrary)]
internal extern static IntPtr CFRetain(IntPtr ptr);
/// <summary>
/// Decrements the reference count on the specified object and, if the ref count hits 0, cleans up the object.
/// </summary>
/// <param name="ptr">The pointer on which to decrement the reference count.</param>
[DllImport(Interop.Libraries.CoreFoundationLibrary)]
internal extern static void CFRelease(IntPtr ptr);
}
}
}

View File

@@ -1,17 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.Credentials
{
internal static partial class Interop
{
private static partial class Libraries
{
internal const string CoreFoundationLibrary = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
internal const string CoreServicesLibrary = "/System/Library/Frameworks/CoreServices.framework/CoreServices";
internal const string SecurityLibrary = "/System/Library/Frameworks/Security.framework/Security";
}
}
}

View File

@@ -1,459 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Runtime.InteropServices;
namespace Microsoft.SqlTools.ServiceLayer.Credentials
{
internal partial class Interop
{
internal partial class Security
{
[DllImport(Libraries.SecurityLibrary, CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern OSStatus SecKeychainAddGenericPassword(IntPtr keyChainRef, UInt32 serviceNameLength, string serviceName,
UInt32 accountNameLength, string accountName, UInt32 passwordLength, IntPtr password, [Out] IntPtr itemRef);
/// <summary>
/// Find a generic password based on the attributes passed
/// </summary>
/// <param name="keyChainRef">
/// A reference to an array of keychains to search, a single keychain, or NULL to search the user's default keychain search list.
/// </param>
/// <param name="serviceNameLength">The length of the buffer pointed to by serviceName.</param>
/// <param name="serviceName">A pointer to a string containing the service name.</param>
/// <param name="accountNameLength">The length of the buffer pointed to by accountName.</param>
/// <param name="accountName">A pointer to a string containing the account name.</param>
/// <param name="passwordLength">On return, the length of the buffer pointed to by passwordData.</param>
/// <param name="password">
/// On return, a pointer to a data buffer containing the password.
/// Your application must call SecKeychainItemFreeContent(NULL, passwordData)
/// to release this data buffer when it is no longer needed.Pass NULL if you are not interested in retrieving the password data at
/// this time, but simply want to find the item reference.
/// </param>
/// <param name="itemRef">On return, a reference to the keychain item which was found.</param>
/// <returns>A result code that should be in <see cref="OSStatus"/></returns>
/// <remarks>
/// The SecKeychainFindGenericPassword function finds the first generic password item which matches the attributes you provide.
/// Most attributes are optional; you should pass only as many as you need to narrow the search sufficiently for your application's intended use.
/// SecKeychainFindGenericPassword optionally returns a reference to the found item.
/// </remarks>
[DllImport(Libraries.SecurityLibrary, CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern OSStatus SecKeychainFindGenericPassword(IntPtr keyChainRef, UInt32 serviceNameLength, string serviceName,
UInt32 accountNameLength, string accountName, out UInt32 passwordLength, out IntPtr password, out IntPtr itemRef);
/// <summary>
/// Releases the memory used by the keychain attribute list and the keychain data retrieved in a previous call to SecKeychainItemCopyContent.
/// </summary>
/// <param name="attrList">A pointer to the attribute list to release. Pass NULL to ignore this parameter.</param>
/// <param name="data">A pointer to the data buffer to release. Pass NULL to ignore this parameter.</param>
/// <returns>A result code that should be in <see cref="OSStatus"/></returns>
[DllImport(Libraries.SecurityLibrary, SetLastError = true)]
internal static extern OSStatus SecKeychainItemFreeContent([In] IntPtr attrList, [In] IntPtr data);
/// <summary>
/// Deletes a keychain item from the default keychain's permanent data store.
/// </summary>
/// <param name="itemRef">A keychain item reference of the item to delete.</param>
/// <returns>A result code that should be in <see cref="OSStatus"/></returns>
/// <remarks>
/// If itemRef has not previously been added to the keychain, SecKeychainItemDelete does nothing and returns ErrSecSuccess.
/// IMPORTANT: SecKeychainItemDelete does not dispose the memory occupied by the item reference itself;
/// use the CFRelease function when you are completely * * finished with an item.
/// </remarks>
[DllImport(Libraries.SecurityLibrary, SetLastError = true)]
internal static extern OSStatus SecKeychainItemDelete(SafeHandle itemRef);
#region OSStatus Codes
/// <summary>Common Unix errno error codes.</summary>
internal enum OSStatus
{
ErrSecSuccess = 0, /* No error. */
ErrSecUnimplemented = -4, /* Function or operation not implemented. */
ErrSecDskFull = -34,
ErrSecIO = -36, /*I/O error*/
ErrSecParam = -50, /* One or more parameters passed to a function were not valid. */
ErrSecWrPerm = -61, /* write permissions error*/
ErrSecAllocate = -108, /* Failed to allocate memory. */
ErrSecUserCanceled = -128, /* User canceled the operation. */
ErrSecBadReq = -909, /* Bad parameter or invalid state for operation. */
ErrSecInternalComponent = -2070,
ErrSecCoreFoundationUnknown = -4960,
ErrSecNotAvailable = -25291, /* No keychain is available. You may need to restart your computer. */
ErrSecReadOnly = -25292, /* This keychain cannot be modified. */
ErrSecAuthFailed = -25293, /* The user name or passphrase you entered is not correct. */
ErrSecNoSuchKeychain = -25294, /* The specified keychain could not be found. */
ErrSecInvalidKeychain = -25295, /* The specified keychain is not a valid keychain file. */
ErrSecDuplicateKeychain = -25296, /* A keychain with the same name already exists. */
ErrSecDuplicateCallback = -25297, /* The specified callback function is already installed. */
ErrSecInvalidCallback = -25298, /* The specified callback function is not valid. */
ErrSecDuplicateItem = -25299, /* The specified item already exists in the keychain. */
ErrSecItemNotFound = -25300, /* The specified item could not be found in the keychain. */
ErrSecBufferTooSmall = -25301, /* There is not enough memory available to use the specified item. */
ErrSecDataTooLarge = -25302, /* This item contains information which is too large or in a format that cannot be displayed. */
ErrSecNoSuchAttr = -25303, /* The specified attribute does not exist. */
ErrSecInvalidItemRef = -25304, /* The specified item is no longer valid. It may have been deleted from the keychain. */
ErrSecInvalidSearchRef = -25305, /* Unable to search the current keychain. */
ErrSecNoSuchClass = -25306, /* The specified item does not appear to be a valid keychain item. */
ErrSecNoDefaultKeychain = -25307, /* A default keychain could not be found. */
ErrSecInteractionNotAllowed = -25308, /* User interaction is not allowed. */
ErrSecReadOnlyAttr = -25309, /* The specified attribute could not be modified. */
ErrSecWrongSecVersion = -25310, /* This keychain was created by a different version of the system software and cannot be opened. */
ErrSecKeySizeNotAllowed = -25311, /* This item specifies a key size which is too large. */
ErrSecNoStorageModule = -25312, /* A required component (data storage module) could not be loaded. You may need to restart your computer. */
ErrSecNoCertificateModule = -25313, /* A required component (certificate module) could not be loaded. You may need to restart your computer. */
ErrSecNoPolicyModule = -25314, /* A required component (policy module) could not be loaded. You may need to restart your computer. */
ErrSecInteractionRequired = -25315, /* User interaction is required, but is currently not allowed. */
ErrSecDataNotAvailable = -25316, /* The contents of this item cannot be retrieved. */
ErrSecDataNotModifiable = -25317, /* The contents of this item cannot be modified. */
ErrSecCreateChainFailed = -25318, /* One or more certificates required to validate this certificate cannot be found. */
ErrSecInvalidPrefsDomain = -25319, /* The specified preferences domain is not valid. */
ErrSecInDarkWake = -25320, /* In dark wake, no UI possible */
ErrSecACLNotSimple = -25240, /* The specified access control list is not in standard (simple) form. */
ErrSecPolicyNotFound = -25241, /* The specified policy cannot be found. */
ErrSecInvalidTrustSetting = -25242, /* The specified trust setting is invalid. */
ErrSecNoAccessForItem = -25243, /* The specified item has no access control. */
ErrSecInvalidOwnerEdit = -25244, /* Invalid attempt to change the owner of this item. */
ErrSecTrustNotAvailable = -25245, /* No trust results are available. */
ErrSecUnsupportedFormat = -25256, /* Import/Export format unsupported. */
ErrSecUnknownFormat = -25257, /* Unknown format in import. */
ErrSecKeyIsSensitive = -25258, /* Key material must be wrapped for export. */
ErrSecMultiplePrivKeys = -25259, /* An attempt was made to import multiple private keys. */
ErrSecPassphraseRequired = -25260, /* Passphrase is required for import/export. */
ErrSecInvalidPasswordRef = -25261, /* The password reference was invalid. */
ErrSecInvalidTrustSettings = -25262, /* The Trust Settings Record was corrupted. */
ErrSecNoTrustSettings = -25263, /* No Trust Settings were found. */
ErrSecPkcs12VerifyFailure = -25264, /* MAC verification failed during PKCS12 import (wrong password?) */
ErrSecNotSigner = -26267, /* A certificate was not signed by its proposed parent. */
ErrSecDecode = -26275, /* Unable to decode the provided data. */
ErrSecServiceNotAvailable = -67585, /* The required service is not available. */
ErrSecInsufficientClientID = -67586, /* The client ID is not correct. */
ErrSecDeviceReset = -67587, /* A device reset has occurred. */
ErrSecDeviceFailed = -67588, /* A device failure has occurred. */
ErrSecAppleAddAppACLSubject = -67589, /* Adding an application ACL subject failed. */
ErrSecApplePublicKeyIncomplete = -67590, /* The public key is incomplete. */
ErrSecAppleSignatureMismatch = -67591, /* A signature mismatch has occurred. */
ErrSecAppleInvalidKeyStartDate = -67592, /* The specified key has an invalid start date. */
ErrSecAppleInvalidKeyEndDate = -67593, /* The specified key has an invalid end date. */
ErrSecConversionError = -67594, /* A conversion error has occurred. */
ErrSecAppleSSLv2Rollback = -67595, /* A SSLv2 rollback error has occurred. */
ErrSecDiskFull = -34, /* The disk is full. */
ErrSecQuotaExceeded = -67596, /* The quota was exceeded. */
ErrSecFileTooBig = -67597, /* The file is too big. */
ErrSecInvalidDatabaseBlob = -67598, /* The specified database has an invalid blob. */
ErrSecInvalidKeyBlob = -67599, /* The specified database has an invalid key blob. */
ErrSecIncompatibleDatabaseBlob = -67600, /* The specified database has an incompatible blob. */
ErrSecIncompatibleKeyBlob = -67601, /* The specified database has an incompatible key blob. */
ErrSecHostNameMismatch = -67602, /* A host name mismatch has occurred. */
ErrSecUnknownCriticalExtensionFlag = -67603, /* There is an unknown critical extension flag. */
ErrSecNoBasicConstraints = -67604, /* No basic constraints were found. */
ErrSecNoBasicConstraintsCA = -67605, /* No basic CA constraints were found. */
ErrSecInvalidAuthorityKeyID = -67606, /* The authority key ID is not valid. */
ErrSecInvalidSubjectKeyID = -67607, /* The subject key ID is not valid. */
ErrSecInvalidKeyUsageForPolicy = -67608, /* The key usage is not valid for the specified policy. */
ErrSecInvalidExtendedKeyUsage = -67609, /* The extended key usage is not valid. */
ErrSecInvalidIDLinkage = -67610, /* The ID linkage is not valid. */
ErrSecPathLengthConstraintExceeded = -67611, /* The path length constraint was exceeded. */
ErrSecInvalidRoot = -67612, /* The root or anchor certificate is not valid. */
ErrSecCRLExpired = -67613, /* The CRL has expired. */
ErrSecCRLNotValidYet = -67614, /* The CRL is not yet valid. */
ErrSecCRLNotFound = -67615, /* The CRL was not found. */
ErrSecCRLServerDown = -67616, /* The CRL server is down. */
ErrSecCRLBadURI = -67617, /* The CRL has a bad Uniform Resource Identifier. */
ErrSecUnknownCertExtension = -67618, /* An unknown certificate extension was encountered. */
ErrSecUnknownCRLExtension = -67619, /* An unknown CRL extension was encountered. */
ErrSecCRLNotTrusted = -67620, /* The CRL is not trusted. */
ErrSecCRLPolicyFailed = -67621, /* The CRL policy failed. */
ErrSecIDPFailure = -67622, /* The issuing distribution point was not valid. */
ErrSecSMIMEEmailAddressesNotFound = -67623, /* An email address mismatch was encountered. */
ErrSecSMIMEBadExtendedKeyUsage = -67624, /* The appropriate extended key usage for SMIME was not found. */
ErrSecSMIMEBadKeyUsage = -67625, /* The key usage is not compatible with SMIME. */
ErrSecSMIMEKeyUsageNotCritical = -67626, /* The key usage extension is not marked as critical. */
ErrSecSMIMENoEmailAddress = -67627, /* No email address was found in the certificate. */
ErrSecSMIMESubjAltNameNotCritical = -67628, /* The subject alternative name extension is not marked as critical. */
ErrSecSSLBadExtendedKeyUsage = -67629, /* The appropriate extended key usage for SSL was not found. */
ErrSecOCSPBadResponse = -67630, /* The OCSP response was incorrect or could not be parsed. */
ErrSecOCSPBadRequest = -67631, /* The OCSP request was incorrect or could not be parsed. */
ErrSecOCSPUnavailable = -67632, /* OCSP service is unavailable. */
ErrSecOCSPStatusUnrecognized = -67633, /* The OCSP server did not recognize this certificate. */
ErrSecEndOfData = -67634, /* An end-of-data was detected. */
ErrSecIncompleteCertRevocationCheck = -67635, /* An incomplete certificate revocation check occurred. */
ErrSecNetworkFailure = -67636, /* A network failure occurred. */
ErrSecOCSPNotTrustedToAnchor = -67637, /* The OCSP response was not trusted to a root or anchor certificate. */
ErrSecRecordModified = -67638, /* The record was modified. */
ErrSecOCSPSignatureError = -67639, /* The OCSP response had an invalid signature. */
ErrSecOCSPNoSigner = -67640, /* The OCSP response had no signer. */
ErrSecOCSPResponderMalformedReq = -67641, /* The OCSP responder was given a malformed request. */
ErrSecOCSPResponderInternalError = -67642, /* The OCSP responder encountered an internal error. */
ErrSecOCSPResponderTryLater = -67643, /* The OCSP responder is busy, try again later. */
ErrSecOCSPResponderSignatureRequired = -67644, /* The OCSP responder requires a signature. */
ErrSecOCSPResponderUnauthorized = -67645, /* The OCSP responder rejected this request as unauthorized. */
ErrSecOCSPResponseNonceMismatch = -67646, /* The OCSP response nonce did not match the request. */
ErrSecCodeSigningBadCertChainLength = -67647, /* Code signing encountered an incorrect certificate chain length. */
ErrSecCodeSigningNoBasicConstraints = -67648, /* Code signing found no basic constraints. */
ErrSecCodeSigningBadPathLengthConstraint= -67649, /* Code signing encountered an incorrect path length constraint. */
ErrSecCodeSigningNoExtendedKeyUsage = -67650, /* Code signing found no extended key usage. */
ErrSecCodeSigningDevelopment = -67651, /* Code signing indicated use of a development-only certificate. */
ErrSecResourceSignBadCertChainLength = -67652, /* Resource signing has encountered an incorrect certificate chain length. */
ErrSecResourceSignBadExtKeyUsage = -67653, /* Resource signing has encountered an error in the extended key usage. */
ErrSecTrustSettingDeny = -67654, /* The trust setting for this policy was set to Deny. */
ErrSecInvalidSubjectName = -67655, /* An invalid certificate subject name was encountered. */
ErrSecUnknownQualifiedCertStatement = -67656, /* An unknown qualified certificate statement was encountered. */
ErrSecMobileMeRequestQueued = -67657, /* The MobileMe request will be sent during the next connection. */
ErrSecMobileMeRequestRedirected = -67658, /* The MobileMe request was redirected. */
ErrSecMobileMeServerError = -67659, /* A MobileMe server error occurred. */
ErrSecMobileMeServerNotAvailable = -67660, /* The MobileMe server is not available. */
ErrSecMobileMeServerAlreadyExists = -67661, /* The MobileMe server reported that the item already exists. */
ErrSecMobileMeServerServiceErr = -67662, /* A MobileMe service error has occurred. */
ErrSecMobileMeRequestAlreadyPending = -67663, /* A MobileMe request is already pending. */
ErrSecMobileMeNoRequestPending = -67664, /* MobileMe has no request pending. */
ErrSecMobileMeCSRVerifyFailure = -67665, /* A MobileMe CSR verification failure has occurred. */
ErrSecMobileMeFailedConsistencyCheck = -67666, /* MobileMe has found a failed consistency check. */
ErrSecNotInitialized = -67667, /* A function was called without initializing CSSM. */
ErrSecInvalidHandleUsage = -67668, /* The CSSM handle does not match with the service type. */
ErrSecPVCReferentNotFound = -67669, /* A reference to the calling module was not found in the list of authorized callers. */
ErrSecFunctionIntegrityFail = -67670, /* A function address was not within the verified module. */
ErrSecInternalError = -67671, /* An internal error has occurred. */
ErrSecMemoryError = -67672, /* A memory error has occurred. */
ErrSecInvalidData = -67673, /* Invalid data was encountered. */
ErrSecMDSError = -67674, /* A Module Directory Service error has occurred. */
ErrSecInvalidPointer = -67675, /* An invalid pointer was encountered. */
ErrSecSelfCheckFailed = -67676, /* Self-check has failed. */
ErrSecFunctionFailed = -67677, /* A function has failed. */
ErrSecModuleManifestVerifyFailed = -67678, /* A module manifest verification failure has occurred. */
ErrSecInvalidGUID = -67679, /* An invalid GUID was encountered. */
ErrSecInvalidHandle = -67680, /* An invalid handle was encountered. */
ErrSecInvalidDBList = -67681, /* An invalid DB list was encountered. */
ErrSecInvalidPassthroughID = -67682, /* An invalid passthrough ID was encountered. */
ErrSecInvalidNetworkAddress = -67683, /* An invalid network address was encountered. */
ErrSecCRLAlreadySigned = -67684, /* The certificate revocation list is already signed. */
ErrSecInvalidNumberOfFields = -67685, /* An invalid number of fields were encountered. */
ErrSecVerificationFailure = -67686, /* A verification failure occurred. */
ErrSecUnknownTag = -67687, /* An unknown tag was encountered. */
ErrSecInvalidSignature = -67688, /* An invalid signature was encountered. */
ErrSecInvalidName = -67689, /* An invalid name was encountered. */
ErrSecInvalidCertificateRef = -67690, /* An invalid certificate reference was encountered. */
ErrSecInvalidCertificateGroup = -67691, /* An invalid certificate group was encountered. */
ErrSecTagNotFound = -67692, /* The specified tag was not found. */
ErrSecInvalidQuery = -67693, /* The specified query was not valid. */
ErrSecInvalidValue = -67694, /* An invalid value was detected. */
ErrSecCallbackFailed = -67695, /* A callback has failed. */
ErrSecACLDeleteFailed = -67696, /* An ACL delete operation has failed. */
ErrSecACLReplaceFailed = -67697, /* An ACL replace operation has failed. */
ErrSecACLAddFailed = -67698, /* An ACL add operation has failed. */
ErrSecACLChangeFailed = -67699, /* An ACL change operation has failed. */
ErrSecInvalidAccessCredentials = -67700, /* Invalid access credentials were encountered. */
ErrSecInvalidRecord = -67701, /* An invalid record was encountered. */
ErrSecInvalidACL = -67702, /* An invalid ACL was encountered. */
ErrSecInvalidSampleValue = -67703, /* An invalid sample value was encountered. */
ErrSecIncompatibleVersion = -67704, /* An incompatible version was encountered. */
ErrSecPrivilegeNotGranted = -67705, /* The privilege was not granted. */
ErrSecInvalidScope = -67706, /* An invalid scope was encountered. */
ErrSecPVCAlreadyConfigured = -67707, /* The PVC is already configured. */
ErrSecInvalidPVC = -67708, /* An invalid PVC was encountered. */
ErrSecEMMLoadFailed = -67709, /* The EMM load has failed. */
ErrSecEMMUnloadFailed = -67710, /* The EMM unload has failed. */
ErrSecAddinLoadFailed = -67711, /* The add-in load operation has failed. */
ErrSecInvalidKeyRef = -67712, /* An invalid key was encountered. */
ErrSecInvalidKeyHierarchy = -67713, /* An invalid key hierarchy was encountered. */
ErrSecAddinUnloadFailed = -67714, /* The add-in unload operation has failed. */
ErrSecLibraryReferenceNotFound = -67715, /* A library reference was not found. */
ErrSecInvalidAddinFunctionTable = -67716, /* An invalid add-in function table was encountered. */
ErrSecInvalidServiceMask = -67717, /* An invalid service mask was encountered. */
ErrSecModuleNotLoaded = -67718, /* A module was not loaded. */
ErrSecInvalidSubServiceID = -67719, /* An invalid subservice ID was encountered. */
ErrSecAttributeNotInContext = -67720, /* An attribute was not in the context. */
ErrSecModuleManagerInitializeFailed = -67721, /* A module failed to initialize. */
ErrSecModuleManagerNotFound = -67722, /* A module was not found. */
ErrSecEventNotificationCallbackNotFound = -67723, /* An event notification callback was not found. */
ErrSecInputLengthError = -67724, /* An input length error was encountered. */
ErrSecOutputLengthError = -67725, /* An output length error was encountered. */
ErrSecPrivilegeNotSupported = -67726, /* The privilege is not supported. */
ErrSecDeviceError = -67727, /* A device error was encountered. */
ErrSecAttachHandleBusy = -67728, /* The CSP handle was busy. */
ErrSecNotLoggedIn = -67729, /* You are not logged in. */
ErrSecAlgorithmMismatch = -67730, /* An algorithm mismatch was encountered. */
ErrSecKeyUsageIncorrect = -67731, /* The key usage is incorrect. */
ErrSecKeyBlobTypeIncorrect = -67732, /* The key blob type is incorrect. */
ErrSecKeyHeaderInconsistent = -67733, /* The key header is inconsistent. */
ErrSecUnsupportedKeyFormat = -67734, /* The key header format is not supported. */
ErrSecUnsupportedKeySize = -67735, /* The key size is not supported. */
ErrSecInvalidKeyUsageMask = -67736, /* The key usage mask is not valid. */
ErrSecUnsupportedKeyUsageMask = -67737, /* The key usage mask is not supported. */
ErrSecInvalidKeyAttributeMask = -67738, /* The key attribute mask is not valid. */
ErrSecUnsupportedKeyAttributeMask = -67739, /* The key attribute mask is not supported. */
ErrSecInvalidKeyLabel = -67740, /* The key label is not valid. */
ErrSecUnsupportedKeyLabel = -67741, /* The key label is not supported. */
ErrSecInvalidKeyFormat = -67742, /* The key format is not valid. */
ErrSecUnsupportedVectorOfBuffers = -67743, /* The vector of buffers is not supported. */
ErrSecInvalidInputVector = -67744, /* The input vector is not valid. */
ErrSecInvalidOutputVector = -67745, /* The output vector is not valid. */
ErrSecInvalidContext = -67746, /* An invalid context was encountered. */
ErrSecInvalidAlgorithm = -67747, /* An invalid algorithm was encountered. */
ErrSecInvalidAttributeKey = -67748, /* A key attribute was not valid. */
ErrSecMissingAttributeKey = -67749, /* A key attribute was missing. */
ErrSecInvalidAttributeInitVector = -67750, /* An init vector attribute was not valid. */
ErrSecMissingAttributeInitVector = -67751, /* An init vector attribute was missing. */
ErrSecInvalidAttributeSalt = -67752, /* A salt attribute was not valid. */
ErrSecMissingAttributeSalt = -67753, /* A salt attribute was missing. */
ErrSecInvalidAttributePadding = -67754, /* A padding attribute was not valid. */
ErrSecMissingAttributePadding = -67755, /* A padding attribute was missing. */
ErrSecInvalidAttributeRandom = -67756, /* A random number attribute was not valid. */
ErrSecMissingAttributeRandom = -67757, /* A random number attribute was missing. */
ErrSecInvalidAttributeSeed = -67758, /* A seed attribute was not valid. */
ErrSecMissingAttributeSeed = -67759, /* A seed attribute was missing. */
ErrSecInvalidAttributePassphrase = -67760, /* A passphrase attribute was not valid. */
ErrSecMissingAttributePassphrase = -67761, /* A passphrase attribute was missing. */
ErrSecInvalidAttributeKeyLength = -67762, /* A key length attribute was not valid. */
ErrSecMissingAttributeKeyLength = -67763, /* A key length attribute was missing. */
ErrSecInvalidAttributeBlockSize = -67764, /* A block size attribute was not valid. */
ErrSecMissingAttributeBlockSize = -67765, /* A block size attribute was missing. */
ErrSecInvalidAttributeOutputSize = -67766, /* An output size attribute was not valid. */
ErrSecMissingAttributeOutputSize = -67767, /* An output size attribute was missing. */
ErrSecInvalidAttributeRounds = -67768, /* The number of rounds attribute was not valid. */
ErrSecMissingAttributeRounds = -67769, /* The number of rounds attribute was missing. */
ErrSecInvalidAlgorithmParms = -67770, /* An algorithm parameters attribute was not valid. */
ErrSecMissingAlgorithmParms = -67771, /* An algorithm parameters attribute was missing. */
ErrSecInvalidAttributeLabel = -67772, /* A label attribute was not valid. */
ErrSecMissingAttributeLabel = -67773, /* A label attribute was missing. */
ErrSecInvalidAttributeKeyType = -67774, /* A key type attribute was not valid. */
ErrSecMissingAttributeKeyType = -67775, /* A key type attribute was missing. */
ErrSecInvalidAttributeMode = -67776, /* A mode attribute was not valid. */
ErrSecMissingAttributeMode = -67777, /* A mode attribute was missing. */
ErrSecInvalidAttributeEffectiveBits = -67778, /* An effective bits attribute was not valid. */
ErrSecMissingAttributeEffectiveBits = -67779, /* An effective bits attribute was missing. */
ErrSecInvalidAttributeStartDate = -67780, /* A start date attribute was not valid. */
ErrSecMissingAttributeStartDate = -67781, /* A start date attribute was missing. */
ErrSecInvalidAttributeEndDate = -67782, /* An end date attribute was not valid. */
ErrSecMissingAttributeEndDate = -67783, /* An end date attribute was missing. */
ErrSecInvalidAttributeVersion = -67784, /* A version attribute was not valid. */
ErrSecMissingAttributeVersion = -67785, /* A version attribute was missing. */
ErrSecInvalidAttributePrime = -67786, /* A prime attribute was not valid. */
ErrSecMissingAttributePrime = -67787, /* A prime attribute was missing. */
ErrSecInvalidAttributeBase = -67788, /* A base attribute was not valid. */
ErrSecMissingAttributeBase = -67789, /* A base attribute was missing. */
ErrSecInvalidAttributeSubprime = -67790, /* A subprime attribute was not valid. */
ErrSecMissingAttributeSubprime = -67791, /* A subprime attribute was missing. */
ErrSecInvalidAttributeIterationCount = -67792, /* An iteration count attribute was not valid. */
ErrSecMissingAttributeIterationCount = -67793, /* An iteration count attribute was missing. */
ErrSecInvalidAttributeDLDBHandle = -67794, /* A database handle attribute was not valid. */
ErrSecMissingAttributeDLDBHandle = -67795, /* A database handle attribute was missing. */
ErrSecInvalidAttributeAccessCredentials = -67796, /* An access credentials attribute was not valid. */
ErrSecMissingAttributeAccessCredentials = -67797, /* An access credentials attribute was missing. */
ErrSecInvalidAttributePublicKeyFormat = -67798, /* A public key format attribute was not valid. */
ErrSecMissingAttributePublicKeyFormat = -67799, /* A public key format attribute was missing. */
ErrSecInvalidAttributePrivateKeyFormat = -67800, /* A private key format attribute was not valid. */
ErrSecMissingAttributePrivateKeyFormat = -67801, /* A private key format attribute was missing. */
ErrSecInvalidAttributeSymmetricKeyFormat = -67802, /* A symmetric key format attribute was not valid. */
ErrSecMissingAttributeSymmetricKeyFormat = -67803, /* A symmetric key format attribute was missing. */
ErrSecInvalidAttributeWrappedKeyFormat = -67804, /* A wrapped key format attribute was not valid. */
ErrSecMissingAttributeWrappedKeyFormat = -67805, /* A wrapped key format attribute was missing. */
ErrSecStagedOperationInProgress = -67806, /* A staged operation is in progress. */
ErrSecStagedOperationNotStarted = -67807, /* A staged operation was not started. */
ErrSecVerifyFailed = -67808, /* A cryptographic verification failure has occurred. */
ErrSecQuerySizeUnknown = -67809, /* The query size is unknown. */
ErrSecBlockSizeMismatch = -67810, /* A block size mismatch occurred. */
ErrSecPublicKeyInconsistent = -67811, /* The public key was inconsistent. */
ErrSecDeviceVerifyFailed = -67812, /* A device verification failure has occurred. */
ErrSecInvalidLoginName = -67813, /* An invalid login name was detected. */
ErrSecAlreadyLoggedIn = -67814, /* The user is already logged in. */
ErrSecInvalidDigestAlgorithm = -67815, /* An invalid digest algorithm was detected. */
ErrSecInvalidCRLGroup = -67816, /* An invalid CRL group was detected. */
ErrSecCertificateCannotOperate = -67817, /* The certificate cannot operate. */
ErrSecCertificateExpired = -67818, /* An expired certificate was detected. */
ErrSecCertificateNotValidYet = -67819, /* The certificate is not yet valid. */
ErrSecCertificateRevoked = -67820, /* The certificate was revoked. */
ErrSecCertificateSuspended = -67821, /* The certificate was suspended. */
ErrSecInsufficientCredentials = -67822, /* Insufficient credentials were detected. */
ErrSecInvalidAction = -67823, /* The action was not valid. */
ErrSecInvalidAuthority = -67824, /* The authority was not valid. */
ErrSecVerifyActionFailed = -67825, /* A verify action has failed. */
ErrSecInvalidCertAuthority = -67826, /* The certificate authority was not valid. */
ErrSecInvaldCRLAuthority = -67827, /* The CRL authority was not valid. */
ErrSecInvalidCRLEncoding = -67828, /* The CRL encoding was not valid. */
ErrSecInvalidCRLType = -67829, /* The CRL type was not valid. */
ErrSecInvalidCRL = -67830, /* The CRL was not valid. */
ErrSecInvalidFormType = -67831, /* The form type was not valid. */
ErrSecInvalidID = -67832, /* The ID was not valid. */
ErrSecInvalidIdentifier = -67833, /* The identifier was not valid. */
ErrSecInvalidIndex = -67834, /* The index was not valid. */
ErrSecInvalidPolicyIdentifiers = -67835, /* The policy identifiers are not valid. */
ErrSecInvalidTimeString = -67836, /* The time specified was not valid. */
ErrSecInvalidReason = -67837, /* The trust policy reason was not valid. */
ErrSecInvalidRequestInputs = -67838, /* The request inputs are not valid. */
ErrSecInvalidResponseVector = -67839, /* The response vector was not valid. */
ErrSecInvalidStopOnPolicy = -67840, /* The stop-on policy was not valid. */
ErrSecInvalidTuple = -67841, /* The tuple was not valid. */
ErrSecMultipleValuesUnsupported = -67842, /* Multiple values are not supported. */
ErrSecNotTrusted = -67843, /* The trust policy was not trusted. */
ErrSecNoDefaultAuthority = -67844, /* No default authority was detected. */
ErrSecRejectedForm = -67845, /* The trust policy had a rejected form. */
ErrSecRequestLost = -67846, /* The request was lost. */
ErrSecRequestRejected = -67847, /* The request was rejected. */
ErrSecUnsupportedAddressType = -67848, /* The address type is not supported. */
ErrSecUnsupportedService = -67849, /* The service is not supported. */
ErrSecInvalidTupleGroup = -67850, /* The tuple group was not valid. */
ErrSecInvalidBaseACLs = -67851, /* The base ACLs are not valid. */
ErrSecInvalidTupleCredendtials = -67852, /* The tuple credentials are not valid. */
ErrSecInvalidEncoding = -67853, /* The encoding was not valid. */
ErrSecInvalidValidityPeriod = -67854, /* The validity period was not valid. */
ErrSecInvalidRequestor = -67855, /* The requestor was not valid. */
ErrSecRequestDescriptor = -67856, /* The request descriptor was not valid. */
ErrSecInvalidBundleInfo = -67857, /* The bundle information was not valid. */
ErrSecInvalidCRLIndex = -67858, /* The CRL index was not valid. */
ErrSecNoFieldValues = -67859, /* No field values were detected. */
ErrSecUnsupportedFieldFormat = -67860, /* The field format is not supported. */
ErrSecUnsupportedIndexInfo = -67861, /* The index information is not supported. */
ErrSecUnsupportedLocality = -67862, /* The locality is not supported. */
ErrSecUnsupportedNumAttributes = -67863, /* The number of attributes is not supported. */
ErrSecUnsupportedNumIndexes = -67864, /* The number of indexes is not supported. */
ErrSecUnsupportedNumRecordTypes = -67865, /* The number of record types is not supported. */
ErrSecFieldSpecifiedMultiple = -67866, /* Too many fields were specified. */
ErrSecIncompatibleFieldFormat = -67867, /* The field format was incompatible. */
ErrSecInvalidParsingModule = -67868, /* The parsing module was not valid. */
ErrSecDatabaseLocked = -67869, /* The database is locked. */
ErrSecDatastoreIsOpen = -67870, /* The data store is open. */
ErrSecMissingValue = -67871, /* A missing value was detected. */
ErrSecUnsupportedQueryLimits = -67872, /* The query limits are not supported. */
ErrSecUnsupportedNumSelectionPreds = -67873, /* The number of selection predicates is not supported. */
ErrSecUnsupportedOperator = -67874, /* The operator is not supported. */
ErrSecInvalidDBLocation = -67875, /* The database location is not valid. */
ErrSecInvalidAccessRequest = -67876, /* The access request is not valid. */
ErrSecInvalidIndexInfo = -67877, /* The index information is not valid. */
ErrSecInvalidNewOwner = -67878, /* The new owner is not valid. */
ErrSecInvalidModifyMode = -67879, /* The modify mode is not valid. */
ErrSecMissingRequiredExtension = -67880, /* A required certificate extension is missing. */
ErrSecExtendedKeyUsageNotCritical = -67881, /* The extended key usage extension was not marked critical. */
ErrSecTimestampMissing = -67882, /* A timestamp was expected but was not found. */
ErrSecTimestampInvalid = -67883, /* The timestamp was not valid. */
ErrSecTimestampNotTrusted = -67884, /* The timestamp was not trusted. */
ErrSecTimestampServiceNotAvailable = -67885, /* The timestamp service is not available. */
ErrSecTimestampBadAlg = -67886, /* An unrecognized or unsupported Algorithm Identifier in timestamp. */
ErrSecTimestampBadRequest = -67887, /* The timestamp transaction is not permitted or supported. */
ErrSecTimestampBadDataFormat = -67888, /* The timestamp data submitted has the wrong format. */
ErrSecTimestampTimeNotAvailable = -67889, /* The time source for the Timestamp Authority is not available. */
ErrSecTimestampUnacceptedPolicy = -67890, /* The requested policy is not supported by the Timestamp Authority. */
ErrSecTimestampUnacceptedExtension = -67891, /* The requested extension is not supported by the Timestamp Authority. */
ErrSecTimestampAddInfoNotAvailable = -67892, /* The additional information requested is not available. */
ErrSecTimestampSystemFailure = -67893, /* The timestamp request cannot be handled due to system failure. */
ErrSecSigningTimeMissing = -67894, /* A signing time was expected but was not found. */
ErrSecTimestampRejection = -67895, /* A timestamp transaction was rejected. */
ErrSecTimestampWaiting = -67896, /* A timestamp transaction is waiting. */
ErrSecTimestampRevocationWarning = -67897, /* A timestamp authority revocation warning was issued. */
ErrSecTimestampRevocationNotification = -67898, /* A timestamp authority revocation notification was issued. */
}
#endregion
}
}
}

View File

@@ -1,165 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Runtime.InteropServices;
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Credentials.OSX
{
#if !WINDOWS_ONLY_BUILD
/// <summary>
/// OSX implementation of the credential store
/// </summary>
internal class OSXCredentialStore : ICredentialStore
{
public bool DeletePassword(string credentialId)
{
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
return DeletePasswordImpl(credentialId);
}
public bool TryGetPassword(string credentialId, out string password)
{
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
return FindPassword(credentialId, out password);
}
public bool Save(Credential credential)
{
Credential.ValidateForSave(credential);
bool result = false;
// Note: OSX blocks AddPassword if the credential
// already exists, so for now we delete the password if already present since we're updating
// the value. In the future, we could consider updating but it's low value to solve this
DeletePasswordImpl(credential.CredentialId);
// Now add the password
result = AddGenericPassword(credential);
return result;
}
private bool AddGenericPassword(Credential credential)
{
IntPtr passwordPtr = Marshal.StringToCoTaskMemUni(credential.Password);
Interop.Security.OSStatus status = Interop.Security.SecKeychainAddGenericPassword(
IntPtr.Zero,
InteropUtils.GetLengthInBytes(credential.CredentialId),
credential.CredentialId,
0,
null,
InteropUtils.GetLengthInBytes(credential.Password),
passwordPtr,
IntPtr.Zero);
return status == Interop.Security.OSStatus.ErrSecSuccess;
}
/// <summary>
/// Finds the first password matching this credential
/// </summary>
private bool FindPassword(string credentialId, out string password)
{
password = null;
using (KeyChainItemHandle handle = LookupKeyChainItem(credentialId))
{
if( handle == null)
{
return false;
}
password = handle.Password;
}
return true;
}
private KeyChainItemHandle LookupKeyChainItem(string credentialId)
{
UInt32 passwordLength;
IntPtr passwordPtr;
IntPtr item;
Interop.Security.OSStatus status = Interop.Security.SecKeychainFindGenericPassword(
IntPtr.Zero,
InteropUtils.GetLengthInBytes(credentialId),
credentialId,
0,
null,
out passwordLength,
out passwordPtr,
out item);
if(status == Interop.Security.OSStatus.ErrSecSuccess)
{
return new KeyChainItemHandle(item, passwordPtr, passwordLength);
}
return null;
}
private bool DeletePasswordImpl(string credentialId)
{
// Find password, then Delete, then cleanup
using (KeyChainItemHandle handle = LookupKeyChainItem(credentialId))
{
if (handle == null)
{
return false;
}
Interop.Security.OSStatus status = Interop.Security.SecKeychainItemDelete(handle);
return status == Interop.Security.OSStatus.ErrSecSuccess;
}
}
private class KeyChainItemHandle : SafeCreateHandle
{
private IntPtr passwordPtr;
private int passwordLength;
public KeyChainItemHandle() : base()
{
}
public KeyChainItemHandle(IntPtr itemPtr) : this(itemPtr, IntPtr.Zero, 0)
{
}
public KeyChainItemHandle(IntPtr itemPtr, IntPtr passwordPtr, UInt32 passwordLength)
: base(itemPtr)
{
this.passwordPtr = passwordPtr;
this.passwordLength = (int) passwordLength;
}
public string Password
{
get {
if (IsInvalid)
{
return null;
}
return InteropUtils.CopyToString(passwordPtr, passwordLength);
}
}
protected override bool ReleaseHandle()
{
if (passwordPtr != IntPtr.Zero)
{
Interop.Security.SecKeychainItemFreeContent(IntPtr.Zero, passwordPtr);
}
base.ReleaseHandle();
return true;
}
}
}
#endif
}

View File

@@ -1,43 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
namespace Microsoft.SqlTools.ServiceLayer.Credentials
{
/// <summary>
/// This class is a wrapper around the Create pattern in OS X where
/// if a Create* function is called, the caller must also CFRelease
/// on the same pointer in order to correctly free the memory.
/// </summary>
[System.Security.SecurityCritical]
internal partial class SafeCreateHandle : SafeHandle
{
internal SafeCreateHandle() : base(IntPtr.Zero, true) { }
internal SafeCreateHandle(IntPtr ptr) : base(IntPtr.Zero, true)
{
this.SetHandle(ptr);
}
[System.Security.SecurityCritical]
protected override bool ReleaseHandle()
{
Interop.CoreFoundation.CFRelease(handle);
return true;
}
public override bool IsInvalid
{
[System.Security.SecurityCritical]
get
{
return handle == IntPtr.Zero;
}
}
}
}

View File

@@ -1,42 +0,0 @@
//
// Code originally from http://credentialmanagement.codeplex.com/,
// Licensed under the Apache License 2.0
//
using System;
using System.Runtime.InteropServices;
using System.Security;
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
{
internal static class SecureStringHelper
{
// Methods
internal static SecureString CreateSecureString(string plainString)
{
SecureString str = new SecureString();
if (!string.IsNullOrEmpty(plainString))
{
foreach (char c in plainString)
{
str.AppendChar(c);
}
}
str.MakeReadOnly();
return str;
}
internal static string CreateString(SecureString value)
{
IntPtr ptr = SecureStringMarshal.SecureStringToGlobalAllocUnicode(value);
try
{
return Marshal.PtrToStringUni(ptr);
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(ptr);
}
}
}
}

View File

@@ -1,113 +0,0 @@
//
// Code originally from http://credentialmanagement.codeplex.com/,
// Licensed under the Apache License 2.0
//
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
{
public class CredentialSet: List<Win32Credential>, IDisposable
{
bool _disposed;
public CredentialSet()
{
}
public CredentialSet(string target)
: this()
{
if (string.IsNullOrEmpty(target))
{
throw new ArgumentNullException(nameof(target));
}
Target = target;
}
public string Target { get; set; }
public void Dispose()
{
Dispose(true);
// Prevent GC Collection since we have already disposed of this object
GC.SuppressFinalize(this);
}
~CredentialSet()
{
Dispose(false);
}
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
if (Count > 0)
{
ForEach(cred => cred.Dispose());
}
}
}
_disposed = true;
}
public CredentialSet Load()
{
LoadInternal();
return this;
}
private void LoadInternal()
{
uint count;
IntPtr pCredentials = IntPtr.Zero;
bool result = NativeMethods.CredEnumerateW(Target, 0, out count, out pCredentials);
if (!result)
{
Logger.Write(LogLevel.Error, string.Format("Win32Exception: {0}", new Win32Exception(Marshal.GetLastWin32Error()).ToString()));
return;
}
// Read in all of the pointers first
IntPtr[] ptrCredList = new IntPtr[count];
for (int i = 0; i < count; i++)
{
ptrCredList[i] = Marshal.ReadIntPtr(pCredentials, IntPtr.Size*i);
}
// Now let's go through all of the pointers in the list
// and create our Credential object(s)
List<NativeMethods.CriticalCredentialHandle> credentialHandles =
ptrCredList.Select(ptrCred => new NativeMethods.CriticalCredentialHandle(ptrCred)).ToList();
IEnumerable<Win32Credential> existingCredentials = credentialHandles
.Select(handle => handle.GetCredential())
.Select(nativeCredential =>
{
Win32Credential credential = new Win32Credential();
credential.LoadInternal(nativeCredential);
return credential;
});
AddRange(existingCredentials);
// The individual credentials should not be free'd
credentialHandles.ForEach(handle => handle.SetHandleAsInvalid());
// Clean up memory to the Enumeration pointer
NativeMethods.CredFree(pCredentials);
}
}
}

View File

@@ -1,16 +0,0 @@
//
// Code originally from http://credentialmanagement.codeplex.com/,
// Licensed under the Apache License 2.0
//
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
{
public enum CredentialType: uint
{
None = 0,
Generic = 1,
DomainPassword = 2,
DomainCertificate = 3,
DomainVisiblePassword = 4
}
}

View File

@@ -1,17 +0,0 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
//
// To add a suppression to this file, right-click the message in the
// Code Analysis results, point to "Suppress Message", and click
// "In Suppression File".
// You do not need to add suppressions to this file manually.
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member", Target = "CredentialManagement.CredentialSet.#LoadInternal()")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1049:TypesThatOwnNativeResourcesShouldBeDisposable", Scope = "type", Target = "CredentialManagement.NativeMethods+CREDENTIAL")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "7", Scope = "member", Target = "CredentialManagement.NativeMethods.#CredUnPackAuthenticationBuffer(System.Int32,System.IntPtr,System.UInt32,System.Text.StringBuilder,System.Int32&,System.Text.StringBuilder,System.Int32&,System.Text.StringBuilder,System.Int32&)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "5", Scope = "member", Target = "CredentialManagement.NativeMethods.#CredUnPackAuthenticationBuffer(System.Int32,System.IntPtr,System.UInt32,System.Text.StringBuilder,System.Int32&,System.Text.StringBuilder,System.Int32&,System.Text.StringBuilder,System.Int32&)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "3", Scope = "member", Target = "CredentialManagement.NativeMethods.#CredUnPackAuthenticationBuffer(System.Int32,System.IntPtr,System.UInt32,System.Text.StringBuilder,System.Int32&,System.Text.StringBuilder,System.Int32&,System.Text.StringBuilder,System.Int32&)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Scope = "member", Target = "CredentialManagement.SecureStringHelper.#CreateString(System.Security.SecureString)")]
<EFBFBD>

View File

@@ -1,109 +0,0 @@
//
// Code originally from http://credentialmanagement.codeplex.com/,
// Licensed under the Apache License 2.0
//
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
{
internal class NativeMethods
{
[StructLayout(LayoutKind.Sequential)]
internal struct CREDENTIAL
{
public int Flags;
public int Type;
[MarshalAs(UnmanagedType.LPWStr)]
public string TargetName;
[MarshalAs(UnmanagedType.LPWStr)]
public string Comment;
public long LastWritten;
public int CredentialBlobSize;
public IntPtr CredentialBlob;
public int Persist;
public int AttributeCount;
public IntPtr Attributes;
[MarshalAs(UnmanagedType.LPWStr)]
public string TargetAlias;
[MarshalAs(UnmanagedType.LPWStr)]
public string UserName;
}
[DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool CredRead(string target, CredentialType type, int reservedFlag, out IntPtr CredentialPtr);
[DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool CredWrite([In] ref CREDENTIAL userCredential, [In] UInt32 flags);
[DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)]
internal static extern bool CredFree([In] IntPtr cred);
[DllImport("advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode)]
internal static extern bool CredDelete(StringBuilder target, CredentialType type, int flags);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool CredEnumerateW(string filter, int flag, out uint count, out IntPtr pCredentials);
[DllImport("ole32.dll")]
internal static extern void CoTaskMemFree(IntPtr ptr);
internal abstract class CriticalHandleZeroOrMinusOneIsInvalid : CriticalHandle
{
protected CriticalHandleZeroOrMinusOneIsInvalid() : base(IntPtr.Zero)
{
}
public override bool IsInvalid
{
get { return handle == new IntPtr(0) || handle == new IntPtr(-1); }
}
}
internal sealed class CriticalCredentialHandle : CriticalHandleZeroOrMinusOneIsInvalid
{
// Set the handle.
internal CriticalCredentialHandle(IntPtr preexistingHandle)
{
SetHandle(preexistingHandle);
}
internal CREDENTIAL GetCredential()
{
if (!IsInvalid)
{
// Get the Credential from the mem location
return (CREDENTIAL)Marshal.PtrToStructure<CREDENTIAL>(handle);
}
else
{
throw new InvalidOperationException(SR.CredentialsServiceInvalidCriticalHandle);
}
}
// Perform any specific actions to release the handle in the ReleaseHandle method.
// Often, you need to use Pinvoke to make a call into the Win32 API to release the
// handle. In this case, however, we can use the Marshal class to release the unmanaged memory.
protected override bool ReleaseHandle()
{
// If the handle was set, free it. Return success.
if (!IsInvalid)
{
// NOTE: We should also ZERO out the memory allocated to the handle, before free'ing it
// so there are no traces of the sensitive data left in memory.
CredFree(handle);
// Mark the handle as invalid for future users.
SetHandleAsInvalid();
return true;
}
// Return false.
return false;
}
}
}
}

View File

@@ -1,14 +0,0 @@
//
// Code originally from http://credentialmanagement.codeplex.com/,
// Licensed under the Apache License 2.0
//
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
{
public enum PersistanceType : uint
{
Session = 1,
LocalComputer = 2,
Enterprise = 3
}
}

View File

@@ -1,290 +0,0 @@
//
// Code originally from http://credentialmanagement.codeplex.com/,
// Licensed under the Apache License 2.0
//
using System;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
{
public class Win32Credential: IDisposable
{
bool disposed;
CredentialType type;
string target;
SecureString password;
string username;
string description;
DateTime lastWriteTime;
PersistanceType persistanceType;
public Win32Credential()
: this(null)
{
}
public Win32Credential(string username)
: this(username, null)
{
}
public Win32Credential(string username, string password)
: this(username, password, null)
{
}
public Win32Credential(string username, string password, string target)
: this(username, password, target, CredentialType.Generic)
{
}
public Win32Credential(string username, string password, string target, CredentialType type)
{
Username = username;
Password = password;
Target = target;
Type = type;
PersistanceType = PersistanceType.Session;
lastWriteTime = DateTime.MinValue;
}
public void Dispose()
{
Dispose(true);
// Prevent GC Collection since we have already disposed of this object
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
SecurePassword.Clear();
SecurePassword.Dispose();
}
}
disposed = true;
}
private void CheckNotDisposed()
{
if (disposed)
{
throw new ObjectDisposedException(SR.CredentialServiceWin32CredentialDisposed);
}
}
public string Username {
get
{
CheckNotDisposed();
return username;
}
set
{
CheckNotDisposed();
username = value;
}
}
public string Password
{
get
{
return SecureStringHelper.CreateString(SecurePassword);
}
set
{
CheckNotDisposed();
SecurePassword = SecureStringHelper.CreateSecureString(string.IsNullOrEmpty(value) ? string.Empty : value);
}
}
public SecureString SecurePassword
{
get
{
CheckNotDisposed();
return null == password ? new SecureString() : password.Copy();
}
set
{
CheckNotDisposed();
if (null != password)
{
password.Clear();
password.Dispose();
}
password = null == value ? new SecureString() : value.Copy();
}
}
public string Target
{
get
{
CheckNotDisposed();
return target;
}
set
{
CheckNotDisposed();
target = value;
}
}
public string Description
{
get
{
CheckNotDisposed();
return description;
}
set
{
CheckNotDisposed();
description = value;
}
}
public DateTime LastWriteTime
{
get
{
return LastWriteTimeUtc.ToLocalTime();
}
}
public DateTime LastWriteTimeUtc
{
get
{
CheckNotDisposed();
return lastWriteTime;
}
private set { lastWriteTime = value; }
}
public CredentialType Type
{
get
{
CheckNotDisposed();
return type;
}
set
{
CheckNotDisposed();
type = value;
}
}
public PersistanceType PersistanceType
{
get
{
CheckNotDisposed();
return persistanceType;
}
set
{
CheckNotDisposed();
persistanceType = value;
}
}
public bool Save()
{
CheckNotDisposed();
byte[] passwordBytes = Encoding.Unicode.GetBytes(Password);
if (Password.Length > (512))
{
throw new ArgumentOutOfRangeException(SR.CredentialsServicePasswordLengthExceeded);
}
NativeMethods.CREDENTIAL credential = new NativeMethods.CREDENTIAL();
credential.TargetName = Target;
credential.UserName = Username;
credential.CredentialBlob = Marshal.StringToCoTaskMemUni(Password);
credential.CredentialBlobSize = passwordBytes.Length;
credential.Comment = Description;
credential.Type = (int)Type;
credential.Persist = (int) PersistanceType;
bool result = NativeMethods.CredWrite(ref credential, 0);
if (!result)
{
return false;
}
LastWriteTimeUtc = DateTime.UtcNow;
return true;
}
public bool Delete()
{
CheckNotDisposed();
if (string.IsNullOrEmpty(Target))
{
throw new InvalidOperationException(SR.CredentialsServiceTargetForDelete);
}
StringBuilder target = string.IsNullOrEmpty(Target) ? new StringBuilder() : new StringBuilder(Target);
bool result = NativeMethods.CredDelete(target, Type, 0);
return result;
}
public bool Load()
{
CheckNotDisposed();
IntPtr credPointer;
bool result = NativeMethods.CredRead(Target, Type, 0, out credPointer);
if (!result)
{
return false;
}
using (NativeMethods.CriticalCredentialHandle credentialHandle = new NativeMethods.CriticalCredentialHandle(credPointer))
{
LoadInternal(credentialHandle.GetCredential());
}
return true;
}
public bool Exists()
{
CheckNotDisposed();
if (string.IsNullOrEmpty(Target))
{
throw new InvalidOperationException(SR.CredentialsServiceTargetForLookup);
}
using (Win32Credential existing = new Win32Credential { Target = Target, Type = Type })
{
return existing.Load();
}
}
internal void LoadInternal(NativeMethods.CREDENTIAL credential)
{
Username = credential.UserName;
if (credential.CredentialBlobSize > 0)
{
Password = Marshal.PtrToStringUni(credential.CredentialBlob, credential.CredentialBlobSize / 2);
}
Target = credential.TargetName;
Type = (CredentialType)credential.Type;
PersistanceType = (PersistanceType)credential.Persist;
Description = credential.Comment;
LastWriteTimeUtc = DateTime.FromFileTimeUtc(credential.LastWritten);
}
}
}

View File

@@ -1,63 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.ServiceLayer.Credentials.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Credentials.Win32
{
/// <summary>
/// Win32 implementation of the credential store
/// </summary>
internal class Win32CredentialStore : ICredentialStore
{
private const string AnyUsername = "*";
public bool DeletePassword(string credentialId)
{
using (Win32Credential cred = new Win32Credential() { Target = credentialId, Username = AnyUsername })
{
return cred.Delete();
}
}
public bool TryGetPassword(string credentialId, out string password)
{
Validate.IsNotNullOrEmptyString("credentialId", credentialId);
password = null;
using (CredentialSet set = new CredentialSet(credentialId).Load())
{
// Note: Credentials are disposed on disposal of the set
Win32Credential foundCred = null;
if (set.Count > 0)
{
foundCred = set[0];
}
if (foundCred != null)
{
password = foundCred.Password;
return true;
}
return false;
}
}
public bool Save(Credential credential)
{
Credential.ValidateForSave(credential);
using (Win32Credential cred =
new Win32Credential(AnyUsername, credential.Password, credential.CredentialId, CredentialType.Generic)
{ PersistanceType = PersistanceType.LocalComputer })
{
return cred.Save();
}
}
}
}

View File

@@ -1,184 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Composition.Convention;
using System.Composition.Hosting;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Extensibility
{
public class ExtensionServiceProvider : RegisteredServiceProvider
{
private Func<ConventionBuilder, ContainerConfiguration> config;
public ExtensionServiceProvider(Func<ConventionBuilder, ContainerConfiguration> config)
{
Validate.IsNotNull(nameof(config), config);
this.config = config;
}
public static ExtensionServiceProvider CreateDefaultServiceProvider()
{
return Create(typeof(ExtensionStore).GetTypeInfo().Assembly.SingleItemAsEnumerable());
}
public static ExtensionServiceProvider Create(IEnumerable<Assembly> assemblies)
{
Validate.IsNotNull(nameof(assemblies), assemblies);
return new ExtensionServiceProvider(conventions => new ContainerConfiguration().WithAssemblies(assemblies, conventions));
}
public static ExtensionServiceProvider Create(IEnumerable<Type> types)
{
Validate.IsNotNull(nameof(types), types);
return new ExtensionServiceProvider(conventions => new ContainerConfiguration().WithParts(types, conventions));
}
protected override IEnumerable<T> GetServicesImpl<T>()
{
EnsureExtensionStoreRegistered<T>();
return base.GetServicesImpl<T>();
}
private void EnsureExtensionStoreRegistered<T>()
{
if (!services.ContainsKey(typeof(T)))
{
ExtensionStore store = new ExtensionStore(typeof(T), config);
base.Register<T>(() => store.GetExports<T>());
}
}
}
/// <summary>
/// A store for MEF exports of a specific type. Provides basic wrapper functionality around MEF to standarize how
/// we lookup types and return to callers.
/// </summary>
public class ExtensionStore
{
private CompositionHost host;
private IList exports;
private Type contractType;
/// <summary>
/// Initializes the store with a type to lookup exports of, and a function that configures the
/// lookup parameters.
/// </summary>
/// <param name="contractType">Type to use as a base for all extensions being looked up</param>
/// <param name="configure">Function that returns the configuration to be used</param>
public ExtensionStore(Type contractType, Func<ConventionBuilder, ContainerConfiguration> configure)
{
Validate.IsNotNull(nameof(contractType), contractType);
Validate.IsNotNull(nameof(configure), configure);
this.contractType = contractType;
ConventionBuilder builder = GetExportBuilder();
ContainerConfiguration config = configure(builder);
host = config.CreateContainer();
}
/// <summary>
/// Loads extensions from the current assembly
/// </summary>
/// <returns>ExtensionStore</returns>
public static ExtensionStore CreateDefaultLoader<T>()
{
return CreateAssemblyStore<T>(typeof(ExtensionStore).GetTypeInfo().Assembly);
}
public static ExtensionStore CreateAssemblyStore<T>(Assembly assembly)
{
Validate.IsNotNull(nameof(assembly), assembly);
return new ExtensionStore(typeof(T), (conventions) =>
new ContainerConfiguration().WithAssembly(assembly, conventions));
}
public static ExtensionStore CreateStoreForCurrentDirectory<T>()
{
string assemblyPath = typeof(ExtensionStore).GetTypeInfo().Assembly.Location;
string directory = Path.GetDirectoryName(assemblyPath);
return new ExtensionStore(typeof(T), (conventions) =>
new ContainerConfiguration().WithAssembliesInPath(directory, conventions));
}
public IEnumerable<T> GetExports<T>()
{
if (exports == null)
{
exports = host.GetExports(contractType).ToList();
}
return exports.Cast<T>();
}
private ConventionBuilder GetExportBuilder()
{
// Define exports as matching a parent type, export as that parent type
var builder = new ConventionBuilder();
builder.ForTypesDerivedFrom(contractType).Export(exportConventionBuilder => exportConventionBuilder.AsContractType(contractType));
return builder;
}
}
public static class ContainerConfigurationExtensions
{
public static ContainerConfiguration WithAssembliesInPath(this ContainerConfiguration configuration, string path, SearchOption searchOption = SearchOption.TopDirectoryOnly)
{
return WithAssembliesInPath(configuration, path, null, searchOption);
}
public static ContainerConfiguration WithAssembliesInPath(this ContainerConfiguration configuration, string path, AttributedModelProvider conventions, SearchOption searchOption = SearchOption.TopDirectoryOnly)
{
AssemblyLoadContext context = new AssemblyLoader(path);
var assemblyNames = Directory
.GetFiles(path, "*.dll", searchOption)
.Select(AssemblyLoadContext.GetAssemblyName);
var assemblies = assemblyNames
.Select(context.LoadFromAssemblyName)
.ToList();
configuration = configuration.WithAssemblies(assemblies, conventions);
return configuration;
}
}
public class AssemblyLoader : AssemblyLoadContext
{
private string folderPath;
public AssemblyLoader(string folderPath)
{
this.folderPath = folderPath;
}
protected override Assembly Load(AssemblyName assemblyName)
{
var deps = DependencyContext.Default;
var res = deps.CompileLibraries.Where(d => d.Name.Equals(assemblyName.Name)).ToList();
if (res.Count > 0)
{
return Assembly.Load(new AssemblyName(res.First().Name));
}
else
{
var apiApplicationFileInfo = new FileInfo($"{folderPath}{Path.DirectorySeparatorChar}{assemblyName.Name}.dll");
if (File.Exists(apiApplicationFileInfo.FullName))
{
var asl = new AssemblyLoader(apiApplicationFileInfo.DirectoryName);
return asl.LoadFromAssemblyPath(apiApplicationFileInfo.FullName);
}
}
return Assembly.Load(assemblyName);
}
}
}

View File

@@ -1,22 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.Extensibility
{
/// <summary>
/// A Service that expects to lookup other services. Using this interface on an exported service
/// will ensure the <see cref="SetServiceProvider(IMultiServiceProvider)"/> method is called during
/// service initialization
/// </summary>
public interface IComposableService
{
/// <summary>
/// Supports settings the service provider being used to initialize the service.
/// This is useful to look up other services and use them in your own service.
/// </summary>
void SetServiceProvider(IMultiServiceProvider provider);
}
}

View File

@@ -1,18 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
namespace Microsoft.SqlTools.ServiceLayer.Extensibility
{
internal static class IEnumerableExt
{
public static IEnumerable<T> SingleItemAsEnumerable<T>(this T item)
{
yield return item;
}
}
}

View File

@@ -1,104 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Extensibility
{
public interface IMultiServiceProvider
{
/// <summary>
/// Gets a service of a specific type. It is expected that only 1 instance of this type will be
/// available
/// </summary>
/// <typeparam name="T">Type of service to be found</typeparam>
/// <returns>Instance of T or null if not found</returns>
/// <exception cref="InvalidOperationException">The input sequence contains more than one element.-or-The input sequence is empty.</exception>
T GetService<T>();
/// <summary>
/// Gets a service of a specific type. The first service matching the specified filter will be returned
/// available
/// </summary>
/// <typeparam name="T">Type of service to be found</typeparam>
/// <param name="filter">Filter to use in </param>
/// <returns>Instance of T or null if not found</returns>
/// <exception cref="InvalidOperationException">The input sequence contains more than one element.-or-The input sequence is empty.</exception>
T GetService<T>(Predicate<T> filter);
/// <summary>
/// Gets multiple services of a given type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>An enumerable of matching services</returns>
IEnumerable<T> GetServices<T>();
/// <summary>
/// Gets multiple services of a given type, where they match a filter
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="filter"></param>
/// <returns></returns>
IEnumerable<T> GetServices<T>(Predicate<T> filter);
}
public abstract class ServiceProviderBase : IMultiServiceProvider
{
public T GetService<T>()
{
return GetServices<T>().SingleOrDefault();
}
public T GetService<T>(Predicate<T> filter)
{
Validate.IsNotNull(nameof(filter), filter);
return GetServices<T>().Where(t => filter(t)).SingleOrDefault();
}
public IEnumerable<T> GetServices<T>(Predicate<T> filter)
{
Validate.IsNotNull(nameof(filter), filter);
return GetServices<T>().Where(t => filter(t));
}
public virtual IEnumerable<T> GetServices<T>()
{
var services = GetServicesImpl<T>();
if (services == null)
{
return Enumerable.Empty<T>();
}
return services.Select(t =>
{
InitComposableService(t);
return t;
});
}
private void InitComposableService<T>(T t)
{
IComposableService c = t as IComposableService;
if (c != null)
{
c.SetServiceProvider(this);
}
}
/// <summary>
/// Gets all services using the build in implementation
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
protected abstract IEnumerable<T> GetServicesImpl<T>();
}
}

View File

@@ -1,110 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Extensibility
{
/// <summary>
/// A service provider implementation that allows registering of specific services
/// </summary>
public class RegisteredServiceProvider : ServiceProviderBase
{
public delegate IEnumerable ServiceLookup();
protected Dictionary<Type, ServiceLookup> services = new Dictionary<Type, ServiceLookup>();
/// <summary>
/// Registers a singular service to be returned during lookup
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>this provider, to simplify fluent declarations</returns>
/// <exception cref="ArgumentNullException">If service is null</exception>
/// <exception cref="InvalidOperationException">If an existing service is already registered</exception>
public RegisteredServiceProvider RegisterSingleService<T>(T service)
{
Validate.IsNotNull(nameof(service), service);
ThrowIfAlreadyRegistered<T>();
services.Add(typeof(T), () => service.SingleItemAsEnumerable());
return this;
}
/// <summary>
/// Registers a singular service to be returned during lookup
/// </summary>
/// <param name="type">
/// Type or interface this service should be registed as. Any <see cref="IMultiServiceProvider.GetServices{T}"/> request
/// for that type will return this service
/// </param>
/// <param name="service">service object to be added</param>
/// <returns>this provider, to simplify fluent declarations</returns>
/// <exception cref="ArgumentNullException">If service is null</exception>
/// <exception cref="InvalidOperationException">If an existing service is already registered</exception>
public RegisteredServiceProvider RegisterSingleService(Type type, object service)
{
Validate.IsNotNull(nameof(type), type);
Validate.IsNotNull(nameof(service), service);
ThrowIfAlreadyRegistered(type);
ThrowIfIncompatible(type, service);
services.Add(type, () => service.SingleItemAsEnumerable());
return this;
}
/// <summary>
/// Registers a function that can look up multiple services
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>this provider, to simplify fluent declarations</returns>
/// <exception cref="ArgumentNullException">If <paramref name="serviceLookup"/> is null</exception>
/// <exception cref="InvalidOperationException">If an existing service is already registered</exception>
public RegisteredServiceProvider Register<T>(Func<IEnumerable<T>> serviceLookup)
{
Validate.IsNotNull(nameof(serviceLookup), serviceLookup);
ThrowIfAlreadyRegistered<T>();
services.Add(typeof(T), () => serviceLookup());
return this;
}
private void ThrowIfAlreadyRegistered<T>()
{
ThrowIfAlreadyRegistered(typeof(T));
}
private void ThrowIfAlreadyRegistered(Type type)
{
if (services.ContainsKey(type))
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.ServiceAlreadyRegistered, type.Name));
}
}
private void ThrowIfIncompatible(Type type, object service)
{
if (!type.IsInstanceOfType(service))
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.ServiceNotOfExpectedType, service.GetType().Name, type.Name));
}
}
protected override IEnumerable<T> GetServicesImpl<T>()
{
ServiceLookup serviceLookup;
if (services.TryGetValue(typeof(T), out serviceLookup))
{
return serviceLookup().Cast<T>();
}
return Enumerable.Empty<T>();
}
}
}

View File

@@ -1,18 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Contracts
{
/// <summary>
/// Defines a class that describes the capabilities of a language
/// client. At this time no specific capabilities are listed for
/// clients.
/// </summary>
public class ClientCapabilities
{
}
}

View File

@@ -1,28 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Contracts
{
/// <summary>
/// Parameters to be used for reporting hosting-level errors, such as protocol violations
/// </summary>
public class HostingErrorParams
{
/// <summary>
/// The message of the error
/// </summary>
public string Message { get; set; }
}
public class HostingErrorEvent
{
public static readonly
EventType<HostingErrorParams> Type =
EventType<HostingErrorParams>.Create("hosting/error");
}
}

View File

@@ -1,46 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Contracts
{
public class InitializeRequest
{
public static readonly
RequestType<InitializeRequest, InitializeResult> Type =
RequestType<InitializeRequest, InitializeResult>.Create("initialize");
/// <summary>
/// Gets or sets the root path of the editor's open workspace.
/// If null it is assumed that a file was opened without having
/// a workspace open.
/// </summary>
public string RootPath { get; set; }
/// <summary>
/// Gets or sets the capabilities provided by the client (editor).
/// </summary>
public ClientCapabilities Capabilities { get; set; }
}
public class InitializeResult
{
/// <summary>
/// Gets or sets the capabilities provided by the language server.
/// </summary>
public ServerCapabilities Capabilities { get; set; }
}
public class InitializeError
{
/// <summary>
/// Gets or sets a boolean indicating whether the client should retry
/// sending the Initialize request after showing the error to the user.
/// </summary>
public bool Retry { get; set;}
}
}

View File

@@ -1,67 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Contracts
{
public class ServerCapabilities
{
public TextDocumentSyncKind? TextDocumentSync { get; set; }
public bool? HoverProvider { get; set; }
public CompletionOptions CompletionProvider { get; set; }
public SignatureHelpOptions SignatureHelpProvider { get; set; }
public bool? DefinitionProvider { get; set; }
public bool? ReferencesProvider { get; set; }
public bool? DocumentHighlightProvider { get; set; }
public bool? DocumentFormattingProvider { get; set; }
public bool? DocumentRangeFormattingProvider { get; set; }
public bool? DocumentSymbolProvider { get; set; }
public bool? WorkspaceSymbolProvider { get; set; }
}
/// <summary>
/// Defines the document synchronization strategies that a server may support.
/// </summary>
public enum TextDocumentSyncKind
{
/// <summary>
/// Indicates that documents should not be synced at all.
/// </summary>
None = 0,
/// <summary>
/// Indicates that document changes are always sent with the full content.
/// </summary>
Full,
/// <summary>
/// Indicates that document changes are sent as incremental changes after
/// the initial document content has been sent.
/// </summary>
Incremental
}
public class CompletionOptions
{
public bool? ResolveProvider { get; set; }
public string[] TriggerCharacters { get; set; }
}
public class SignatureHelpOptions
{
public string[] TriggerCharacters { get; set; }
}
}

View File

@@ -1,32 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Contracts
{
/// <summary>
/// Defines a message that is sent from the client to request
/// that the server shut down.
/// </summary>
public class ShutdownRequest
{
public static readonly
RequestType<object, object> Type =
RequestType<object, object>.Create("shutdown");
}
/// <summary>
/// Defines an event that is sent from the client to notify that
/// the client is exiting and the server should as well.
/// </summary>
public class ExitNotification
{
public static readonly
EventType<object> Type =
EventType<object>.Create("exit");
}
}

View File

@@ -1,20 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Contracts
{
/// <summary>
/// Defines a message that is sent from the client to request
/// the version of the server.
/// </summary>
public class VersionRequest
{
public static readonly
RequestType<object, string> Type =
RequestType<object, string>.Create("version");
}
}

View File

@@ -1,71 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using Microsoft.SqlTools.ServiceLayer.Extensibility;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
namespace Microsoft.SqlTools.ServiceLayer.Hosting
{
/// <summary>
/// Defines a hosted service that communicates with external processes via
/// messages passed over the <see cref="ServiceHost"/>. The service defines
/// a standard initialization method where it can hook up to the host.
/// </summary>
public interface IHostedService
{
/// <summary>
/// Callback to initialize this service
/// </summary>
/// <param name="serviceHost"><see cref="IProtocolEndpoint"/> which supports registering
/// event handlers and other callbacks for messages passed to external callers</param>
void InitializeService(IProtocolEndpoint serviceHost);
/// <summary>
/// What is the service type that you wish to register?
/// </summary>
Type ServiceType { get; }
}
/// <summary>
/// Base class for <see cref="IHostedService"/> implementations that handles defining the <see cref="ServiceType"/>
/// being registered. This simplifies service registration. This also implements <see cref="IComposableService"/> which
/// allows injection of the service provider for lookup of other services.
///
/// Extending classes should implement per below code example
/// <code>
/// [Export(typeof(IHostedService)]
/// MyService : HostedService&lt;MyService&gt;
/// {
/// public override void InitializeService(IProtocolEndpoint serviceHost)
/// {
/// serviceHost.SetRequestHandler(MyRequest.Type, HandleMyRequest);
/// }
/// }
/// </code>
/// </summary>
/// <typeparam name="T">Type to be registered for lookup in the service provider</typeparam>
public abstract class HostedService<T> : IHostedService, IComposableService
{
protected IMultiServiceProvider ServiceProvider { get; private set; }
public void SetServiceProvider(IMultiServiceProvider provider)
{
ServiceProvider = provider;
}
public Type ServiceType
{
get
{
return typeof(T);
}
}
public abstract void InitializeService(IProtocolEndpoint serviceHost);
}
}

View File

@@ -1,81 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel
{
/// <summary>
/// Defines a base implementation for servers and their clients over a
/// single kind of communication channel.
/// </summary>
public abstract class ChannelBase
{
/// <summary>
/// Gets a boolean that is true if the channel is connected or false if not.
/// </summary>
public bool IsConnected { get; protected set; }
/// <summary>
/// Gets the MessageReader for reading messages from the channel.
/// </summary>
public MessageReader MessageReader { get; protected set; }
/// <summary>
/// Gets the MessageWriter for writing messages to the channel.
/// </summary>
public MessageWriter MessageWriter { get; protected set; }
/// <summary>
/// Starts the channel and initializes the MessageDispatcher.
/// </summary>
/// <param name="messageProtocolType">The type of message protocol used by the channel.</param>
public void Start(MessageProtocolType messageProtocolType)
{
IMessageSerializer messageSerializer = null;
if (messageProtocolType == MessageProtocolType.LanguageServer)
{
messageSerializer = new JsonRpcMessageSerializer();
}
else
{
messageSerializer = new V8MessageSerializer();
}
this.Initialize(messageSerializer);
}
/// <summary>
/// Returns a Task that allows the consumer of the ChannelBase
/// implementation to wait until a connection has been made to
/// the opposite endpoint whether it's a client or server.
/// </summary>
/// <returns>A Task to be awaited until a connection is made.</returns>
public abstract Task WaitForConnection();
/// <summary>
/// Stops the channel.
/// </summary>
public void Stop()
{
this.Shutdown();
}
/// <summary>
/// A method to be implemented by subclasses to handle the
/// actual initialization of the channel and the creation and
/// assignment of the MessageReader and MessageWriter properties.
/// </summary>
/// <param name="messageSerializer">The IMessageSerializer to use for message serialization.</param>
protected abstract void Initialize(IMessageSerializer messageSerializer);
/// <summary>
/// A method to be implemented by subclasses to handle shutdown
/// of the channel once Stop is called.
/// </summary>
protected abstract void Shutdown();
}
}

View File

@@ -1,126 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel
{
/// <summary>
/// Provides a client implementation for the standard I/O channel.
/// Launches the server process and then attaches to its console
/// streams.
/// </summary>
public class StdioClientChannel : ChannelBase
{
private string serviceProcessPath;
private string serviceProcessArguments;
private Stream inputStream;
private Stream outputStream;
private Process serviceProcess;
/// <summary>
/// Gets the process ID of the server process.
/// </summary>
public int ProcessId { get; private set; }
/// <summary>
/// Initializes an instance of the StdioClient.
/// </summary>
/// <param name="serverProcessPath">The full path to the server process executable.</param>
/// <param name="serverProcessArguments">Optional arguments to pass to the service process executable.</param>
public StdioClientChannel(
string serverProcessPath,
params string[] serverProcessArguments)
{
this.serviceProcessPath = serverProcessPath;
if (serverProcessArguments != null)
{
this.serviceProcessArguments =
string.Join(
" ",
serverProcessArguments);
}
}
protected override void Initialize(IMessageSerializer messageSerializer)
{
this.serviceProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = this.serviceProcessPath,
Arguments = this.serviceProcessArguments,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
StandardOutputEncoding = Encoding.UTF8,
},
EnableRaisingEvents = true,
};
// Start the process
this.serviceProcess.Start();
this.ProcessId = this.serviceProcess.Id;
// Open the standard input/output streams
this.inputStream = this.serviceProcess.StandardOutput.BaseStream;
this.outputStream = this.serviceProcess.StandardInput.BaseStream;
// Set up the message reader and writer
this.MessageReader =
new MessageReader(
this.inputStream,
messageSerializer);
this.MessageWriter =
new MessageWriter(
this.outputStream,
messageSerializer);
this.IsConnected = true;
}
public override Task WaitForConnection()
{
// We're always connected immediately in the stdio channel
return Task.FromResult(true);
}
protected override void Shutdown()
{
if (this.inputStream != null)
{
this.inputStream.Dispose();
this.inputStream = null;
}
if (this.outputStream != null)
{
this.outputStream.Dispose();
this.outputStream = null;
}
if (this.MessageReader != null)
{
this.MessageReader = null;
}
if (this.MessageWriter != null)
{
this.MessageWriter = null;
}
this.serviceProcess.Kill();
}
}
}

View File

@@ -1,61 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel
{
/// <summary>
/// Provides a server implementation for the standard I/O channel.
/// When started in a process, attaches to the console I/O streams
/// to communicate with the client that launched the process.
/// </summary>
public class StdioServerChannel : ChannelBase
{
private Stream inputStream;
private Stream outputStream;
protected override void Initialize(IMessageSerializer messageSerializer)
{
#if !NanoServer
// Ensure that the console is using UTF-8 encoding
System.Console.InputEncoding = Encoding.UTF8;
System.Console.OutputEncoding = Encoding.UTF8;
#endif
// Open the standard input/output streams
this.inputStream = System.Console.OpenStandardInput();
this.outputStream = System.Console.OpenStandardOutput();
// Set up the reader and writer
this.MessageReader =
new MessageReader(
this.inputStream,
messageSerializer);
this.MessageWriter =
new MessageWriter(
this.outputStream,
messageSerializer);
this.IsConnected = true;
}
public override Task WaitForConnection()
{
// We're always connected immediately in the stdio channel
return Task.FromResult(true);
}
protected override void Shutdown()
{
// No default implementation needed, streams will be
// disposed on process shutdown.
}
}
}

View File

@@ -1,27 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
{
public static class Constants
{
public const string ContentLengthFormatString = "Content-Length: {0}\r\n\r\n";
public static readonly JsonSerializerSettings JsonSerializerSettings;
public static readonly string SqlLoginAuthenticationType = "SqlLogin";
static Constants()
{
JsonSerializerSettings = new JsonSerializerSettings();
// Camel case all object properties
JsonSerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();
}
}
}

View File

@@ -1,33 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts
{
/// <summary>
/// Defines an event type with a particular method name.
/// </summary>
/// <typeparam name="TParams">The parameter type for this event.</typeparam>
public class EventType<TParams>
{
/// <summary>
/// Gets the method name for the event type.
/// </summary>
public string MethodName { get; private set; }
/// <summary>
/// Creates an EventType instance with the given parameter type and method name.
/// </summary>
/// <param name="methodName">The method name of the event.</param>
/// <returns>A new EventType instance for the defined type.</returns>
public static EventType<TParams> Create(string methodName)
{
return new EventType<TParams>()
{
MethodName = methodName
};
}
}
}

View File

@@ -1,136 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Diagnostics;
using Newtonsoft.Json.Linq;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts
{
/// <summary>
/// Defines all possible message types.
/// </summary>
public enum MessageType
{
Unknown,
Request,
Response,
Event
}
/// <summary>
/// Provides common details for protocol messages of any format.
/// </summary>
[DebuggerDisplay("MessageType = {MessageType.ToString()}, Method = {Method}, Id = {Id}")]
public class Message
{
/// <summary>
/// Gets or sets the message type.
/// </summary>
public MessageType MessageType { get; set; }
/// <summary>
/// Gets or sets the message's sequence ID.
/// </summary>
public string Id { get; set; }
/// <summary>
/// Gets or sets the message's method/command name.
/// </summary>
public string Method { get; set; }
/// <summary>
/// Gets or sets a JToken containing the contents of the message.
/// </summary>
public JToken Contents { get; set; }
/// <summary>
/// Gets or sets a JToken containing error details.
/// </summary>
public JToken Error { get; set; }
/// <summary>
/// Creates a message with an Unknown type.
/// </summary>
/// <returns>A message with Unknown type.</returns>
public static Message Unknown()
{
return new Message
{
MessageType = MessageType.Unknown
};
}
/// <summary>
/// Creates a message with a Request type.
/// </summary>
/// <param name="id">The sequence ID of the request.</param>
/// <param name="method">The method name of the request.</param>
/// <param name="contents">The contents of the request.</param>
/// <returns>A message with a Request type.</returns>
public static Message Request(string id, string method, JToken contents)
{
return new Message
{
MessageType = MessageType.Request,
Id = id,
Method = method,
Contents = contents
};
}
/// <summary>
/// Creates a message with a Response type.
/// </summary>
/// <param name="id">The sequence ID of the original request.</param>
/// <param name="method">The method name of the original request.</param>
/// <param name="contents">The contents of the response.</param>
/// <returns>A message with a Response type.</returns>
public static Message Response(string id, string method, JToken contents)
{
return new Message
{
MessageType = MessageType.Response,
Id = id,
Method = method,
Contents = contents
};
}
/// <summary>
/// Creates a message with a Response type and error details.
/// </summary>
/// <param name="id">The sequence ID of the original request.</param>
/// <param name="method">The method name of the original request.</param>
/// <param name="error">The error details of the response.</param>
/// <returns>A message with a Response type and error details.</returns>
public static Message ResponseError(string id, string method, JToken error)
{
return new Message
{
MessageType = MessageType.Response,
Id = id,
Method = method,
Error = error
};
}
/// <summary>
/// Creates a message with an Event type.
/// </summary>
/// <param name="method">The method name of the event.</param>
/// <param name="contents">The contents of the event.</param>
/// <returns>A message with an Event type.</returns>
public static Message Event(string method, JToken contents)
{
return new Message
{
MessageType = MessageType.Event,
Method = method,
Contents = contents
};
}
}
}

View File

@@ -1,24 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Diagnostics;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts
{
[DebuggerDisplay("RequestType MethodName = {MethodName}")]
public class RequestType<TParams, TResult>
{
public string MethodName { get; private set; }
public static RequestType<TParams, TResult> Create(string typeName)
{
return new RequestType<TParams, TResult>()
{
MethodName = typeName
};
}
}
}

View File

@@ -1,39 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
{
/// <summary>
/// Provides context for a received event so that handlers
/// can write events back to the channel.
/// </summary>
public class EventContext
{
private readonly MessageWriter messageWriter;
/// <summary>
/// Parameterless constructor required for mocking
/// </summary>
public EventContext() { }
public EventContext(MessageWriter messageWriter)
{
this.messageWriter = messageWriter;
}
public async Task SendEvent<TParams>(
EventType<TParams> eventType,
TParams eventParams)
{
await this.messageWriter.WriteEvent(
eventType,
eventParams);
}
}
}

View File

@@ -1,15 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
{
public interface IEventSender
{
Task SendEvent<TParams>(EventType<TParams> eventType, TParams eventParams);
}
}

View File

@@ -1,32 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
{
/// <summary>
/// A ProtocolEndpoint is used for inter-process communication. Services can register to
/// respond to requests and events, send their own requests, and listen for notifications
/// sent by the other side of the endpoint
/// </summary>
public interface IProtocolEndpoint : IEventSender, IRequestSender
{
void SetRequestHandler<TParams, TResult>(
RequestType<TParams, TResult> requestType,
Func<TParams, RequestContext<TResult>, Task> requestHandler);
void SetEventHandler<TParams>(
EventType<TParams> eventType,
Func<TParams, EventContext, Task> eventHandler);
void SetEventHandler<TParams>(
EventType<TParams> eventType,
Func<TParams, EventContext, Task> eventHandler,
bool overrideExisting);
}
}

View File

@@ -1,16 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
{
public interface IRequestSender
{
Task<TResult> SendRequest<TParams, TResult>(RequestType<TParams, TResult> requestType, TParams requestParams,
bool waitForResponse);
}
}

View File

@@ -1,337 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Contracts;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
{
public class MessageDispatcher
{
#region Fields
private ChannelBase protocolChannel;
private AsyncContextThread messageLoopThread;
internal Dictionary<string, Func<Message, MessageWriter, Task>> requestHandlers =
new Dictionary<string, Func<Message, MessageWriter, Task>>();
internal Dictionary<string, Func<Message, MessageWriter, Task>> eventHandlers =
new Dictionary<string, Func<Message, MessageWriter, Task>>();
private Action<Message> responseHandler;
private CancellationTokenSource messageLoopCancellationToken =
new CancellationTokenSource();
#endregion
#region Properties
public SynchronizationContext SynchronizationContext { get; private set; }
public bool InMessageLoopThread
{
get
{
// We're in the same thread as the message loop if the
// current synchronization context equals the one we
// know.
return SynchronizationContext.Current == this.SynchronizationContext;
}
}
protected MessageReader MessageReader { get; private set; }
protected MessageWriter MessageWriter { get; private set; }
#endregion
#region Constructors
public MessageDispatcher(ChannelBase protocolChannel)
{
this.protocolChannel = protocolChannel;
this.MessageReader = protocolChannel.MessageReader;
this.MessageWriter = protocolChannel.MessageWriter;
}
#endregion
#region Public Methods
public void Start()
{
// Start the main message loop thread. The Task is
// not explicitly awaited because it is running on
// an independent background thread.
this.messageLoopThread = new AsyncContextThread("Message Dispatcher");
this.messageLoopThread
.Run(() => this.ListenForMessages(this.messageLoopCancellationToken.Token))
.ContinueWith(this.OnListenTaskCompleted);
}
public void Stop()
{
// Stop the message loop thread
if (this.messageLoopThread != null)
{
this.messageLoopCancellationToken.Cancel();
this.messageLoopThread.Stop();
}
}
public void SetRequestHandler<TParams, TResult>(
RequestType<TParams, TResult> requestType,
Func<TParams, RequestContext<TResult>, Task> requestHandler)
{
this.SetRequestHandler(
requestType,
requestHandler,
false);
}
public void SetRequestHandler<TParams, TResult>(
RequestType<TParams, TResult> requestType,
Func<TParams, RequestContext<TResult>, Task> requestHandler,
bool overrideExisting)
{
if (overrideExisting)
{
// Remove the existing handler so a new one can be set
this.requestHandlers.Remove(requestType.MethodName);
}
this.requestHandlers.Add(
requestType.MethodName,
(requestMessage, messageWriter) =>
{
var requestContext =
new RequestContext<TResult>(
requestMessage,
messageWriter);
TParams typedParams = default(TParams);
if (requestMessage.Contents != null)
{
// TODO: Catch parse errors!
typedParams = requestMessage.Contents.ToObject<TParams>();
}
return requestHandler(typedParams, requestContext);
});
}
public void SetEventHandler<TParams>(
EventType<TParams> eventType,
Func<TParams, EventContext, Task> eventHandler)
{
this.SetEventHandler(
eventType,
eventHandler,
false);
}
public void SetEventHandler<TParams>(
EventType<TParams> eventType,
Func<TParams, EventContext, Task> eventHandler,
bool overrideExisting)
{
if (overrideExisting)
{
// Remove the existing handler so a new one can be set
this.eventHandlers.Remove(eventType.MethodName);
}
this.eventHandlers.Add(
eventType.MethodName,
(eventMessage, messageWriter) =>
{
var eventContext = new EventContext(messageWriter);
TParams typedParams = default(TParams);
if (eventMessage.Contents != null)
{
try
{
typedParams = eventMessage.Contents.ToObject<TParams>();
}
catch (Exception ex)
{
Logger.Write(LogLevel.Verbose, ex.ToString());
}
}
return eventHandler(typedParams, eventContext);
});
}
public void SetResponseHandler(Action<Message> responseHandler)
{
this.responseHandler = responseHandler;
}
#endregion
#region Events
public event EventHandler<Exception> UnhandledException;
protected void OnUnhandledException(Exception unhandledException)
{
if (this.UnhandledException != null)
{
this.UnhandledException(this, unhandledException);
}
}
#endregion
#region Private Methods
private async Task ListenForMessages(CancellationToken cancellationToken)
{
this.SynchronizationContext = SynchronizationContext.Current;
// Run the message loop
while (!cancellationToken.IsCancellationRequested)
{
Message newMessage;
try
{
// Read a message from the channel
newMessage = await this.MessageReader.ReadMessage();
}
catch (MessageParseException e)
{
string message = string.Format("Exception occurred while parsing message: {0}", e.Message);
Logger.Write(LogLevel.Error, message);
await MessageWriter.WriteEvent(HostingErrorEvent.Type, new HostingErrorParams { Message = message });
// Continue the loop
continue;
}
catch (EndOfStreamException)
{
// The stream has ended, end the message loop
break;
}
catch (Exception e)
{
// Log the error and send an error event to the client
string message = string.Format("Exception occurred while receiving message: {0}", e.Message);
Logger.Write(LogLevel.Error, message);
await MessageWriter.WriteEvent(HostingErrorEvent.Type, new HostingErrorParams { Message = message });
// Continue the loop
continue;
}
// The message could be null if there was an error parsing the
// previous message. In this case, do not try to dispatch it.
if (newMessage != null)
{
// Verbose logging
string logMessage = string.Format("Received message of type[{0}] and method[{1}]",
newMessage.MessageType, newMessage.Method);
Logger.Write(LogLevel.Verbose, logMessage);
// Process the message
await this.DispatchMessage(newMessage, this.MessageWriter);
}
}
}
protected async Task DispatchMessage(
Message messageToDispatch,
MessageWriter messageWriter)
{
Task handlerToAwait = null;
if (messageToDispatch.MessageType == MessageType.Request)
{
Func<Message, MessageWriter, Task> requestHandler = null;
if (this.requestHandlers.TryGetValue(messageToDispatch.Method, out requestHandler))
{
handlerToAwait = requestHandler(messageToDispatch, messageWriter);
}
// else
// {
// // TODO: Message not supported error
// }
}
else if (messageToDispatch.MessageType == MessageType.Response)
{
if (this.responseHandler != null)
{
this.responseHandler(messageToDispatch);
}
}
else if (messageToDispatch.MessageType == MessageType.Event)
{
Func<Message, MessageWriter, Task> eventHandler = null;
if (this.eventHandlers.TryGetValue(messageToDispatch.Method, out eventHandler))
{
handlerToAwait = eventHandler(messageToDispatch, messageWriter);
}
else
{
// TODO: Message not supported error
}
}
// else
// {
// // TODO: Return message not supported
// }
if (handlerToAwait != null)
{
try
{
await handlerToAwait;
}
catch (TaskCanceledException)
{
// Some tasks may be cancelled due to legitimate
// timeouts so don't let those exceptions go higher.
}
catch (AggregateException e)
{
if (!(e.InnerExceptions[0] is TaskCanceledException))
{
// Cancelled tasks aren't a problem, so rethrow
// anything that isn't a TaskCanceledException
throw e;
}
}
}
}
internal void OnListenTaskCompleted(Task listenTask)
{
if (listenTask.IsFaulted)
{
this.OnUnhandledException(listenTask.Exception);
}
else if (listenTask.IsCompleted || listenTask.IsCanceled)
{
// TODO: Dispose of anything?
}
}
#endregion
}
}

View File

@@ -1,23 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
{
public class MessageParseException : Exception
{
public string OriginalMessageText { get; private set; }
public MessageParseException(
string originalMessageText,
string errorMessage,
params object[] errorMessageArgs)
: base(string.Format(errorMessage, errorMessageArgs))
{
this.OriginalMessageText = originalMessageText;
}
}
}

View File

@@ -1,23 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
{
/// <summary>
/// Defines the possible message protocol types.
/// </summary>
public enum MessageProtocolType
{
/// <summary>
/// Identifies the language server message protocol.
/// </summary>
LanguageServer,
/// <summary>
/// Identifies the debug adapter message protocol.
/// </summary>
DebugAdapter
}
}

View File

@@ -1,272 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers;
using Microsoft.SqlTools.ServiceLayer.Utility;
using Newtonsoft.Json.Linq;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
{
public class MessageReader
{
#region Private Fields
public const int DefaultBufferSize = 8192;
public const double BufferResizeTrigger = 0.25;
private const int CR = 0x0D;
private const int LF = 0x0A;
private static readonly string[] NewLineDelimiters = { Environment.NewLine };
private readonly Stream inputStream;
private readonly IMessageSerializer messageSerializer;
private readonly Encoding messageEncoding;
private ReadState readState;
private bool needsMoreData = true;
private int readOffset;
private int bufferEndOffset;
private byte[] messageBuffer;
private int expectedContentLength;
private Dictionary<string, string> messageHeaders;
private enum ReadState
{
Headers,
Content
}
#endregion
#region Constructors
public MessageReader(
Stream inputStream,
IMessageSerializer messageSerializer,
Encoding messageEncoding = null)
{
Validate.IsNotNull("streamReader", inputStream);
Validate.IsNotNull("messageSerializer", messageSerializer);
this.inputStream = inputStream;
this.messageSerializer = messageSerializer;
this.messageEncoding = messageEncoding;
if (messageEncoding == null)
{
this.messageEncoding = Encoding.UTF8;
}
this.messageBuffer = new byte[DefaultBufferSize];
}
#endregion
#region Public Methods
public async Task<Message> ReadMessage()
{
string messageContent = null;
// Do we need to read more data or can we process the existing buffer?
while (!this.needsMoreData || await this.ReadNextChunk())
{
// Clear the flag since we should have what we need now
this.needsMoreData = false;
// Do we need to look for message headers?
if (this.readState == ReadState.Headers &&
!this.TryReadMessageHeaders())
{
// If we don't have enough data to read headers yet, keep reading
this.needsMoreData = true;
continue;
}
// Do we need to look for message content?
if (this.readState == ReadState.Content &&
!this.TryReadMessageContent(out messageContent))
{
// If we don't have enough data yet to construct the content, keep reading
this.needsMoreData = true;
continue;
}
// We've read a message now, break out of the loop
break;
}
// Now that we have a message, reset the buffer's state
ShiftBufferBytesAndShrink(readOffset);
// Get the JObject for the JSON content
JObject messageObject = JObject.Parse(messageContent);
// Return the parsed message
return this.messageSerializer.DeserializeMessage(messageObject);
}
#endregion
#region Private Methods
private async Task<bool> ReadNextChunk()
{
// Do we need to resize the buffer? See if less than 1/4 of the space is left.
if (((double)(this.messageBuffer.Length - this.bufferEndOffset) / this.messageBuffer.Length) < 0.25)
{
// Double the size of the buffer
Array.Resize(
ref this.messageBuffer,
this.messageBuffer.Length * 2);
}
// Read the next chunk into the message buffer
int readLength =
await this.inputStream.ReadAsync(
this.messageBuffer,
this.bufferEndOffset,
this.messageBuffer.Length - this.bufferEndOffset);
this.bufferEndOffset += readLength;
if (readLength == 0)
{
// If ReadAsync returns 0 then it means that the stream was
// closed unexpectedly (usually due to the client application
// ending suddenly). For now, just terminate the language
// server immediately.
// TODO: Provide a more graceful shutdown path
throw new EndOfStreamException(SR.HostingUnexpectedEndOfStream);
}
return true;
}
private bool TryReadMessageHeaders()
{
int scanOffset = this.readOffset;
// Scan for the final double-newline that marks the end of the header lines
while (scanOffset + 3 < this.bufferEndOffset &&
(this.messageBuffer[scanOffset] != CR ||
this.messageBuffer[scanOffset + 1] != LF ||
this.messageBuffer[scanOffset + 2] != CR ||
this.messageBuffer[scanOffset + 3] != LF))
{
scanOffset++;
}
// Make sure we haven't reached the end of the buffer without finding a separator (e.g CRLFCRLF)
if (scanOffset + 3 >= this.bufferEndOffset)
{
return false;
}
// Convert the header block into a array of lines
var headers = Encoding.ASCII.GetString(this.messageBuffer, this.readOffset, scanOffset)
.Split(NewLineDelimiters, StringSplitOptions.RemoveEmptyEntries);
try
{
// Read each header and store it in the dictionary
this.messageHeaders = new Dictionary<string, string>();
foreach (var header in headers)
{
int currentLength = header.IndexOf(':');
if (currentLength == -1)
{
throw new ArgumentException(SR.HostingHeaderMissingColon);
}
var key = header.Substring(0, currentLength);
var value = header.Substring(currentLength + 1).Trim();
this.messageHeaders[key] = value;
}
// Parse out the content length as an int
string contentLengthString;
if (!this.messageHeaders.TryGetValue("Content-Length", out contentLengthString))
{
throw new MessageParseException("", SR.HostingHeaderMissingContentLengthHeader);
}
// Parse the content length to an integer
if (!int.TryParse(contentLengthString, out this.expectedContentLength))
{
throw new MessageParseException("", SR.HostingHeaderMissingContentLengthValue);
}
}
catch (Exception)
{
// The content length was invalid or missing. Trash the buffer we've read
ShiftBufferBytesAndShrink(scanOffset + 4);
throw;
}
// Skip past the headers plus the newline characters
this.readOffset += scanOffset + 4;
// Done reading headers, now read content
this.readState = ReadState.Content;
return true;
}
private bool TryReadMessageContent(out string messageContent)
{
messageContent = null;
// Do we have enough bytes to reach the expected length?
if ((this.bufferEndOffset - this.readOffset) < this.expectedContentLength)
{
return false;
}
// Convert the message contents to a string using the specified encoding
messageContent = this.messageEncoding.GetString(
this.messageBuffer,
this.readOffset,
this.expectedContentLength);
readOffset += expectedContentLength;
// Done reading content, now look for headers for the next message
this.readState = ReadState.Headers;
return true;
}
private void ShiftBufferBytesAndShrink(int bytesToRemove)
{
// Create a new buffer that is shrunken by the number of bytes to remove
// Note: by using Max, we can guarantee a buffer of at least default buffer size
byte[] newBuffer = new byte[Math.Max(messageBuffer.Length - bytesToRemove, DefaultBufferSize)];
// If we need to do shifting, do the shifting
if (bytesToRemove <= messageBuffer.Length)
{
// Copy the existing buffer starting at the offset to remove
Buffer.BlockCopy(messageBuffer, bytesToRemove, newBuffer, 0, bufferEndOffset - bytesToRemove);
}
// Make the new buffer the message buffer
messageBuffer = newBuffer;
// Reset the read offset and the end offset
readOffset = 0;
bufferEndOffset -= bytesToRemove;
}
#endregion
}
}

View File

@@ -1,142 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers;
using Microsoft.SqlTools.ServiceLayer.Utility;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
{
public class MessageWriter
{
#region Private Fields
private Stream outputStream;
private IMessageSerializer messageSerializer;
private AsyncLock writeLock = new AsyncLock();
private JsonSerializer contentSerializer =
JsonSerializer.Create(
Constants.JsonSerializerSettings);
#endregion
#region Constructors
public MessageWriter(
Stream outputStream,
IMessageSerializer messageSerializer)
{
Validate.IsNotNull("streamWriter", outputStream);
Validate.IsNotNull("messageSerializer", messageSerializer);
this.outputStream = outputStream;
this.messageSerializer = messageSerializer;
}
#endregion
#region Public Methods
// TODO: This method should be made protected or private
public async Task WriteMessage(Message messageToWrite)
{
Validate.IsNotNull("messageToWrite", messageToWrite);
// Serialize the message
JObject messageObject =
this.messageSerializer.SerializeMessage(
messageToWrite);
// Log the JSON representation of the message
Logger.Write(
LogLevel.Verbose,
string.Format(
"WRITE MESSAGE:\r\n\r\n{0}",
JsonConvert.SerializeObject(
messageObject,
Formatting.Indented,
Constants.JsonSerializerSettings)));
string serializedMessage =
JsonConvert.SerializeObject(
messageObject,
Constants.JsonSerializerSettings);
byte[] messageBytes = Encoding.UTF8.GetBytes(serializedMessage);
byte[] headerBytes =
Encoding.ASCII.GetBytes(
string.Format(
Constants.ContentLengthFormatString,
messageBytes.Length));
// Make sure only one call is writing at a time. You might be thinking
// "Why not use a normal lock?" We use an AsyncLock here so that the
// message loop doesn't get blocked while waiting for I/O to complete.
using (await this.writeLock.LockAsync())
{
// Send the message
await this.outputStream.WriteAsync(headerBytes, 0, headerBytes.Length);
await this.outputStream.WriteAsync(messageBytes, 0, messageBytes.Length);
await this.outputStream.FlushAsync();
}
}
public async Task WriteRequest<TParams, TResult>(
RequestType<TParams, TResult> requestType,
TParams requestParams,
int requestId)
{
// Allow null content
JToken contentObject =
requestParams != null ?
JToken.FromObject(requestParams, contentSerializer) :
null;
await this.WriteMessage(
Message.Request(
requestId.ToString(),
requestType.MethodName,
contentObject));
}
public async Task WriteResponse<TResult>(TResult resultContent, string method, string requestId)
{
// Allow null content
JToken contentObject =
resultContent != null ?
JToken.FromObject(resultContent, contentSerializer) :
null;
await this.WriteMessage(
Message.Response(
requestId,
method,
contentObject));
}
public async Task WriteEvent<TParams>(EventType<TParams> eventType, TParams eventParams)
{
// Allow null content
JToken contentObject =
eventParams != null ?
JToken.FromObject(eventParams, contentSerializer) :
null;
await this.WriteMessage(
Message.Event(
eventType.MethodName,
contentObject));
}
#endregion
}
}

View File

@@ -1,351 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
{
/// <summary>
/// Provides behavior for a client or server endpoint that
/// communicates using the specified protocol.
/// </summary>
public class ProtocolEndpoint : IProtocolEndpoint
{
private bool isInitialized;
private bool isStarted;
private int currentMessageId;
private ChannelBase protocolChannel;
private MessageProtocolType messageProtocolType;
private TaskCompletionSource<bool> endpointExitedTask;
private SynchronizationContext originalSynchronizationContext;
private Dictionary<string, TaskCompletionSource<Message>> pendingRequests =
new Dictionary<string, TaskCompletionSource<Message>>();
/// <summary>
/// When true, SendEvent will ignore exceptions and write them
/// to the log instead. Intended to be used for test scenarios
/// where SendEvent throws exceptions unrelated to what is
/// being tested.
/// </summary>
internal static bool SendEventIgnoreExceptions = false;
/// <summary>
/// Gets the MessageDispatcher which allows registration of
/// handlers for requests, responses, and events that are
/// transmitted through the channel.
/// </summary>
protected MessageDispatcher MessageDispatcher { get; set; }
/// <summary>
/// Initializes an instance of the protocol server using the
/// specified channel for communication.
/// </summary>
/// <param name="protocolChannel">
/// The channel to use for communication with the connected endpoint.
/// </param>
/// <param name="messageProtocolType">
/// The type of message protocol used by the endpoint.
/// </param>
public ProtocolEndpoint(
ChannelBase protocolChannel,
MessageProtocolType messageProtocolType)
{
this.protocolChannel = protocolChannel;
this.messageProtocolType = messageProtocolType;
this.originalSynchronizationContext = SynchronizationContext.Current;
}
/// <summary>
/// Initializes
/// </summary>
public void Initialize()
{
if (!this.isInitialized)
{
// Start the provided protocol channel
this.protocolChannel.Start(this.messageProtocolType);
// Start the message dispatcher
this.MessageDispatcher = new MessageDispatcher(this.protocolChannel);
// Set the handler for any message responses that come back
this.MessageDispatcher.SetResponseHandler(this.HandleResponse);
// Listen for unhandled exceptions from the dispatcher
this.MessageDispatcher.UnhandledException += MessageDispatcher_UnhandledException;
this.isInitialized = true;
}
}
/// <summary>
/// Starts the language server client and sends the Initialize method.
/// </summary>
/// <returns>A Task that can be awaited for initialization to complete.</returns>
public async Task Start()
{
if (!this.isStarted)
{
// Notify implementation about endpoint start
await this.OnStart();
// Wait for connection and notify the implementor
// NOTE: This task is not meant to be awaited.
Task waitTask =
this.protocolChannel
.WaitForConnection()
.ContinueWith(
async (t) =>
{
// Start the MessageDispatcher
this.MessageDispatcher.Start();
await this.OnConnect();
});
// Endpoint is now started
this.isStarted = true;
}
}
public void WaitForExit()
{
this.endpointExitedTask = new TaskCompletionSource<bool>();
this.endpointExitedTask.Task.Wait();
}
public async Task Stop()
{
if (this.isStarted)
{
// Make sure no future calls try to stop the endpoint during shutdown
this.isStarted = false;
// Stop the implementation first
await this.OnStop();
// Stop the dispatcher and channel
this.MessageDispatcher.Stop();
this.protocolChannel.Stop();
// Notify anyone waiting for exit
if (this.endpointExitedTask != null)
{
this.endpointExitedTask.SetResult(true);
}
}
}
#region Message Sending
/// <summary>
/// Sends a request to the server
/// </summary>
/// <typeparam name="TParams"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="requestType"></param>
/// <param name="requestParams"></param>
/// <returns></returns>
public Task<TResult> SendRequest<TParams, TResult>(
RequestType<TParams, TResult> requestType,
TParams requestParams)
{
return this.SendRequest(requestType, requestParams, true);
}
public async Task<TResult> SendRequest<TParams, TResult>(
RequestType<TParams, TResult> requestType,
TParams requestParams,
bool waitForResponse)
{
if (!this.protocolChannel.IsConnected)
{
throw new InvalidOperationException("SendRequest called when ProtocolChannel was not yet connected");
}
this.currentMessageId++;
TaskCompletionSource<Message> responseTask = null;
if (waitForResponse)
{
responseTask = new TaskCompletionSource<Message>();
this.pendingRequests.Add(
this.currentMessageId.ToString(),
responseTask);
}
await this.protocolChannel.MessageWriter.WriteRequest<TParams, TResult>(
requestType,
requestParams,
this.currentMessageId);
if (responseTask != null)
{
var responseMessage = await responseTask.Task;
return
responseMessage.Contents != null ?
responseMessage.Contents.ToObject<TResult>() :
default(TResult);
}
else
{
// TODO: Better default value here?
return default(TResult);
}
}
/// <summary>
/// Sends an event to the channel's endpoint.
/// </summary>
/// <typeparam name="TParams">The event parameter type.</typeparam>
/// <param name="eventType">The type of event being sent.</param>
/// <param name="eventParams">The event parameters being sent.</param>
/// <returns>A Task that tracks completion of the send operation.</returns>
public Task SendEvent<TParams>(
EventType<TParams> eventType,
TParams eventParams)
{
try
{
if (!this.protocolChannel.IsConnected)
{
throw new InvalidOperationException("SendEvent called when ProtocolChannel was not yet connected");
}
// Some events could be raised from a different thread.
// To ensure that messages are written serially, dispatch
// dispatch the SendEvent call to the message loop thread.
if (!this.MessageDispatcher.InMessageLoopThread)
{
TaskCompletionSource<bool> writeTask = new TaskCompletionSource<bool>();
this.MessageDispatcher.SynchronizationContext.Post(
async (obj) =>
{
await this.protocolChannel.MessageWriter.WriteEvent(
eventType,
eventParams);
writeTask.SetResult(true);
}, null);
return writeTask.Task;
}
else
{
return this.protocolChannel.MessageWriter.WriteEvent(
eventType,
eventParams);
}
}
catch (Exception ex)
{
if (SendEventIgnoreExceptions)
{
Logger.Write(LogLevel.Verbose, "Exception in SendEvent " + ex.ToString());
}
else
{
throw;
}
}
return Task.FromResult(false);
}
#endregion
#region Message Handling
public void SetRequestHandler<TParams, TResult>(
RequestType<TParams, TResult> requestType,
Func<TParams, RequestContext<TResult>, Task> requestHandler)
{
this.MessageDispatcher.SetRequestHandler(
requestType,
requestHandler);
}
public void SetEventHandler<TParams>(
EventType<TParams> eventType,
Func<TParams, EventContext, Task> eventHandler)
{
this.MessageDispatcher.SetEventHandler(
eventType,
eventHandler,
false);
}
public void SetEventHandler<TParams>(
EventType<TParams> eventType,
Func<TParams, EventContext, Task> eventHandler,
bool overrideExisting)
{
this.MessageDispatcher.SetEventHandler(
eventType,
eventHandler,
overrideExisting);
}
private void HandleResponse(Message responseMessage)
{
TaskCompletionSource<Message> pendingRequestTask = null;
if (this.pendingRequests.TryGetValue(responseMessage.Id, out pendingRequestTask))
{
pendingRequestTask.SetResult(responseMessage);
this.pendingRequests.Remove(responseMessage.Id);
}
}
#endregion
#region Subclass Lifetime Methods
protected virtual Task OnStart()
{
return Task.FromResult(true);
}
protected virtual Task OnConnect()
{
return Task.FromResult(true);
}
protected virtual Task OnStop()
{
return Task.FromResult(true);
}
#endregion
#region Event Handlers
private void MessageDispatcher_UnhandledException(object sender, Exception e)
{
if (this.endpointExitedTask != null)
{
this.endpointExitedTask.SetException(e);
}
else if (this.originalSynchronizationContext != null)
{
this.originalSynchronizationContext.Post(o => { throw e; }, null);
}
}
#endregion
}
}

View File

@@ -1,50 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
using Newtonsoft.Json.Linq;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol
{
public class RequestContext<TResult> : IEventSender
{
private readonly Message requestMessage;
private readonly MessageWriter messageWriter;
public RequestContext(Message requestMessage, MessageWriter messageWriter)
{
this.requestMessage = requestMessage;
this.messageWriter = messageWriter;
}
public RequestContext() { }
public virtual async Task SendResult(TResult resultDetails)
{
await this.messageWriter.WriteResponse(
resultDetails,
requestMessage.Method,
requestMessage.Id);
}
public virtual async Task SendEvent<TParams>(EventType<TParams> eventType, TParams eventParams)
{
await this.messageWriter.WriteEvent(
eventType,
eventParams);
}
public virtual async Task SendError(object errorDetails)
{
await this.messageWriter.WriteMessage(
Message.ResponseError(
requestMessage.Id,
requestMessage.Method,
JToken.FromObject(errorDetails)));
}
}
}

View File

@@ -1,31 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
using Newtonsoft.Json.Linq;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers
{
/// <summary>
/// Defines a common interface for message serializers.
/// </summary>
public interface IMessageSerializer
{
/// <summary>
/// Serializes a Message to a JObject.
/// </summary>
/// <param name="message">The message to be serialized.</param>
/// <returns>A JObject which contains the JSON representation of the message.</returns>
JObject SerializeMessage(Message message);
/// <summary>
/// Deserializes a JObject to a Messsage.
/// </summary>
/// <param name="messageJson">The JObject containing the JSON representation of the message.</param>
/// <returns>The Message that was represented by the JObject.</returns>
Message DeserializeMessage(JObject messageJson);
}
}

View File

@@ -1,100 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
using Newtonsoft.Json.Linq;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers
{
/// <summary>
/// Serializes messages in the JSON RPC format. Used primarily
/// for language servers.
/// </summary>
public class JsonRpcMessageSerializer : IMessageSerializer
{
public JObject SerializeMessage(Message message)
{
JObject messageObject = new JObject();
messageObject.Add("jsonrpc", JToken.FromObject("2.0"));
if (message.MessageType == MessageType.Request)
{
messageObject.Add("id", JToken.FromObject(message.Id));
messageObject.Add("method", message.Method);
messageObject.Add("params", message.Contents);
}
else if (message.MessageType == MessageType.Event)
{
messageObject.Add("method", message.Method);
messageObject.Add("params", message.Contents);
}
else if (message.MessageType == MessageType.Response)
{
messageObject.Add("id", JToken.FromObject(message.Id));
if (message.Error != null)
{
// Write error
messageObject.Add("error", message.Error);
}
else
{
// Write result
messageObject.Add("result", message.Contents);
}
}
return messageObject;
}
public Message DeserializeMessage(JObject messageJson)
{
// TODO: Check for jsonrpc version
JToken token = null;
if (messageJson.TryGetValue("id", out token))
{
// Message is a Request or Response
string messageId = token.ToString();
if (messageJson.TryGetValue("result", out token))
{
return Message.Response(messageId, null, token);
}
else if (messageJson.TryGetValue("error", out token))
{
return Message.ResponseError(messageId, null, token);
}
else
{
JToken messageParams = null;
messageJson.TryGetValue("params", out messageParams);
if (!messageJson.TryGetValue("method", out token))
{
// TODO: Throw parse error
}
return Message.Request(messageId, token.ToString(), messageParams);
}
}
else
{
// Messages without an id are events
JToken messageParams = token;
messageJson.TryGetValue("params", out messageParams);
if (!messageJson.TryGetValue("method", out token))
{
// TODO: Throw parse error
}
return Message.Event(token.ToString(), messageParams);
}
}
}
}

View File

@@ -1,114 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Newtonsoft.Json.Linq;
using System;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Serializers
{
/// <summary>
/// Serializes messages in the V8 format. Used primarily for debug adapters.
/// </summary>
public class V8MessageSerializer : IMessageSerializer
{
public JObject SerializeMessage(Message message)
{
JObject messageObject = new JObject();
if (message.MessageType == MessageType.Request)
{
messageObject.Add("type", JToken.FromObject("request"));
messageObject.Add("seq", JToken.FromObject(message.Id));
messageObject.Add("command", message.Method);
messageObject.Add("arguments", message.Contents);
}
else if (message.MessageType == MessageType.Event)
{
messageObject.Add("type", JToken.FromObject("event"));
messageObject.Add("event", message.Method);
messageObject.Add("body", message.Contents);
}
else if (message.MessageType == MessageType.Response)
{
messageObject.Add("type", JToken.FromObject("response"));
messageObject.Add("request_seq", JToken.FromObject(message.Id));
messageObject.Add("command", message.Method);
if (message.Error != null)
{
// Write error
messageObject.Add("success", JToken.FromObject(false));
messageObject.Add("message", message.Error);
}
else
{
// Write result
messageObject.Add("success", JToken.FromObject(true));
messageObject.Add("body", message.Contents);
}
}
return messageObject;
}
public Message DeserializeMessage(JObject messageJson)
{
JToken token = null;
if (messageJson.TryGetValue("type", out token))
{
string messageType = token.ToString();
if (string.Equals("request", messageType, StringComparison.CurrentCultureIgnoreCase))
{
return Message.Request(
messageJson.GetValue("seq").ToString(),
messageJson.GetValue("command").ToString(),
messageJson.GetValue("arguments"));
}
else if (string.Equals("response", messageType, StringComparison.CurrentCultureIgnoreCase))
{
if (messageJson.TryGetValue("success", out token))
{
// Was the response for a successful request?
if (token.ToObject<bool>() == true)
{
return Message.Response(
messageJson.GetValue("request_seq").ToString(),
messageJson.GetValue("command").ToString(),
messageJson.GetValue("body"));
}
else
{
return Message.ResponseError(
messageJson.GetValue("request_seq").ToString(),
messageJson.GetValue("command").ToString(),
messageJson.GetValue("message"));
}
}
// else
// {
// // TODO: Parse error
// }
}
else if (string.Equals("event", messageType, StringComparison.CurrentCultureIgnoreCase))
{
return Message.Event(
messageJson.GetValue("event").ToString(),
messageJson.GetValue("body"));
}
else
{
return Message.Unknown();
}
}
return Message.Unknown();
}
}
}

View File

@@ -1,185 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Contracts;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Hosting
{
/// <summary>
/// SQL Tools VS Code Language Server request handler. Provides the entire JSON RPC
/// implementation for sending/receiving JSON requests and dispatching the requests to
/// handlers that are registered prior to startup.
/// </summary>
public sealed class ServiceHost : ServiceHostBase
{
/// <summary>
/// This timeout limits the amount of time that shutdown tasks can take to complete
/// prior to the process shutting down.
/// </summary>
private const int ShutdownTimeoutInSeconds = 120;
public static readonly string[] CompletionTriggerCharacters = new string[] { ".", "-", ":", "\\", "[", "\"" };
#region Singleton Instance Code
/// <summary>
/// Singleton instance of the service host for internal storage
/// </summary>
private static readonly Lazy<ServiceHost> instance = new Lazy<ServiceHost>(() => new ServiceHost());
/// <summary>
/// Current instance of the ServiceHost
/// </summary>
public static ServiceHost Instance
{
get { return instance.Value; }
}
/// <summary>
/// Constructs new instance of ServiceHost using the host and profile details provided.
/// Access is private to ensure only one instance exists at a time.
/// </summary>
private ServiceHost() : base(new StdioServerChannel())
{
// Initialize the shutdown activities
shutdownCallbacks = new List<ShutdownCallback>();
initializeCallbacks = new List<InitializeCallback>();
}
/// <summary>
/// Provide initialization that must occur after the service host is started
/// </summary>
public void InitializeRequestHandlers()
{
// Register the requests that this service host will handle
this.SetRequestHandler(InitializeRequest.Type, this.HandleInitializeRequest);
this.SetRequestHandler(ShutdownRequest.Type, this.HandleShutdownRequest);
this.SetRequestHandler(VersionRequest.Type, HandleVersionRequest);
}
#endregion
#region Member Variables
/// <summary>
/// Delegate definition for the host shutdown event
/// </summary>
/// <param name="shutdownParams"></param>
/// <param name="shutdownRequestContext"></param>
public delegate Task ShutdownCallback(object shutdownParams, RequestContext<object> shutdownRequestContext);
/// <summary>
/// Delegate definition for the host initialization event
/// </summary>
/// <param name="startupParams"></param>
/// <param name="requestContext"></param>
public delegate Task InitializeCallback(InitializeRequest startupParams, RequestContext<InitializeResult> requestContext);
private readonly List<ShutdownCallback> shutdownCallbacks;
private readonly List<InitializeCallback> initializeCallbacks;
private static readonly Version serviceVersion = Assembly.GetEntryAssembly().GetName().Version;
#endregion
#region Public Methods
/// <summary>
/// Adds a new callback to be called when the shutdown request is submitted
/// </summary>
/// <param name="callback">Callback to perform when a shutdown request is submitted</param>
public void RegisterShutdownTask(ShutdownCallback callback)
{
shutdownCallbacks.Add(callback);
}
/// <summary>
/// Add a new method to be called when the initialize request is submitted
/// </summary>
/// <param name="callback">Callback to perform when an initialize request is submitted</param>
public void RegisterInitializeTask(InitializeCallback callback)
{
initializeCallbacks.Add(callback);
}
#endregion
#region Request Handlers
/// <summary>
/// Handles the shutdown event for the Language Server
/// </summary>
private async Task HandleShutdownRequest(object shutdownParams, RequestContext<object> requestContext)
{
Logger.Write(LogLevel.Normal, "Service host is shutting down...");
// Call all the shutdown methods provided by the service components
Task[] shutdownTasks = shutdownCallbacks.Select(t => t(shutdownParams, requestContext)).ToArray();
TimeSpan shutdownTimeout = TimeSpan.FromSeconds(ShutdownTimeoutInSeconds);
// shut down once all tasks are completed, or after the timeout expires, whichever comes first.
await Task.WhenAny(Task.WhenAll(shutdownTasks), Task.Delay(shutdownTimeout)).ContinueWith(t => Environment.Exit(0));
}
/// <summary>
/// Handles the initialization request
/// </summary>
/// <param name="initializeParams"></param>
/// <param name="requestContext"></param>
/// <returns></returns>
internal async Task HandleInitializeRequest(InitializeRequest initializeParams, RequestContext<InitializeResult> requestContext)
{
// Call all tasks that registered on the initialize request
var initializeTasks = initializeCallbacks.Select(t => t(initializeParams, requestContext));
await Task.WhenAll(initializeTasks);
// TODO: Figure out where this needs to go to be agnostic of the language
// Send back what this server can do
await requestContext.SendResult(
new InitializeResult
{
Capabilities = new ServerCapabilities
{
TextDocumentSync = TextDocumentSyncKind.Incremental,
DefinitionProvider = true,
ReferencesProvider = false,
DocumentFormattingProvider = true,
DocumentRangeFormattingProvider = true,
DocumentHighlightProvider = false,
HoverProvider = true,
CompletionProvider = new CompletionOptions
{
ResolveProvider = true,
TriggerCharacters = CompletionTriggerCharacters
},
SignatureHelpProvider = new SignatureHelpOptions
{
TriggerCharacters = new string[] { " ", "," }
}
}
});
}
/// <summary>
/// Handles the version request. Sends back the server version as result.
/// </summary>
private static async Task HandleVersionRequest(
object versionRequestParams,
RequestContext<string> requestContext)
{
await requestContext.SendResult(serviceVersion.ToString());
}
#endregion
}
}

View File

@@ -1,47 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Hosting.Contracts;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Channel;
namespace Microsoft.SqlTools.ServiceLayer.Hosting
{
public abstract class ServiceHostBase : ProtocolEndpoint
{
private bool isStarted;
private TaskCompletionSource<bool> serverExitedTask;
protected ServiceHostBase(ChannelBase serverChannel) :
base(serverChannel, MessageProtocolType.LanguageServer)
{
}
protected override Task OnStart()
{
// Register handlers for server lifetime messages
this.SetEventHandler(ExitNotification.Type, this.HandleExitNotification);
return Task.FromResult(true);
}
private async Task HandleExitNotification(
object exitParams,
EventContext eventContext)
{
// Stop the server channel
await this.Stop();
// Notify any waiter that the server has exited
if (this.serverExitedTask != null)
{
this.serverExitedTask.SetResult(true);
}
}
}
}

View File

@@ -1,114 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
#if false
using Microsoft.SqlTools.EditorServices.Extensions;
using Microsoft.SqlTools.EditorServices.Protocol.LanguageServer;
using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol;
using System.Threading.Tasks;
namespace Microsoft.SqlTools.EditorServices.Protocol.Server
{
internal class LanguageServerEditorOperations : IEditorOperations
{
private EditorSession editorSession;
private IMessageSender messageSender;
public LanguageServerEditorOperations(
EditorSession editorSession,
IMessageSender messageSender)
{
this.editorSession = editorSession;
this.messageSender = messageSender;
}
public async Task<EditorContext> GetEditorContext()
{
ClientEditorContext clientContext =
await this.messageSender.SendRequest(
GetEditorContextRequest.Type,
new GetEditorContextRequest(),
true);
return this.ConvertClientEditorContext(clientContext);
}
public async Task InsertText(string filePath, string text, BufferRange insertRange)
{
await this.messageSender.SendRequest(
InsertTextRequest.Type,
new InsertTextRequest
{
FilePath = filePath,
InsertText = text,
InsertRange =
new Range
{
Start = new Position
{
Line = insertRange.Start.Line - 1,
Character = insertRange.Start.Column - 1
},
End = new Position
{
Line = insertRange.End.Line - 1,
Character = insertRange.End.Column - 1
}
}
}, false);
// TODO: Set the last param back to true!
}
public Task SetSelection(BufferRange selectionRange)
{
return this.messageSender.SendRequest(
SetSelectionRequest.Type,
new SetSelectionRequest
{
SelectionRange =
new Range
{
Start = new Position
{
Line = selectionRange.Start.Line - 1,
Character = selectionRange.Start.Column - 1
},
End = new Position
{
Line = selectionRange.End.Line - 1,
Character = selectionRange.End.Column - 1
}
}
}, true);
}
public EditorContext ConvertClientEditorContext(
ClientEditorContext clientContext)
{
return
new EditorContext(
this,
this.editorSession.Workspace.GetFile(clientContext.CurrentFilePath),
new BufferPosition(
clientContext.CursorPosition.Line + 1,
clientContext.CursorPosition.Character + 1),
new BufferRange(
clientContext.SelectionRange.Start.Line + 1,
clientContext.SelectionRange.Start.Character + 1,
clientContext.SelectionRange.End.Line + 1,
clientContext.SelectionRange.End.Character + 1));
}
public Task OpenFile(string filePath)
{
return
this.messageSender.SendRequest(
OpenFileRequest.Type,
filePath,
true);
}
}
}
#endif

1867
src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -1,96 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
@@ -105,19 +27,5 @@
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="TestLocalizationConstant" xml:space="preserve">
<value>ES_LOCALIZATION</value>
</data>
</root>
<resheader name="resmimetype"><value>text/microsoft-resx</value></resheader><resheader name="version"><value>1.3</value></resheader><resheader name="reader"><value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader><resheader name="writer"><value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader><data name="TestLocalizationConstant"><value>ES_LOCALIZATION</value></data>
</root>

1084
src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -48,50 +48,11 @@ ConnectionParamsValidateNullServerName = ServerName cannot be null or empty
ConnectionParamsValidateNullSqlAuth(string component) = {0} cannot be null or empty when using SqlLogin authentication
############################################################################
# Credentials Service
CredentialsServiceInvalidCriticalHandle = Invalid CriticalHandle!
CredentialsServicePasswordLengthExceeded = The password has exceeded 512 bytes
CredentialsServiceTargetForDelete = Target must be specified to delete a credential
CredentialsServiceTargetForLookup = Target must be specified to check existance of a credential
CredentialServiceWin32CredentialDisposed = Win32Credential object is already disposed
############################################################################
# Extensibility
ServiceAlreadyRegistered = Cannot register service for type {0}, one or more services already registered
MultipleServicesFound = Multiple services found for type {0}, expected only 1
IncompatibleServiceForExtensionLoader = Service of type {0} cannot be created by ExtensionLoader<{1}>
ServiceProviderNotSet = SetServiceProvider() was not called to establish the required service provider
ServiceNotFound = Service {0} was not found in the service provider
ServiceNotOfExpectedType = Service of Type {0} is not compatible with registered Type {1}
############################################################################
# Formatter
ErrorUnexpectedCodeObjectType = Cannot convert SqlCodeObject Type {0} to Type {1}
############################################################################
# Hosting
HostingUnexpectedEndOfStream = MessageReader's input stream ended unexpectedly, terminating
HostingHeaderMissingColon = Message header must separate key and value using ':'
HostingHeaderMissingContentLengthHeader = Fatal error: Content-Length header must be provided
HostingHeaderMissingContentLengthValue = Fatal error: Content-Length value is not an integer
############################################################################
# Query Execution Service

View File

@@ -56,51 +56,6 @@
<note>.
Parameters: 0 - component (string) </note>
</trans-unit>
<trans-unit id="CredentialsServiceInvalidCriticalHandle">
<source>Invalid CriticalHandle!</source>
<target state="new">Invalid CriticalHandle!</target>
<note></note>
</trans-unit>
<trans-unit id="CredentialsServicePasswordLengthExceeded">
<source>The password has exceeded 512 bytes</source>
<target state="new">The password has exceeded 512 bytes</target>
<note></note>
</trans-unit>
<trans-unit id="CredentialsServiceTargetForDelete">
<source>Target must be specified to delete a credential</source>
<target state="new">Target must be specified to delete a credential</target>
<note></note>
</trans-unit>
<trans-unit id="CredentialsServiceTargetForLookup">
<source>Target must be specified to check existance of a credential</source>
<target state="new">Target must be specified to check existance of a credential</target>
<note></note>
</trans-unit>
<trans-unit id="CredentialServiceWin32CredentialDisposed">
<source>Win32Credential object is already disposed</source>
<target state="new">Win32Credential object is already disposed</target>
<note></note>
</trans-unit>
<trans-unit id="HostingUnexpectedEndOfStream">
<source>MessageReader's input stream ended unexpectedly, terminating</source>
<target state="new">MessageReader's input stream ended unexpectedly, terminating</target>
<note></note>
</trans-unit>
<trans-unit id="HostingHeaderMissingColon">
<source>Message header must separate key and value using ':'</source>
<target state="new">Message header must separate key and value using ':'</target>
<note></note>
</trans-unit>
<trans-unit id="HostingHeaderMissingContentLengthHeader">
<source>Fatal error: Content-Length header must be provided</source>
<target state="new">Fatal error: Content-Length header must be provided</target>
<note></note>
</trans-unit>
<trans-unit id="HostingHeaderMissingContentLengthValue">
<source>Fatal error: Content-Length value is not an integer</source>
<target state="new">Fatal error: Content-Length value is not an integer</target>
<note></note>
</trans-unit>
<trans-unit id="QueryServiceCancelAlreadyCompleted">
<source>The query has already completed, it cannot be cancelled</source>
<target state="new">The query has already completed, it cannot be cancelled</target>
@@ -469,36 +424,6 @@
<target state="new">EN_LOCALIZATION</target>
<note></note>
</trans-unit>
<trans-unit id="ServiceAlreadyRegistered">
<source>Cannot register service for type {0}, one or more services already registered</source>
<target state="new">Cannot register service for type {0}, one or more services already registered</target>
<note></note>
</trans-unit>
<trans-unit id="MultipleServicesFound">
<source>Multiple services found for type {0}, expected only 1</source>
<target state="new">Multiple services found for type {0}, expected only 1</target>
<note></note>
</trans-unit>
<trans-unit id="IncompatibleServiceForExtensionLoader">
<source>Service of type {0} cannot be created by ExtensionLoader&lt;{1}&gt;</source>
<target state="new">Service of type {0} cannot be created by ExtensionLoader&lt;{1}&gt;</target>
<note></note>
</trans-unit>
<trans-unit id="ServiceProviderNotSet">
<source>SetServiceProvider() was not called to establish the required service provider</source>
<target state="new">SetServiceProvider() was not called to establish the required service provider</target>
<note></note>
</trans-unit>
<trans-unit id="ServiceNotFound">
<source>Service {0} was not found in the service provider</source>
<target state="new">Service {0} was not found in the service provider</target>
<note></note>
</trans-unit>
<trans-unit id="ServiceNotOfExpectedType">
<source>Service of Type {0} is not compatible with registered Type {1}</source>
<target state="new">Service of Type {0} is not compatible with registered Type {1}</target>
<note></note>
</trans-unit>
<trans-unit id="ErrorUnexpectedCodeObjectType">
<source>Cannot convert SqlCodeObject Type {0} to Type {1}</source>
<target state="new">Cannot convert SqlCodeObject Type {0} to Type {1}</target>
@@ -595,6 +520,12 @@
<note>.
Parameters: 0 - typeName (string) </note>
</trans-unit>
<trans-unit id="ConnectionServiceDbErrorDefaultNotConnected">
<source>Specified URI '{0}' does not have a default connection</source>
<target state="new">Specified URI '{0}' does not have a default connection</target>
<note>.
Parameters: 0 - uri (string) </note>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -1,92 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.SqlTools.ServiceLayer.SqlContext
{
/// <summary>
/// Contains details about the current host application (most
/// likely the editor which is using the host process).
/// </summary>
public class HostDetails
{
#region Constants
/// <summary>
/// The default host name for SqlTools Editor Services. Used
/// if no host name is specified by the host application.
/// </summary>
public const string DefaultHostName = "SqlTools Service Host";
/// <summary>
/// The default host ID for SqlTools Editor Services. Used
/// for the host-specific profile path if no host ID is specified.
/// </summary>
public const string DefaultHostProfileId = "Microsoft.SqlToolsServiceHost";
/// <summary>
/// The default host version for SqlTools Editor Services. If
/// no version is specified by the host application, we use 0.0.0
/// to indicate a lack of version.
/// </summary>
public static readonly Version DefaultHostVersion = new Version("0.0.0");
/// <summary>
/// The default host details in a HostDetails object.
/// </summary>
public static readonly HostDetails Default = new HostDetails(null, null, null);
#endregion
#region Properties
/// <summary>
/// Gets the name of the host.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Gets the profile ID of the host, used to determine the
/// host-specific profile path.
/// </summary>
public string ProfileId { get; private set; }
/// <summary>
/// Gets the version of the host.
/// </summary>
public Version Version { get; private set; }
#endregion
#region Constructors
/// <summary>
/// Creates an instance of the HostDetails class.
/// </summary>
/// <param name="name">
/// The display name for the host, typically in the form of
/// "[Application Name] Host".
/// </param>
/// <param name="profileId">
/// The identifier of the SqlTools host to use for its profile path.
/// loaded. Used to resolve a profile path of the form 'X_profile.ps1'
/// where 'X' represents the value of hostProfileId. If null, a default
/// will be used.
/// </param>
/// <param name="version">The host application's version.</param>
public HostDetails(
string name = null,
string profileId = null,
Version version = null)
{
this.Name = name ?? DefaultHostName;
this.ProfileId = profileId ?? DefaultHostProfileId;
this.Version = version ?? DefaultHostVersion;
}
#endregion
}
}

View File

@@ -1,33 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.SqlTools.ServiceLayer.SqlContext
{
/// <summary>
/// Context for SQL Tools
/// </summary>
public class SqlToolsContext
{
/// <summary>
/// Gets the PowerShell version of the current runspace.
/// </summary>
public Version SqlToolsVersion
{
get; private set;
}
/// <summary>
/// Initalizes the SQL Tools context instance
/// </summary>
/// <param name="hostDetails"></param>
public SqlToolsContext(HostDetails hostDetails)
{
this.SqlToolsVersion = hostDetails.Version;
}
}
}

View File

@@ -1,52 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.SqlTools.ServiceLayer.Utility
{
/// <summary>
/// Simplifies the setup of a SynchronizationContext for the use
/// of async calls in the current thread.
/// </summary>
public static class AsyncContext
{
/// <summary>
/// Starts a new ThreadSynchronizationContext, attaches it to
/// the thread, and then runs the given async main function.
/// </summary>
/// <param name="asyncMainFunc">
/// The Task-returning Func which represents the "main" function
/// for the thread.
/// </param>
public static void Start(Func<Task> asyncMainFunc)
{
// Is there already a synchronization context?
if (SynchronizationContext.Current != null)
{
throw new InvalidOperationException(
"A SynchronizationContext is already assigned on this thread.");
}
// Create and register a synchronization context for this thread
var threadSyncContext = new ThreadSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(threadSyncContext);
// Get the main task and act on its completion
Task asyncMainTask = asyncMainFunc();
asyncMainTask.ContinueWith(
t => threadSyncContext.EndLoop(),
TaskScheduler.Default);
// Start the synchronization context's request loop and
// wait for the main task to complete
threadSyncContext.RunLoopOnCurrentThread();
asyncMainTask.GetAwaiter().GetResult();
}
}
}

View File

@@ -1,85 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.SqlTools.ServiceLayer.Utility
{
/// <summary>
/// Provides a simplified interface for creating a new thread
/// and establishing an AsyncContext in it.
/// </summary>
public class AsyncContextThread
{
#region Private Fields
private Task threadTask;
private string threadName;
private CancellationTokenSource threadCancellationToken =
new CancellationTokenSource();
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the AsyncContextThread class.
/// </summary>
/// <param name="threadName">
/// The name of the thread for debugging purposes.
/// </param>
public AsyncContextThread(string threadName)
{
this.threadName = threadName;
}
#endregion
#region Public Methods
/// <summary>
/// Runs a task on the AsyncContextThread.
/// </summary>
/// <param name="taskReturningFunc">
/// A Func which returns the task to be run on the thread.
/// </param>
/// <returns>
/// A Task which can be used to monitor the thread for completion.
/// </returns>
public Task Run(Func<Task> taskReturningFunc)
{
// Start up a long-running task with the action as the
// main entry point for the thread
this.threadTask =
Task.Factory.StartNew(
() =>
{
// Set the thread's name to help with debugging
Thread.CurrentThread.Name = "AsyncContextThread: " + this.threadName;
// Set up an AsyncContext to run the task
AsyncContext.Start(taskReturningFunc);
},
this.threadCancellationToken.Token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
return this.threadTask;
}
/// <summary>
/// Stops the thread task.
/// </summary>
public void Stop()
{
this.threadCancellationToken.Cancel();
}
#endregion
}
}

View File

@@ -1,103 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.SqlTools.ServiceLayer.Utility
{
/// <summary>
/// Provides a simple wrapper over a SemaphoreSlim to allow
/// synchronization locking inside of async calls. Cannot be
/// used recursively.
/// </summary>
public class AsyncLock
{
#region Fields
private Task<IDisposable> lockReleaseTask;
private SemaphoreSlim lockSemaphore = new SemaphoreSlim(1, 1);
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the AsyncLock class.
/// </summary>
public AsyncLock()
{
this.lockReleaseTask =
Task.FromResult(
(IDisposable)new LockReleaser(this));
}
#endregion
#region Public Methods
/// <summary>
/// Locks
/// </summary>
/// <returns>A task which has an IDisposable</returns>
public Task<IDisposable> LockAsync()
{
return this.LockAsync(CancellationToken.None);
}
/// <summary>
/// Obtains or waits for a lock which can be used to synchronize
/// access to a resource. The wait may be cancelled with the
/// given CancellationToken.
/// </summary>
/// <param name="cancellationToken">
/// A CancellationToken which can be used to cancel the lock.
/// </param>
/// <returns></returns>
public Task<IDisposable> LockAsync(CancellationToken cancellationToken)
{
Task waitTask = lockSemaphore.WaitAsync(cancellationToken);
return waitTask.IsCompleted ?
this.lockReleaseTask :
waitTask.ContinueWith(
(t, releaser) =>
{
return (IDisposable)releaser;
},
this.lockReleaseTask.Result,
cancellationToken,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
#endregion
#region Private Classes
/// <summary>
/// Provides an IDisposable wrapper around an AsyncLock so
/// that it can easily be used inside of a 'using' block.
/// </summary>
private class LockReleaser : IDisposable
{
private AsyncLock lockToRelease;
internal LockReleaser(AsyncLock lockToRelease)
{
this.lockToRelease = lockToRelease;
}
public void Dispose()
{
this.lockToRelease.lockSemaphore.Release();
}
}
#endregion
}
}

View File

@@ -1,155 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.SqlTools.ServiceLayer.Utility
{
/// <summary>
/// Provides a synchronized queue which can be used from within async
/// operations. This is primarily used for producer/consumer scenarios.
/// </summary>
/// <typeparam name="T">The type of item contained in the queue.</typeparam>
public class AsyncQueue<T>
{
#region Private Fields
private AsyncLock queueLock = new AsyncLock();
private Queue<T> itemQueue;
private Queue<TaskCompletionSource<T>> requestQueue;
#endregion
#region Properties
/// <summary>
/// Returns true if the queue is currently empty.
/// </summary>
public bool IsEmpty { get; private set; }
#endregion
#region Constructors
/// <summary>
/// Initializes an empty instance of the AsyncQueue class.
/// </summary>
public AsyncQueue() : this(Enumerable.Empty<T>())
{
}
/// <summary>
/// Initializes an instance of the AsyncQueue class, pre-populated
/// with the given collection of items.
/// </summary>
/// <param name="initialItems">
/// An IEnumerable containing the initial items with which the queue will
/// be populated.
/// </param>
public AsyncQueue(IEnumerable<T> initialItems)
{
this.itemQueue = new Queue<T>(initialItems);
this.requestQueue = new Queue<TaskCompletionSource<T>>();
}
#endregion
#region Public Methods
/// <summary>
/// Enqueues an item onto the end of the queue.
/// </summary>
/// <param name="item">The item to be added to the queue.</param>
/// <returns>
/// A Task which can be awaited until the synchronized enqueue
/// operation completes.
/// </returns>
public async Task EnqueueAsync(T item)
{
using (await queueLock.LockAsync())
{
TaskCompletionSource<T> requestTaskSource = null;
// Are any requests waiting?
while (this.requestQueue.Count > 0)
{
// Is the next request cancelled already?
requestTaskSource = this.requestQueue.Dequeue();
if (!requestTaskSource.Task.IsCanceled)
{
// Dispatch the item
requestTaskSource.SetResult(item);
return;
}
}
// No more requests waiting, queue the item for a later request
this.itemQueue.Enqueue(item);
this.IsEmpty = false;
}
}
/// <summary>
/// Dequeues an item from the queue or waits asynchronously
/// until an item is available.
/// </summary>
/// <returns>
/// A Task which can be awaited until a value can be dequeued.
/// </returns>
public Task<T> DequeueAsync()
{
return this.DequeueAsync(CancellationToken.None);
}
/// <summary>
/// Dequeues an item from the queue or waits asynchronously
/// until an item is available. The wait can be cancelled
/// using the given CancellationToken.
/// </summary>
/// <param name="cancellationToken">
/// A CancellationToken with which a dequeue wait can be cancelled.
/// </param>
/// <returns>
/// A Task which can be awaited until a value can be dequeued.
/// </returns>
public async Task<T> DequeueAsync(CancellationToken cancellationToken)
{
Task<T> requestTask;
using (await queueLock.LockAsync(cancellationToken))
{
if (this.itemQueue.Count > 0)
{
// Items are waiting to be taken so take one immediately
T item = this.itemQueue.Dequeue();
this.IsEmpty = this.itemQueue.Count == 0;
return item;
}
else
{
// Queue the request for the next item
var requestTaskSource = new TaskCompletionSource<T>();
this.requestQueue.Enqueue(requestTaskSource);
// Register the wait task for cancel notifications
cancellationToken.Register(
() => requestTaskSource.TrySetCanceled());
requestTask = requestTaskSource.Task;
}
}
// Wait for the request task to complete outside of the lock
return await requestTask;
}
#endregion
}
}

View File

@@ -45,7 +45,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
EnableLogging = true;
break;
case "-locale":
setLocale(argProperty);
SetLocale(argProperty);
break;
case "h":
case "-help":
@@ -109,7 +109,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
}
}
private void setLocale(string locale){
private void SetLocale(string locale)
{
try
{
// Creating cultureInfo from our given locale

View File

@@ -1,59 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.SqlTools.ServiceLayer.Utility
{
internal static class ObjectExtensions
{
/// <summary>
/// Extension to evaluate an object's ToString() method in an exception safe way. This will
/// extension method will not throw.
/// </summary>
/// <param name="obj">The object on which to call ToString()</param>
/// <returns>The ToString() return value or a suitable error message is that throws.</returns>
public static string SafeToString(this object obj)
{
string str;
try
{
str = obj.ToString();
}
catch (Exception ex)
{
str = $"<Error converting poperty value to string - {ex.Message}>";
}
return str;
}
/// <summary>
/// Converts a boolean to a "1" or "0" string. Particularly helpful when sending telemetry
/// </summary>
public static string ToOneOrZeroString(this bool isTrue)
{
return isTrue ? "1" : "0";
}
}
internal static class NullableExtensions
{
/// <summary>
/// Extension method to evaluate a bool? and determine if it has the value and is true.
/// This way we avoid throwing if the bool? doesn't have a value.
/// </summary>
/// <param name="obj">The <c>bool?</c> to process</param>
/// <returns>
/// <c>true</c> if <paramref name="obj"/> has a value and it is <c>true</c>
/// <c>false</c> otherwise.
/// </returns>
public static bool HasTrue(this bool? obj)
{
return obj.HasValue && obj.Value;
}
}
}

View File

@@ -1,271 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
namespace Microsoft.SqlTools.ServiceLayer.Utility
{
/// <summary>
/// Defines the level indicators for log messages.
/// </summary>
public enum LogLevel
{
/// <summary>
/// Indicates a verbose log message.
/// </summary>
Verbose,
/// <summary>
/// Indicates a normal, non-verbose log message.
/// </summary>
Normal,
/// <summary>
/// Indicates a warning message.
/// </summary>
Warning,
/// <summary>
/// Indicates an error message.
/// </summary>
Error
}
/// <summary>
/// Provides a simple logging interface. May be replaced with a
/// more robust solution at a later date.
/// </summary>
public static class Logger
{
private static LogWriter logWriter;
private static bool isEnabled;
private static bool isInitialized = false;
/// <summary>
/// Initializes the Logger for the current session.
/// </summary>
/// <param name="logFilePath">
/// Optional. Specifies the path at which log messages will be written.
/// </param>
/// <param name="minimumLogLevel">
/// Optional. Specifies the minimum log message level to write to the log file.
/// </param>
public static void Initialize(
string logFilePath = "sqltools",
LogLevel minimumLogLevel = LogLevel.Normal,
bool isEnabled = true)
{
Logger.isEnabled = isEnabled;
// return if the logger is not enabled or already initialized
if (!Logger.isEnabled || Logger.isInitialized)
{
return;
}
Logger.isInitialized = true;
// get a unique number to prevent conflicts of two process launching at the same time
int uniqueId;
try
{
uniqueId = Process.GetCurrentProcess().Id;
}
catch (Exception)
{
// if the pid look up fails for any reason, just use a random number
uniqueId = new Random().Next(1000, 9999);
}
// make the log path unique
string fullFileName = string.Format(
"{0}_{1,4:D4}{2,2:D2}{3,2:D2}{4,2:D2}{5,2:D2}{6,2:D2}{7}.log",
logFilePath,
DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day,
DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second,
uniqueId);
if (logWriter != null)
{
logWriter.Dispose();
}
// TODO: Parameterize this
logWriter =
new LogWriter(
minimumLogLevel,
fullFileName,
true);
Logger.Write(LogLevel.Normal, "Initializing SQL Tools Service Host logger");
}
/// <summary>
/// Closes the Logger.
/// </summary>
public static void Close()
{
if (logWriter != null)
{
logWriter.Dispose();
}
}
/// <summary>
/// Writes a message to the log file.
/// </summary>
/// <param name="logLevel">The level at which the message will be written.</param>
/// <param name="logMessage">The message text to be written.</param>
/// <param name="callerName">The name of the calling method.</param>
/// <param name="callerSourceFile">The source file path where the calling method exists.</param>
/// <param name="callerLineNumber">The line number of the calling method.</param>
public static void Write(
LogLevel logLevel,
string logMessage,
[CallerMemberName] string callerName = null,
[CallerFilePath] string callerSourceFile = null,
[CallerLineNumber] int callerLineNumber = 0)
{
// return if the logger is not enabled or not initialized
if (!Logger.isEnabled || !Logger.isInitialized)
{
return;
}
if (logWriter != null)
{
logWriter.Write(
logLevel,
logMessage,
callerName,
callerSourceFile,
callerLineNumber);
}
}
}
internal class LogWriter : IDisposable
{
private object logLock = new object();
private TextWriter textWriter;
private LogLevel minimumLogLevel = LogLevel.Verbose;
public LogWriter(LogLevel minimumLogLevel, string logFilePath, bool deleteExisting)
{
this.minimumLogLevel = minimumLogLevel;
// Ensure that we have a usable log file path
if (!Path.IsPathRooted(logFilePath))
{
logFilePath =
Path.Combine(
AppContext.BaseDirectory,
logFilePath);
}
if (!this.TryOpenLogFile(logFilePath, deleteExisting))
{
// If the log file couldn't be opened at this location,
// try opening it in a more reliable path
this.TryOpenLogFile(
Path.Combine(
Environment.GetEnvironmentVariable("TEMP"),
Path.GetFileName(logFilePath)),
deleteExisting);
}
}
public void Write(
LogLevel logLevel,
string logMessage,
string callerName = null,
string callerSourceFile = null,
int callerLineNumber = 0)
{
if (this.textWriter != null &&
logLevel >= this.minimumLogLevel)
{
// System.IO is not thread safe
lock (this.logLock)
{
// Print the timestamp and log level
this.textWriter.WriteLine(
"{0} [{1}] - Method \"{2}\" at line {3} of {4}\r\n",
DateTime.Now,
logLevel.ToString().ToUpper(),
callerName,
callerLineNumber,
callerSourceFile);
// Print out indented message lines
foreach (var messageLine in logMessage.Split('\n'))
{
this.textWriter.WriteLine(" " + messageLine.TrimEnd());
}
// Finish with a newline and flush the writer
this.textWriter.WriteLine();
this.textWriter.Flush();
}
}
}
public void Dispose()
{
if (this.textWriter != null)
{
this.textWriter.Flush();
this.textWriter.Dispose();
this.textWriter = null;
}
}
private bool TryOpenLogFile(
string logFilePath,
bool deleteExisting)
{
try
{
// Make sure the log directory exists
Directory.CreateDirectory(
Path.GetDirectoryName(
logFilePath));
// Open the log file for writing with UTF8 encoding
this.textWriter =
new StreamWriter(
new FileStream(
logFilePath,
deleteExisting ?
FileMode.Create :
FileMode.Append),
Encoding.UTF8);
return true;
}
catch (Exception e)
{
if (e is UnauthorizedAccessException ||
e is IOException)
{
// This exception is thrown when we can't open the file
// at the path in logFilePath. Return false to indicate
// that the log file couldn't be created.
return false;
}
// Unexpected exception, rethrow it
throw;
}
}
}
}

View File

@@ -1,275 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections;
using System.Collections.Generic;
namespace Microsoft.SqlTools.ServiceLayer.Utility
{
/// <summary>
/// Collection class that permits storage of over <c>int.MaxValue</c> items. This is performed
/// by using a 2D list of lists. The internal lists are only initialized as necessary. This
/// collection implements IEnumerable to make it easier to run LINQ queries against it.
/// </summary>
/// <remarks>
/// This class is based on code from $\Data Tools\SSMS_Main\sql\ssms\core\DataStorage\ArrayList64.cs
/// with additions to bring it up to .NET 4.5 standards
/// </remarks>
/// <typeparam name="T">Type of the values to store</typeparam>
public class LongList<T> : IEnumerable<T>
{
#region Member Variables
private int expandListSize = int.MaxValue;
private List<List<T>> expandedList;
private readonly List<T> shortList;
#endregion
/// <summary>
/// Creates a new long list
/// </summary>
public LongList()
{
shortList = new List<T>();
Count = 0;
}
#region Properties
/// <summary>
/// The total number of elements in the array
/// </summary>
public long Count { get; private set; }
public T this[long index]
{
get
{
return GetItem(index);
}
}
public int ExpandListSize
{
get
{
return this.expandListSize;
}
internal set
{
this.expandListSize = value;
}
}
#endregion
#region Public Methods
/// <summary>
/// Adds the specified value to the end of the list
/// </summary>
/// <param name="val">Value to add to the list</param>
/// <returns>Index of the item that was just added</returns>
public long Add(T val)
{
if (Count <= this.ExpandListSize)
{
shortList.Add(val);
}
else // need to split values into several arrays
{
if (expandedList == null)
{
// very inefficient so delay as much as possible
// immediately add 0th array
expandedList = new List<List<T>> {shortList};
}
int arrayIndex = (int)(Count / this.ExpandListSize); // 0 based
List<T> arr;
if (expandedList.Count <= arrayIndex) // need to make a new array
{
arr = new List<T>();
expandedList.Add(arr);
}
else // use existing array
{
arr = expandedList[arrayIndex];
}
arr.Add(val);
}
return (++Count);
}
/// <summary>
/// Returns the item at the specified index
/// </summary>
/// <param name="index">Index of the item to return</param>
/// <returns>The item at the index specified</returns>
public T GetItem(long index)
{
T val = default(T);
if (Count <= this.ExpandListSize)
{
int i32Index = Convert.ToInt32(index);
val = shortList[i32Index];
}
else
{
int iArray32Index = (int) (Count / this.ExpandListSize);
if (expandedList.Count > iArray32Index)
{
List<T> arr = expandedList[iArray32Index];
int i32Index = (int) (Count % this.ExpandListSize);
if (arr.Count > i32Index)
{
val = arr[i32Index];
}
}
}
return val;
}
/// <summary>
/// Removes an item at the specified location and shifts all the items after the provided
/// index up by one.
/// </summary>
/// <param name="index">The index to remove from the list</param>
public void RemoveAt(long index)
{
if (Count <= this.ExpandListSize)
{
int iArray32MemberIndex = Convert.ToInt32(index); // 0 based
shortList.RemoveAt(iArray32MemberIndex);
}
else // handle the case of multiple arrays
{
// find out which array it is in
int arrayIndex = (int) (index / this.ExpandListSize);
List<T> arr = expandedList[arrayIndex];
// find out index into this array
int iArray32MemberIndex = (int) (index % this.ExpandListSize);
arr.RemoveAt(iArray32MemberIndex);
// now shift members of the array back one
int iArray32TotalIndex = (int) (Count / this.ExpandListSize);
for (int i = arrayIndex + 1; i < iArray32TotalIndex; i++)
{
List<T> arr1 = expandedList[i - 1];
List<T> arr2 = expandedList[i];
arr1.Add(arr2[this.ExpandListSize - 1]);
arr2.RemoveAt(0);
}
}
--Count;
}
#endregion
#region IEnumerable<object> Implementation
/// <summary>
/// Returns a generic enumerator for enumeration of this LongList
/// </summary>
/// <returns>Enumerator for LongList</returns>
public IEnumerator<T> GetEnumerator()
{
return new LongListEnumerator<T>(this);
}
/// <summary>
/// Returns an enumerator for enumeration of this LongList
/// </summary>
/// <returns></returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
public class LongListEnumerator<TEt> : IEnumerator<TEt>
{
#region Member Variables
/// <summary>
/// The index into the list of the item that is the current item
/// </summary>
private long index;
/// <summary>
/// The current list that we're iterating over.
/// </summary>
private readonly LongList<TEt> localList;
#endregion
/// <summary>
/// Constructs a new enumerator for a given LongList
/// </summary>
/// <param name="list">The list to enumerate</param>
public LongListEnumerator(LongList<TEt> list)
{
localList = list;
index = 0;
Current = default(TEt);
}
#region IEnumerator Implementation
/// <summary>
/// Returns the current item in the enumeration
/// </summary>
public TEt Current { get; private set; }
object IEnumerator.Current
{
get { return Current; }
}
/// <summary>
/// Moves to the next item in the list we're iterating over
/// </summary>
/// <returns>Whether or not the move was successful</returns>
public bool MoveNext()
{
if (index < localList.Count)
{
Current = localList[index];
index++;
return true;
}
Current = default(TEt);
return false;
}
/// <summary>
/// Resets the enumeration
/// </summary>
public void Reset()
{
index = 0;
Current = default(TEt);
}
/// <summary>
/// Disposal method. Does nothing.
/// </summary>
public void Dispose()
{
}
#endregion
}
}
}

View File

@@ -1,127 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.Utility
{
public static class TextUtilities
{
/// <summary>
/// Find the position of the cursor in the SQL script content buffer and return previous new line position
/// </summary>
/// <param name="sql"></param>
/// <param name="startRow">parameter is 0-based</param>
/// <param name="startColumn">parameter is 0-based</param>
/// <param name="prevNewLine">parameter is 0-based</param>
public static int PositionOfCursor(string sql, int startRow, int startColumn, out int prevNewLine)
{
prevNewLine = 0;
if (string.IsNullOrWhiteSpace(sql))
{
return 1;
}
for (int i = 0; i < startRow; ++i)
{
while (prevNewLine < sql.Length && sql[prevNewLine] != '\n')
{
++prevNewLine;
}
++prevNewLine;
}
return startColumn + prevNewLine;
}
/// <summary>
/// Find the position of the previous delimeter for autocomplete token replacement.
/// SQL Parser may have similar functionality in which case we'll delete this method.
/// </summary>
/// <param name="sql"></param>
/// <param name="startRow">parameter is 0-based</param>
/// <param name="startColumn">parameter is 0-based</param>
/// <param name="tokenText"></param>
public static int PositionOfPrevDelimeter(string sql, int startRow, int startColumn)
{
int prevNewLine;
int delimeterPos = PositionOfCursor(sql, startRow, startColumn, out prevNewLine);
if (delimeterPos - 1 < sql.Length)
{
while (--delimeterPos >= prevNewLine)
{
if (IsCharacterDelimeter(sql[delimeterPos]))
{
break;
}
}
delimeterPos = delimeterPos + 1 - prevNewLine;
}
return delimeterPos;
}
/// <summary>
/// Find the position of the next delimeter for autocomplete token replacement.
/// </summary>
/// <param name="sql"></param>
/// <param name="startRow">parameter is 0-based</param>
/// <param name="startColumn">parameter is 0-based</param>
public static int PositionOfNextDelimeter(string sql, int startRow, int startColumn)
{
int prevNewLine;
int delimeterPos = PositionOfCursor(sql, startRow, startColumn, out prevNewLine);
while (delimeterPos < sql.Length)
{
if (IsCharacterDelimeter(sql[delimeterPos]))
{
break;
}
++delimeterPos;
}
return delimeterPos - prevNewLine;
}
/// <summary>
/// Determine if the character is a SQL token delimiter
/// </summary>
/// <param name="ch"></param>
private static bool IsCharacterDelimeter(char ch)
{
return ch == ' '
|| ch == '\t'
|| ch == '\n'
|| ch == '.'
|| ch == '+'
|| ch == '-'
|| ch == '*'
|| ch == '>'
|| ch == '<'
|| ch == '='
|| ch == '/'
|| ch == '%'
|| ch == ','
|| ch == ';'
|| ch == '('
|| ch == ')';
}
/// <summary>
/// Remove square bracket syntax from a token string
/// </summary>
/// <param name="tokenText"></param>
/// <returns> string with outer brackets removed</returns>
public static string RemoveSquareBracketSyntax(string tokenText)
{
if(tokenText.StartsWith("[") && tokenText.EndsWith("]"))
{
return tokenText.Substring(1, tokenText.Length - 2);
}
return tokenText;
}
}
}

View File

@@ -1,77 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Concurrent;
using System.Threading;
namespace Microsoft.SqlTools.ServiceLayer.Utility
{
/// <summary>
/// Provides a SynchronizationContext implementation that can be used
/// in console applications or any thread which doesn't have its
/// own SynchronizationContext.
/// </summary>
public class ThreadSynchronizationContext : SynchronizationContext
{
#region Private Fields
private BlockingCollection<Tuple<SendOrPostCallback, object>> requestQueue =
new BlockingCollection<Tuple<SendOrPostCallback, object>>();
#endregion
#region Constructors
/// <summary>
/// Posts a request for execution to the SynchronizationContext.
/// This will be executed on the SynchronizationContext's thread.
/// </summary>
/// <param name="callback">
/// The callback to be invoked on the SynchronizationContext's thread.
/// </param>
/// <param name="state">
/// A state object to pass along to the callback when executed through
/// the SynchronizationContext.
/// </param>
public override void Post(SendOrPostCallback callback, object state)
{
// Add the request to the queue
this.requestQueue.Add(
new Tuple<SendOrPostCallback, object>(
callback, state));
}
#endregion
#region Public Methods
/// <summary>
/// Starts the SynchronizationContext message loop on the current thread.
/// </summary>
public void RunLoopOnCurrentThread()
{
Tuple<SendOrPostCallback, object> request;
while (this.requestQueue.TryTake(out request, Timeout.Infinite))
{
// Invoke the request's callback
request.Item1(request.Item2);
}
}
/// <summary>
/// Ends the SynchronizationContext message loop.
/// </summary>
public void EndLoop()
{
// Tell the blocking queue that we're done
this.requestQueue.CompleteAdding();
}
#endregion
}
}

View File

@@ -1,158 +0,0 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
namespace Microsoft.SqlTools.ServiceLayer.Utility
{
/// <summary>
/// Provides common validation methods to simplify method
/// parameter checks.
/// </summary>
public static class Validate
{
/// <summary>
/// Throws ArgumentNullException if value is null.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="valueToCheck">The value of the parameter being validated.</param>
public static void IsNotNull(string parameterName, object valueToCheck)
{
if (valueToCheck == null)
{
throw new ArgumentNullException(parameterName);
}
}
/// <summary>
/// Throws ArgumentOutOfRangeException if the value is outside
/// of the given lower and upper limits.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="valueToCheck">The value of the parameter being validated.</param>
/// <param name="lowerLimit">The lower limit which the value should not be less than.</param>
/// <param name="upperLimit">The upper limit which the value should not be greater than.</param>
public static void IsWithinRange(
string parameterName,
int valueToCheck,
int lowerLimit,
int upperLimit)
{
// TODO: Debug assert here if lowerLimit >= upperLimit
if (valueToCheck < lowerLimit || valueToCheck > upperLimit)
{
throw new ArgumentOutOfRangeException(
parameterName,
valueToCheck,
string.Format(
"Value is not between {0} and {1}",
lowerLimit,
upperLimit));
}
}
/// <summary>
/// Throws ArgumentOutOfRangeException if the value is greater than or equal
/// to the given upper limit.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="valueToCheck">The value of the parameter being validated.</param>
/// <param name="upperLimit">The upper limit which the value should be less than.</param>
public static void IsLessThan(
string parameterName,
int valueToCheck,
int upperLimit)
{
if (valueToCheck >= upperLimit)
{
throw new ArgumentOutOfRangeException(
parameterName,
valueToCheck,
string.Format(
"Value is greater than or equal to {0}",
upperLimit));
}
}
/// <summary>
/// Throws ArgumentOutOfRangeException if the value is less than or equal
/// to the given lower limit.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="valueToCheck">The value of the parameter being validated.</param>
/// <param name="lowerLimit">The lower limit which the value should be greater than.</param>
public static void IsGreaterThan(
string parameterName,
int valueToCheck,
int lowerLimit)
{
if (valueToCheck < lowerLimit)
{
throw new ArgumentOutOfRangeException(
parameterName,
valueToCheck,
string.Format(
"Value is less than or equal to {0}",
lowerLimit));
}
}
/// <summary>
/// Throws ArgumentException if the value is equal to the undesired value.
/// </summary>
/// <typeparam name="TValue">The type of value to be validated.</typeparam>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="undesiredValue">The value that valueToCheck should not equal.</param>
/// <param name="valueToCheck">The value of the parameter being validated.</param>
public static void IsNotEqual<TValue>(
string parameterName,
TValue valueToCheck,
TValue undesiredValue)
{
if (EqualityComparer<TValue>.Default.Equals(valueToCheck, undesiredValue))
{
throw new ArgumentException(
string.Format(
"The given value '{0}' should not equal '{1}'",
valueToCheck,
undesiredValue),
parameterName);
}
}
/// <summary>
/// Throws ArgumentException if the value is null or an empty string.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="valueToCheck">The value of the parameter being validated.</param>
public static void IsNotNullOrEmptyString(string parameterName, string valueToCheck)
{
if (string.IsNullOrEmpty(valueToCheck))
{
throw new ArgumentException(
"Parameter contains a null, empty, or whitespace string.",
parameterName);
}
}
/// <summary>
/// Throws ArgumentException if the value is null, an empty string,
/// or a string containing only whitespace.
/// </summary>
/// <param name="parameterName">The name of the parameter being validated.</param>
/// <param name="valueToCheck">The value of the parameter being validated.</param>
public static void IsNotNullOrWhitespaceString(string parameterName, string valueToCheck)
{
if (string.IsNullOrWhiteSpace(valueToCheck))
{
throw new ArgumentException(
"Parameter contains a null, empty, or whitespace string.",
parameterName);
}
}
}
}

View File

@@ -33,7 +33,13 @@
"System.Threading.Thread": "4.0.0",
"System.Runtime.Loader": "4.0.0",
"System.Composition": "1.0.31-beta-24326-02",
"Microsoft.Extensions.DependencyModel": "1.0.0"
"Microsoft.Extensions.DependencyModel": "1.0.0",
"Microsoft.SqlTools.Hosting": {
"target": "project"
},
"Microsoft.SqlTools.Credentials": {
"target": "project"
}
},
"frameworks": {
"netcoreapp1.0": {