mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-08 17:25:08 -05:00
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:
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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<MyService>
|
||||
/// {
|
||||
/// 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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
1867
src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
@@ -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
1084
src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
|
||||
@@ -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<{1}></source>
|
||||
<target state="new">Service of type {0} cannot be created by ExtensionLoader<{1}></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>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user