/// <summary> /// Retrieves web.config for a given Azure Website. /// </summary> /// <returns>XML document with the contents of web.config, or <c>null</c> if it could not be retrieved.</returns> private async Task <XDocument> GetWebConfig(AzureWebSiteInfo webSite) { var publishXml = await GetPublishXml(webSite); if (publishXml == null) { return(null); } // Get FTP publish URL and credentials from publish settings. var publishProfile = publishXml.Elements("publishData").Elements("publishProfile").FirstOrDefault(el => (string)el.Attribute("publishMethod") == "FTP"); if (publishProfile == null) { return(null); } var publishUrl = (string)publishProfile.Attribute("publishUrl"); var userName = (string)publishProfile.Attribute("userName"); var userPwd = (string)publishProfile.Attribute("userPWD"); if (publishUrl == null || userName == null || userPwd == null) { return(null); } // Get web.config for the site via FTP. if (!publishUrl.EndsWith("/", StringComparison.Ordinal)) { publishUrl += "/"; } publishUrl += "web.config"; if (!Uri.TryCreate(publishUrl, UriKind.Absolute, out var webConfigUri)) { return(null); } var request = WebRequest.Create(webConfigUri) as FtpWebRequest; // Check that this is actually an FTP request, in case we get some valid but weird URL back. if (request == null) { return(null); } request.Credentials = new NetworkCredential(userName, userPwd); using (var response = await request.GetResponseAsync()) using (var stream = response.GetResponseStream()) { // There is no XDocument.LoadAsync, but we want the networked I/O at least to be async, even if parsing is not. var xmlData = new MemoryStream(); await stream.CopyToAsync(xmlData); xmlData.Position = 0; return(XDocument.Load(xmlData)); } }
private async Task <bool> AttachWorker(AzureWebSiteInfo webSite) { using (new WaitDialog("Azure remote debugging", "Attaching to Azure web site at " + webSite.Uri, _serviceProvider, showProgress: true)) { // Get path (relative to site URL) for the debugger endpoint. XDocument webConfig; try { webConfig = await GetWebConfig(webSite); } catch (WebException) { return(false); } catch (IOException) { return(false); } catch (XmlException) { return(false); } if (webConfig == null) { return(false); } var path = (from add in webConfig.Elements("configuration").Elements("system.webServer").Elements("handlers").Elements("add") let type = (string)add.Attribute("type") where type != null let components = type.Split(',') where components[0].Trim() == "Microsoft.PythonTools.Debugger.WebSocketProxy" select(string) add.Attribute("path") ).FirstOrDefault(); if (path == null) { return(false); } var secret = (from add in webConfig.Elements("configuration").Elements("appSettings").Elements("add") where (string)add.Attribute("key") == "WSGI_PTVSD_SECRET" select(string) add.Attribute("value") ).FirstOrDefault(); if (secret == null) { return(false); } try { AttachDebugger(new UriBuilder(webSite.Uri) { Scheme = "wss", Port = -1, Path = path, UserName = secret }.Uri); } catch (Exception ex) { // If we got to this point, the attach logic in debug engine will catch exceptions, display proper error message and // ask the user to retry, so the only case where we actually get here is if user canceled on error. If this is the case, // we don't want to pop any additional error messages, so always return true, but log the error in the Output window. var output = OutputWindowRedirector.GetGeneral(_serviceProvider); output.WriteErrorLine("Failed to attach to Azure web site: " + ex.Message); output.ShowAndActivate(); } return(true); } }
private async Task <bool> AttachWorker(AzureWebSiteInfo webSite) { using (new WaitDialog( Project.SR.GetString(Project.SR.AzureRemoteDebugWaitCaption), Project.SR.GetString(Project.SR.AzureRemoteDebugWaitMessage, webSite.Uri), NodejsPackage.Instance, showProgress: true)) { // Get path (relative to site URL) for the debugger endpoint. XDocument webConfig; try { webConfig = await GetWebConfig(webSite); } catch (WebException) { return(false); } catch (IOException) { return(false); } catch (XmlException) { return(false); } if (webConfig == null) { return(false); } var path = (from add in webConfig.Elements("configuration").Elements("system.webServer").Elements("handlers").Elements("add") let type = (string)add.Attribute("type") where type != null let components = type.Split(',') where components[0].Trim() == "Microsoft.NodejsTools.Debugger.WebSocketProxy" select(string) add.Attribute("path") ).FirstOrDefault(); if (path == null) { return(false); } try { AttachDebugger(new UriBuilder(webSite.Uri) { Scheme = "wss", Port = -1, Path = path }.Uri); } catch (Exception ex) { // If we got to this point, the attach logic in debug engine will catch exceptions, display proper error message and // ask the user to retry, so the only case where we actually get here is if user canceled on error. If this is the case, // we don't want to pop any additional error messages, so always return true, but log the error in the Output window. var output = OutputWindowRedirector.GetGeneral(NodejsPackage.Instance); output.WriteErrorLine(Project.SR.GetString(Project.SR.AzureRemoveDebugCouldNotAttachToWebsiteExceptionErrorMessage, ex.Message)); output.ShowAndActivate(); } return(true); } }
/// <summary> /// Retrieves the publish settings file (.pubxml) for the given Azure web site. /// </summary> /// <returns>XML document with the contents of .pubxml, or <c>null</c> if it could not be retrieved.</returns> private async Task<XDocument> GetPublishXml(AzureWebSiteInfo webSiteInfo) { // To build the publish settings request URL, we need to know subscription ID, site name, and web region to which it belongs, // but we only have subscription ID and the public URL of the site at this point. Use the Azure web site service to look up // the site from those two, and retrieve the missing info. // The code below must avoid doing anything that would result in types from the Azure Tools contract assemblies being // referenced in any way in signatures of any members of any classes in this assembly. Because the contract assembly can // be be missing (if Azure SDK is not installed), such references will cause Reflection to break, which will break MEF // and block all our MEF exports. The main danger is classes generated by the compiler to support constructs such as // lambdas and await. To that extent, the following are not allowed in the code below: // // - local variables of Azure types (become fields if captured by a lambda or used across await); // - lambda arguments of Azure types (become arguments on generated method), and LINQ expressions that would implicitly // produce such lambdas; // - await on a Task that has result of an Azure type (produces a field of type TaskAwaiter<TResult>); // // To make it easier to verify, "var" and LINQ syntactic sugar should be avoided completely when dealing with Azure interfaces, // and all lambdas should have the types of arguments explicitly specified. For "await", cast the return type of any // Azure-type-returning async method to untyped Task first before awaiting, then use an explicit cast to Task<T> to read Result. // The only mentions of Azure types in the body of the method should be in casts. object webSiteServices = _serviceProvider.GetService(typeof(IVsAzureServices)); if (webSiteServices == null) { return null; } object webSiteService = ((IVsAzureServices)webSiteServices).GetAzureWebSitesService(); if (webSiteService == null) { return null; } Task getSubscriptionsAsyncTask = (Task)((IAzureWebSitesService)webSiteService).GetSubscriptionsAsync(); await getSubscriptionsAsyncTask; IEnumerable<object> subscriptions = ((Task<List<IAzureSubscription>>)getSubscriptionsAsyncTask).Result; object subscription = subscriptions.FirstOrDefault((object sub) => ((IAzureSubscription)sub).SubscriptionId == webSiteInfo.SubscriptionId); if (subscription == null) { return null; } Task getResourcesAsyncTask = (Task)((IAzureSubscription)subscription).GetResourcesAsync(false); await getResourcesAsyncTask; IEnumerable<object> resources = ((Task<List<IAzureResource>>)getResourcesAsyncTask).Result; object webSite = resources.FirstOrDefault((object res) => { IAzureWebSite ws = res as IAzureWebSite; if (ws == null) { return false; } Uri browseUri; Uri.TryCreate(ws.BrowseURL, UriKind.Absolute, out browseUri); return browseUri != null && browseUri.Equals(webSiteInfo.Uri); }); if (webSite == null) { return null; } // Prepare a web request to get the publish settings. // See http://msdn.microsoft.com/en-us/library/windowsazure/dn166996.aspx string requestPath = string.Format( "{0}/services/WebSpaces/{1}/sites/{2}/publishxml", ((IAzureSubscription)subscription).SubscriptionId, ((IAzureWebSite)webSite).WebSpace, ((IAzureWebSite)webSite).Name); Uri requestUri = new Uri(((IAzureSubscription)subscription).ServiceManagementEndpointUri, requestPath); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri); request.Method = "GET"; request.ContentType = "application/xml"; request.Headers.Add("x-ms-version", "2010-10-28"); // Set up authentication for the request, depending on whether the associated subscription context is // account-based or certificate-based. object context = ((IAzureSubscription)subscription).AzureCredentials; if (context is IAzureAuthenticationCertificateSubscriptionContext) { X509Certificate2 cert = await ((IAzureAuthenticationCertificateSubscriptionContext)context).AuthenticationCertificate.GetCertificateFromStoreAsync(); request.ClientCertificates.Add(cert); } else if (context is IAzureUserAccountSubscriptionContext) { string authHeader = await ((IAzureUserAccountSubscriptionContext)context).GetAuthenticationHeaderAsync(false); request.Headers.Add(HttpRequestHeader.Authorization, authHeader); } else { return null; } using (WebResponse response = await request.GetResponseAsync()) using (Stream stream = response.GetResponseStream()) { // There is no XDocument.LoadAsync, but we want the networked I/O at least to be async, even if parsing is not. Stream xmlData = new MemoryStream(); await stream.CopyToAsync(xmlData); xmlData.Position = 0; return XDocument.Load(xmlData); } }
/// <summary> /// Retrieves web.config for a given Azure web site. /// </summary> /// <returns>XML document with the contents of web.config, or <c>null</c> if it could not be retrieved.</returns> private async Task<XDocument> GetWebConfig(AzureWebSiteInfo webSite) { var publishXml = await GetPublishXml(webSite); if (publishXml == null) { return null; } // Get FTP publish URL and credentials from publish settings. var publishProfile = publishXml.Elements("publishData").Elements("publishProfile").FirstOrDefault(el => (string)el.Attribute("publishMethod") == "FTP"); if (publishProfile == null) { return null; } var publishUrl = (string)publishProfile.Attribute("publishUrl"); var userName = (string)publishProfile.Attribute("userName"); var userPwd = (string)publishProfile.Attribute("userPWD"); if (publishUrl == null || userName == null || userPwd == null) { return null; } // Get web.config for the site via FTP. if (!publishUrl.EndsWith("/")) { publishUrl += "/"; } publishUrl += "web.config"; Uri webConfigUri; if (!Uri.TryCreate(publishUrl, UriKind.Absolute, out webConfigUri)) { return null; } var request = WebRequest.Create(webConfigUri) as FtpWebRequest; // Check that this is actually an FTP request, in case we get some valid but weird URL back. if (request == null) { return null; } request.Credentials = new NetworkCredential(userName, userPwd); using (var response = await request.GetResponseAsync()) using (var stream = response.GetResponseStream()) { // There is no XDocument.LoadAsync, but we want the networked I/O at least to be async, even if parsing is not. var xmlData = new MemoryStream(); await stream.CopyToAsync(xmlData); xmlData.Position = 0; return XDocument.Load(xmlData); } }
private async Task<bool> AttachWorker(AzureWebSiteInfo webSite) { using (new WaitDialog("Azure remote debugging", "Attaching to Azure web site at " + webSite.Uri, _serviceProvider, showProgress: true)) { // Get path (relative to site URL) for the debugger endpoint. XDocument webConfig; try { webConfig = await GetWebConfig(webSite); } catch (WebException) { return false; } catch (IOException) { return false; } catch (XmlException) { return false; } if (webConfig == null) { return false; } var path = (from add in webConfig.Elements("configuration").Elements("system.webServer").Elements("handlers").Elements("add") let type = (string)add.Attribute("type") where type != null let components = type.Split(',') where components[0].Trim() == "Microsoft.PythonTools.Debugger.WebSocketProxy" select (string)add.Attribute("path") ).FirstOrDefault(); if (path == null) { return false; } var secret = (from add in webConfig.Elements("configuration").Elements("appSettings").Elements("add") where (string)add.Attribute("key") == "WSGI_PTVSD_SECRET" select (string)add.Attribute("value") ).FirstOrDefault(); if (secret == null) { return false; } try { AttachDebugger(new UriBuilder(webSite.Uri) { Scheme = "wss", Port = -1, Path = path, UserName = secret }.Uri); } catch (Exception ex) { // If we got to this point, the attach logic in debug engine will catch exceptions, display proper error message and // ask the user to retry, so the only case where we actually get here is if user canceled on error. If this is the case, // we don't want to pop any additional error messages, so always return true, but log the error in the Output window. var output = OutputWindowRedirector.GetGeneral(_serviceProvider); output.WriteErrorLine("Failed to attach to Azure web site: " + ex.Message); output.ShowAndActivate(); } return true; } }
/// <summary> /// Retrieves the publish settings file (.pubxml) for the given Azure Website. /// </summary> /// <returns>XML document with the contents of .pubxml, or <c>null</c> if it could not be retrieved.</returns> private async Task <XDocument> GetPublishXml(AzureWebSiteInfo webSiteInfo) { // To build the publish settings request URL, we need to know subscription ID, site name, and web region to which it belongs, // but we only have subscription ID and the public URL of the site at this point. Use the Azure Website service to look up // the site from those two, and retrieve the missing info. IVsAzureServices webSiteServices = new VsAzureServicesShim(NodejsPackage.GetGlobalService(_azureServicesType)); if (webSiteServices == null) { return(null); } var webSiteService = webSiteServices.GetAzureWebSitesService(); if (webSiteService == null) { return(null); } var subscriptions = await webSiteService.GetSubscriptionsAsync(); var subscription = subscriptions.FirstOrDefault(sub => sub.SubscriptionId == webSiteInfo.SubscriptionId); if (subscription == null) { return(null); } var resources = await subscription.GetResourcesAsync(false); var webSite = resources.OfType <IAzureWebSite>().FirstOrDefault(ws => { Uri browseUri; Uri.TryCreate(ws.BrowseURL, UriKind.Absolute, out browseUri); return(browseUri != null && browseUri.Equals(webSiteInfo.Uri)); }); if (webSite == null) { return(null); } // Prepare a web request to get the publish settings. // See http://msdn.microsoft.com/en-us/library/windowsazure/dn166996.aspx string requestPath = string.Format(CultureInfo.InvariantCulture, "{0}/services/WebSpaces/{1}/sites/{2}/publishxml", subscription.SubscriptionId, webSite.WebSpace, webSite.Name); Uri requestUri = new Uri(((IAzureSubscription)subscription).ServiceManagementEndpointUri, requestPath); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri); request.Method = "GET"; request.ContentType = "application/xml"; request.Headers.Add("x-ms-version", "2010-10-28"); // Set up authentication for the request, depending on whether the associated subscription context is // account-based or certificate-based. object context = subscription.AzureCredentials; var certContext = context as IAzureAuthenticationCertificateSubscriptionContext; if (certContext != null) { var cert = await certContext.AuthenticationCertificate.GetCertificateFromStoreAsync(); request.ClientCertificates.Add(cert); } else { var accountCountext = context as IAzureUserAccountSubscriptionContext; if (accountCountext != null) { string authHeader = await accountCountext.GetAuthenticationHeaderAsync(false); request.Headers.Add(HttpRequestHeader.Authorization, authHeader); } else { return(null); } } using (WebResponse response = await request.GetResponseAsync()) using (Stream stream = response.GetResponseStream()) { // There is no XDocument.LoadAsync, but we want the networked I/O at least to be async, even if parsing is not. Stream xmlData = new MemoryStream(); await stream.CopyToAsync(xmlData); xmlData.Position = 0; return(XDocument.Load(xmlData)); } }
/// <summary> /// Retrieves the publish settings file (.pubxml) for the given Azure Website. /// </summary> /// <returns>XML document with the contents of .pubxml, or <c>null</c> if it could not be retrieved.</returns> private async Task<XDocument> GetPublishXml(AzureWebSiteInfo webSiteInfo) { // To build the publish settings request URL, we need to know subscription ID, site name, and web region to which it belongs, // but we only have subscription ID and the public URL of the site at this point. Use the Azure Website service to look up // the site from those two, and retrieve the missing info. IVsAzureServices webSiteServices = new VsAzureServicesShim(NodejsPackage.GetGlobalService(_azureServicesType)); if (webSiteServices == null) { return null; } var webSiteService = webSiteServices.GetAzureWebSitesService(); if (webSiteService == null) { return null; } var subscriptions = await webSiteService.GetSubscriptionsAsync(); var subscription = subscriptions.FirstOrDefault(sub => sub.SubscriptionId == webSiteInfo.SubscriptionId); if (subscription == null) { return null; } var resources = await subscription.GetResourcesAsync(false); var webSite = resources.OfType<IAzureWebSite>().FirstOrDefault(ws => { Uri browseUri; Uri.TryCreate(ws.BrowseURL, UriKind.Absolute, out browseUri); return browseUri != null && browseUri.Equals(webSiteInfo.Uri); }); if (webSite == null) { return null; } // Prepare a web request to get the publish settings. // See http://msdn.microsoft.com/en-us/library/windowsazure/dn166996.aspx string requestPath = string.Format( "{0}/services/WebSpaces/{1}/sites/{2}/publishxml", subscription.SubscriptionId, webSite.WebSpace, webSite.Name); Uri requestUri = new Uri(((IAzureSubscription)subscription).ServiceManagementEndpointUri, requestPath); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri); request.Method = "GET"; request.ContentType = "application/xml"; request.Headers.Add("x-ms-version", "2010-10-28"); // Set up authentication for the request, depending on whether the associated subscription context is // account-based or certificate-based. object context = subscription.AzureCredentials; var certContext = context as IAzureAuthenticationCertificateSubscriptionContext; if (certContext != null) { var cert = await certContext.AuthenticationCertificate.GetCertificateFromStoreAsync(); request.ClientCertificates.Add(cert); } else { var accountCountext = context as IAzureUserAccountSubscriptionContext; if (accountCountext != null) { string authHeader = await accountCountext.GetAuthenticationHeaderAsync(false); request.Headers.Add(HttpRequestHeader.Authorization, authHeader); } else { return null; } } using (WebResponse response = await request.GetResponseAsync()) using (Stream stream = response.GetResponseStream()) { // There is no XDocument.LoadAsync, but we want the networked I/O at least to be async, even if parsing is not. Stream xmlData = new MemoryStream(); await stream.CopyToAsync(xmlData); xmlData.Position = 0; return XDocument.Load(xmlData); } }
/// <summary> /// Retrieves the publish settings file (.pubxml) for the given Azure web site. /// </summary> /// <returns>XML document with the contents of .pubxml, or <c>null</c> if it could not be retrieved.</returns> private async Task <XDocument> GetPublishXml(AzureWebSiteInfo webSiteInfo) { // To build the publish settings request URL, we need to know subscription ID, site name, and web region to which it belongs, // but we only have subscription ID and the public URL of the site at this point. Use the Azure web site service to look up // the site from those two, and retrieve the missing info. // The code below must avoid doing anything that would result in types from the Azure Tools contract assemblies being // referenced in any way in signatures of any members of any classes in this assembly. Because the contract assembly can // be be missing (if Azure SDK is not installed), such references will cause Reflection to break, which will break MEF // and block all our MEF exports. The main danger is classes generated by the compiler to support constructs such as // lambdas and await. To that extent, the following are not allowed in the code below: // // - local variables of Azure types (become fields if captured by a lambda or used across await); // - lambda arguments of Azure types (become arguments on generated method), and LINQ expressions that would implicitly // produce such lambdas; // - await on a Task that has result of an Azure type (produces a field of type TaskAwaiter<TResult>); // // To make it easier to verify, "var" and LINQ syntactic sugar should be avoided completely when dealing with Azure interfaces, // and all lambdas should have the types of arguments explicitly specified. For "await", cast the return type of any // Azure-type-returning async method to untyped Task first before awaiting, then use an explicit cast to Task<T> to read Result. // The only mentions of Azure types in the body of the method should be in casts. object webSiteServices = _serviceProvider.GetService(typeof(IVsAzureServices)); if (webSiteServices == null) { return(null); } object webSiteService = ((IVsAzureServices)webSiteServices).GetAzureWebSitesService(); if (webSiteService == null) { return(null); } Task getSubscriptionsAsyncTask = (Task)((IAzureWebSitesService)webSiteService).GetSubscriptionsAsync(); await getSubscriptionsAsyncTask; IEnumerable <object> subscriptions = ((Task <List <IAzureSubscription> >)getSubscriptionsAsyncTask).Result; object subscription = subscriptions.FirstOrDefault((object sub) => ((IAzureSubscription)sub).SubscriptionId == webSiteInfo.SubscriptionId); if (subscription == null) { return(null); } Task getResourcesAsyncTask = (Task)((IAzureSubscription)subscription).GetResourcesAsync(false); await getResourcesAsyncTask; IEnumerable <object> resources = ((Task <List <IAzureResource> >)getResourcesAsyncTask).Result; object webSite = resources.FirstOrDefault((object res) => { IAzureWebSite ws = res as IAzureWebSite; if (ws == null) { return(false); } Uri browseUri; Uri.TryCreate(ws.BrowseURL, UriKind.Absolute, out browseUri); return(browseUri != null && browseUri.Equals(webSiteInfo.Uri)); }); if (webSite == null) { return(null); } // Prepare a web request to get the publish settings. // See http://msdn.microsoft.com/en-us/library/windowsazure/dn166996.aspx string requestPath = string.Format( "{0}/services/WebSpaces/{1}/sites/{2}/publishxml", ((IAzureSubscription)subscription).SubscriptionId, ((IAzureWebSite)webSite).WebSpace, ((IAzureWebSite)webSite).Name); Uri requestUri = new Uri(((IAzureSubscription)subscription).ServiceManagementEndpointUri, requestPath); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri); request.Method = "GET"; request.ContentType = "application/xml"; request.Headers.Add("x-ms-version", "2010-10-28"); // Set up authentication for the request, depending on whether the associated subscription context is // account-based or certificate-based. object context = ((IAzureSubscription)subscription).AzureCredentials; if (context is IAzureAuthenticationCertificateSubscriptionContext) { X509Certificate2 cert = await((IAzureAuthenticationCertificateSubscriptionContext)context).AuthenticationCertificate.GetCertificateFromStoreAsync(); request.ClientCertificates.Add(cert); } else if (context is IAzureUserAccountSubscriptionContext) { string authHeader = await((IAzureUserAccountSubscriptionContext)context).GetAuthenticationHeaderAsync(false); request.Headers.Add(HttpRequestHeader.Authorization, authHeader); } else { return(null); } using (WebResponse response = await request.GetResponseAsync()) using (Stream stream = response.GetResponseStream()) { // There is no XDocument.LoadAsync, but we want the networked I/O at least to be async, even if parsing is not. Stream xmlData = new MemoryStream(); await stream.CopyToAsync(xmlData); xmlData.Position = 0; return(XDocument.Load(xmlData)); } }
private async Task<bool> AttachWorker(AzureWebSiteInfo webSite) { using (new WaitDialog( Resources.AzureRemoteDebugWaitCaption, string.Format(CultureInfo.CurrentCulture, Resources.AzureRemoteDebugWaitMessage, webSite.Uri), NodejsPackage.Instance, showProgress: true)) { // Get path (relative to site URL) for the debugger endpoint. XDocument webConfig; try { webConfig = await GetWebConfig(webSite); } catch (WebException) { return false; } catch (IOException) { return false; } catch (XmlException) { return false; } if (webConfig == null) { return false; } var path = (from add in webConfig.Elements("configuration").Elements("system.webServer").Elements("handlers").Elements("add") let type = (string)add.Attribute("type") where type != null let components = type.Split(',') where components[0].Trim() == "Microsoft.NodejsTools.Debugger.WebSocketProxy" select (string)add.Attribute("path") ).FirstOrDefault(); if (path == null) { return false; } try { AttachDebugger(new UriBuilder(webSite.Uri) { Scheme = "wss", Port = -1, Path = path }.Uri); } catch (Exception ex) { // If we got to this point, the attach logic in debug engine will catch exceptions, display proper error message and // ask the user to retry, so the only case where we actually get here is if user canceled on error. If this is the case, // we don't want to pop any additional error messages, so always return true, but log the error in the Output window. var output = OutputWindowRedirector.GetGeneral(NodejsPackage.Instance); output.WriteErrorLine(string.Format(CultureInfo.CurrentCulture, Resources.AzureRemoveDebugCouldNotAttachToWebsiteExceptionErrorMessage, ex.Message)); output.ShowAndActivate(); } return true; } }