// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. namespace WixToolset.Http { using System; using System.Collections.Generic; using System.Xml.Linq; using WixToolset.Data; using WixToolset.Extensibility; using WixToolset.Extensibility.Data; using WixToolset.Http.Symbols; /// /// The compiler for the WiX Toolset Http Extension. /// public sealed class HttpCompiler : BaseCompilerExtension { public override XNamespace Namespace => "http://wixtoolset.org/schemas/v4/wxs/http"; /// /// Processes an element for the Compiler. /// /// Source line number for the parent element. /// Parent element of element to process. /// Element to process. /// Extra information about the context in which this element is being parsed. public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context) { switch (parentElement.Name.LocalName) { case "ServiceInstall": var serviceInstallName = context["ServiceInstallName"]; var serviceUser = String.IsNullOrEmpty(serviceInstallName) ? null : String.Concat("NT SERVICE\\", serviceInstallName); var serviceComponentId = context["ServiceInstallComponentId"]; switch (element.Name.LocalName) { case "UrlReservation": this.ParseUrlReservationElement(intermediate, section, element, serviceComponentId, serviceUser); break; default: this.ParseHelper.UnexpectedElement(parentElement, element); break; } break; case "Component": string componentId = context["ComponentId"]; switch (element.Name.LocalName) { case "SniSslCertificate": this.ParseCertificateElement(intermediate, section, element, componentId, CertificateType.SniSsl); break; case "SslCertificate": this.ParseCertificateElement(intermediate, section, element, componentId, CertificateType.IpSsl); break; case "UrlReservation": this.ParseUrlReservationElement(intermediate, section, element, componentId, null); break; default: this.ParseHelper.UnexpectedElement(parentElement, element); break; } break; default: this.ParseHelper.UnexpectedElement(parentElement, element); break; } } /// /// Parses a SniSsl element. /// /// The element to parse. /// Identifier of the component that owns this SNI SSL Certificate. private void ParseCertificateElement(Intermediate intermediate, IntermediateSection section, XElement node, string componentId, CertificateType type) { var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(node); Identifier id = null; string host = null; string port = null; string appId = null; string store = null; string thumbprint = null; var handleExisting = HandleExisting.Replace; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); break; case "AppId": appId = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); break; case "HandleExisting": var handleExistingValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); switch (handleExistingValue) { case "replace": handleExisting = HandleExisting.Replace; break; case "ignore": handleExisting = HandleExisting.Ignore; break; case "fail": handleExisting = HandleExisting.Fail; break; default: this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "HandleExisting", handleExistingValue, "replace", "ignore", "fail")); break; } break; case "Host": host = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); break; case "Port": port = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); break; case "Store": store = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); break; case "Thumbprint": thumbprint = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); break; default: this.ParseHelper.UnexpectedAttribute(node, attrib); break; } } else { this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, node, attrib); } } // Need the element ID for child element processing, so generate now if not authored. if (null == id) { var prefix = type == CertificateType.IpSsl ? "ips" : "sni"; id = this.ParseHelper.CreateIdentifier(prefix, componentId, host, port); } // Required attributes. if (null == host) { if (type == CertificateType.SniSsl) { this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Host")); } } else { if (type == CertificateType.IpSsl) { this.Messaging.Write(ErrorMessages.IllegalAttributeExceptOnElement(sourceLineNumbers, node.Name.LocalName, "Host", "SniSslCertificate")); } } if (null == port) { this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Port")); } if (null == thumbprint) { this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Thumbprint")); } // Parse unknown children. this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, node); if (!this.Messaging.EncounteredError) { section.AddSymbol(new HttpCertificateSymbol(sourceLineNumbers, id) { Host = host, Port = port, Thumbprint = thumbprint, AppId = appId, Store = store, HandleExisting = handleExisting, CertificateType = type, ComponentRef = componentId, }); this.ParseHelper.CreateCustomActionReference(sourceLineNumbers, section, "Wix6SchedHttpCertificatesInstall", this.Context.Platform, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64); this.ParseHelper.CreateCustomActionReference(sourceLineNumbers, section, "Wix6SchedHttpCertificatesUninstall", this.Context.Platform, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64); } } /// /// Parses a UrlReservation element. /// /// The element to parse. /// Identifier of the component that owns this URL reservation. /// The security principal of the parent element (null if nested under Component). private void ParseUrlReservationElement(Intermediate intermediate, IntermediateSection section, XElement node, string componentId, string securityPrincipal) { var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(node); Identifier id = null; var handleExisting = HandleExisting.Replace; string sddl = null; string url = null; var foundACE = false; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); break; case "HandleExisting": var handleExistingValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); switch (handleExistingValue) { case "replace": handleExisting = HandleExisting.Replace; break; case "ignore": handleExisting = HandleExisting.Ignore; break; case "fail": handleExisting = HandleExisting.Fail; break; default: this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "HandleExisting", handleExistingValue, "replace", "ignore", "fail")); break; } break; case "Sddl": sddl = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); break; case "Url": url = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); break; default: this.ParseHelper.UnexpectedAttribute(node, attrib); break; } } else { this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, node, attrib); } } // Need the element ID for child element processing, so generate now if not authored. if (null == id) { id = this.ParseHelper.CreateIdentifier("url", componentId, securityPrincipal, url); } // Parse UrlAce children. foreach (var child in node.Elements()) { if (this.Namespace == child.Name.Namespace) { switch (child.Name.LocalName) { case "UrlAce": if (null != sddl) { this.Messaging.Write(ErrorMessages.IllegalParentAttributeWhenNested(sourceLineNumbers, "UrlReservation", "Sddl", "UrlAce")); } else { foundACE = true; this.ParseUrlAceElement(intermediate, section, child, id.Id, securityPrincipal); } break; default: this.ParseHelper.UnexpectedElement(node, child); break; } } else { this.ParseHelper.ParseExtensionElement(this.Context.Extensions, intermediate, section, node, child); } } // Url is required. if (null == url) { this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Url")); } // Security is required. if (null == sddl && !foundACE) { this.Messaging.Write(HttpErrors.NoSecuritySpecified(sourceLineNumbers)); } if (!this.Messaging.EncounteredError) { section.AddSymbol(new WixHttpUrlReservationSymbol(sourceLineNumbers, id) { HandleExisting = handleExisting, Sddl = sddl, Url = url, ComponentRef = componentId, }); this.ParseHelper.CreateCustomActionReference(sourceLineNumbers, section, "Wix4SchedHttpUrlReservationsInstall", this.Context.Platform, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64); this.ParseHelper.CreateCustomActionReference(sourceLineNumbers, section, "Wix4SchedHttpUrlReservationsUninstall", this.Context.Platform, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64); } } /// /// Parses a UrlAce element. /// /// The element to parse. /// The URL reservation ID. /// The default security principal. private void ParseUrlAceElement(Intermediate intermediate, IntermediateSection section, XElement node, string urlReservationId, string defaultSecurityPrincipal) { var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(node); Identifier id = null; var securityPrincipal = defaultSecurityPrincipal; var rights = HttpConstants.GENERIC_ALL; string rightsValue = null; foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { case "Id": id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); break; case "SecurityPrincipal": securityPrincipal = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); break; case "Rights": rightsValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); switch (rightsValue) { case "all": rights = HttpConstants.GENERIC_ALL; break; case "delegate": rights = HttpConstants.GENERIC_WRITE; break; case "register": rights = HttpConstants.GENERIC_EXECUTE; break; default: this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Rights", rightsValue, "all", "delegate", "register")); break; } break; default: this.ParseHelper.UnexpectedAttribute(node, attrib); break; } } else { this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, node, attrib); } } // Generate Id now if not authored. if (null == id) { id = this.ParseHelper.CreateIdentifier("ace", urlReservationId, securityPrincipal, rightsValue); } this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, node); // SecurityPrincipal is required. if (null == securityPrincipal) { this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SecurityPrincipal")); } if (!this.Messaging.EncounteredError) { section.AddSymbol(new WixHttpUrlAceSymbol(sourceLineNumbers, id) { WixHttpUrlReservationRef = urlReservationId, SecurityPrincipal = securityPrincipal, Rights = rights, }); } } } }