/// <summary> /// Handles the process of rendering an asset. /// </summary> /// <param name="request">The HTTP request.</param> /// <param name="response">The HTTP response.</param> private void HandleAssetRenderRequest(HttpListenerRequest request, HttpListenerResponse response) { // Try to demand policy // Navigate asset AppletAsset navigateAsset = null; var appletManagerService = ApplicationContext.Current.GetService <IAppletManagerService>(); String appletPath = request.Url.AbsolutePath.ToLower(); if (!this.m_cacheApplets.TryGetValue(appletPath, out navigateAsset)) { navigateAsset = appletManagerService.Applets.ResolveAsset(appletPath); if (navigateAsset == null) { throw new FileNotFoundException(request.RawUrl); } lock (m_lockObject) { if (!this.m_cacheApplets.ContainsKey(appletPath)) { this.m_cacheApplets.Add(appletPath, navigateAsset); } } } #if DEBUG response.AddHeader("Cache-Control", "no-cache"); #else if (request.Url.ToString().EndsWith(".js") || request.Url.ToString().EndsWith(".css") || request.Url.ToString().EndsWith(".png") || request.Url.ToString().EndsWith(".woff2")) { response.AddHeader("Cache-Control", "public"); response.AddHeader("Expires", DateTime.UtcNow.AddHours(1).ToString("ddd, dd MMM yyyy HH:mm:ss 'GMT'")); } else { response.AddHeader("Cache-Control", "no-cache"); } #endif // Navigate policy? if (navigateAsset.Policies != null) { foreach (var policy in navigateAsset.Policies) { new PolicyPermission(System.Security.Permissions.PermissionState.Unrestricted, policy).Demand(); } } response.ContentType = navigateAsset.MimeType; // Write asset var content = appletManagerService.Applets.RenderAssetContent(navigateAsset, CultureInfo.CurrentUICulture.TwoLetterISOLanguageName); response.AddHeader("Content-Encoding", "deflate"); using (var gzs = new DeflateStream(response.OutputStream, CompressionMode.Compress)) gzs.Write(content, 0, content.Length); }
/// <summary> /// Resolve asset /// </summary> public object ResolveAppletAsset(AppletAsset navigateAsset) { String itmPath = System.IO.Path.Combine( ApplicationContext.Current.Configuration.GetSection <AppletConfigurationSection>().AppletDirectory, "assets", navigateAsset.Manifest.Info.Id, navigateAsset.Name); return(File.ReadAllBytes(itmPath)); }
/// <summary> /// Injection for HTML headers /// </summary> public List <AssetScriptReference> GetLazyScripts(AppletAsset asset) { var htmlAsset = asset.Content as AppletAssetHtml; if (htmlAsset == null && this.Resolver != null) { htmlAsset = this.Resolver(asset) as AppletAssetHtml; } // Insert scripts & Styles List <AssetScriptReference> scriptRefs = new List <AssetScriptReference>(); if (htmlAsset == null) { return(scriptRefs); } scriptRefs.AddRange(htmlAsset.Script.Where(o => o.IsStatic == false)); // Content - SSI var includes = htmlAsset.Html.DescendantNodes().OfType <XComment>().Where(o => o?.Value?.Trim().StartsWith("#include virtual=\"") == true).ToList(); foreach (var inc in includes) { String assetName = inc.Value.Trim().Substring(18); // HACK: Should be a REGEX if (assetName.EndsWith("\"")) { assetName = assetName.Substring(0, assetName.Length - 1); } if (assetName == "content") { continue; } var includeAsset = this.ResolveAsset(assetName, asset); if (includeAsset != null) { scriptRefs.AddRange(this.GetLazyScripts(includeAsset)); } } // Re-write foreach (var itm in scriptRefs.Where(o => o.Reference.StartsWith("~"))) { itm.Reference = String.Format("/{0}/{1}", asset.Manifest.Info.Id, itm.Reference.Substring(2)); //itm.Value = itm.Value.Replace(APPLET_SCHEME, this.AppletBase).Replace(ASSET_SCHEME, this.AssetBase).Replace(DRAWABLE_SCHEME, this.DrawableBase); } return(scriptRefs.Distinct(new AssetScriptReferenceEqualityComparer()).ToList()); }
/// <summary> /// Resolve asset /// </summary> public object ResolveAppletAsset(AppletAsset navigateAsset) { String itmPath = System.IO.Path.Combine( ApplicationContext.Current.Configuration.GetSection <AppletConfigurationSection>().AppletDirectory, "assets", navigateAsset.Manifest.Info.Id, navigateAsset.Name); if (navigateAsset.MimeType == "text/javascript" || navigateAsset.MimeType == "text/css" || navigateAsset.MimeType == "application/json" || navigateAsset.MimeType == "text/json" || navigateAsset.MimeType == "text/xml") { var script = File.ReadAllText(itmPath); if (itmPath.Contains("santedb.js") || itmPath.Contains("santedb.min.js")) { script += this.GetShimMethods(); } return(script); } else { using (MemoryStream response = new MemoryStream()) using (var fs = File.OpenRead(itmPath)) { int br = 8096; byte[] buffer = new byte[8096]; while (br == 8096) { br = fs.Read(buffer, 0, 8096); response.Write(buffer, 0, br); } return(response.ToArray()); } } }
/// <summary> /// Injection for HTML headers /// </summary> private List <XElement> GetInjectionHeaders(AppletAsset asset, bool isUiContainer) { var htmlAsset = asset.Content as AppletAssetHtml; if (htmlAsset == null && this.Resolver != null) { htmlAsset = this.Resolver(asset) as AppletAssetHtml; } // Insert scripts & Styles List <XElement> headerInjection = new List <XElement>(); if (htmlAsset == null) { return(headerInjection); } // Inject special headers foreach (var itm in htmlAsset.Bundle) { var bundle = this.m_referenceBundles.Find(o => o.Name == itm); if (bundle == null) { throw new FileNotFoundException(String.Format("Bundle {0} not found", itm)); } headerInjection.AddRange(bundle.Content.SelectMany(o => o.HeaderElement)); } // All scripts if (isUiContainer) // IS A UI CONTAINER = ANGULAR UI REQUIRES ALL CONTROLLERS BE LOADED { return(this.ViewStateAssets.SelectMany(o => this.GetInjectionHeaders(o, false)).Distinct(new XElementEquityComparer()).ToList()); } else { foreach (var itm in htmlAsset.Script) { var incAsset = this.ResolveAsset(itm, asset); if (incAsset != null) { headerInjection.AddRange(new ScriptBundleContent(itm).HeaderElement); } else { throw new FileNotFoundException(String.Format("Asset {0} not found", itm)); } } } foreach (var itm in htmlAsset.Style) { var incAsset = this.ResolveAsset(itm, asset); if (incAsset != null) { headerInjection.AddRange(new StyleBundleContent(itm).HeaderElement); } else { throw new FileNotFoundException(String.Format("Asset {0} not found", itm)); } } // Content - SSI var includes = htmlAsset.Html.DescendantNodes().OfType <XComment>().Where(o => o?.Value?.Trim().StartsWith("#include virtual=\"") == true).ToList(); foreach (var inc in includes) { String assetName = inc.Value.Trim().Substring(18); // HACK: Should be a REGEX if (assetName.EndsWith("\"")) { assetName = assetName.Substring(0, assetName.Length - 1); } if (assetName == "content") { continue; } var includeAsset = this.ResolveAsset(assetName, asset); if (includeAsset != null) { headerInjection.AddRange(this.GetInjectionHeaders(includeAsset, isUiContainer)); } } // Re-write foreach (var itm in headerInjection.OfType <XElement>().SelectMany(o => o.Attributes()).Where(o => o.Value.StartsWith("~"))) { itm.Value = String.Format("/{0}/{1}", asset.Manifest.Info.Id, itm.Value.Substring(2)); //itm.Value = itm.Value.Replace(APPLET_SCHEME, this.AppletBase).Replace(ASSET_SCHEME, this.AssetBase).Replace(DRAWABLE_SCHEME, this.DrawableBase); } return(headerInjection.Distinct(new XElementEquityComparer()).ToList()); }
/// <summary> /// Render asset content /// </summary> public byte[] RenderAssetContent(AppletAsset asset, string preProcessLocalization = null) { // First, is there an object already byte[] cacheObject = null; string assetPath = String.Format("{0}?lang={1}", asset.ToString(), preProcessLocalization); if (this.CachePages && s_cache.TryGetValue(assetPath, out cacheObject)) { return(cacheObject); } // Resolve content var content = asset.Content; if (content == null && this.Resolver != null) { content = this.Resolver(asset); } if (content is String) // Content is a string { return(Encoding.UTF8.GetBytes(content as String)); } else if (content is byte[]) // Content is a binary asset { return(content as byte[]); } else if (content is XElement) // Content is XML { using (MemoryStream ms = new MemoryStream()) using (XmlWriter xw = XmlWriter.Create(ms)) { (content as XElement).WriteTo(xw); xw.Flush(); ms.Flush(); return(ms.ToArray()); } } else if (content is AppletAssetHtml) // Content is HTML { // Is the content HTML? var sourceAsset = content as AppletAssetHtml; var htmlAsset = new AppletAssetHtml() { Html = new XElement(sourceAsset.Html), Layout = sourceAsset.Layout, Script = new List <String>(sourceAsset.Script), Titles = new List <LocaleString>(sourceAsset.Titles), Style = new List <string>(sourceAsset.Style) }; XElement htmlContent = null; if (htmlAsset.Static) { htmlContent = htmlAsset.Html as XElement; } else { // Type of tag to render basic content switch (htmlAsset.Html.Name.LocalName) { case "html": // The content is a complete HTML page { htmlContent = htmlAsset.Html as XElement; var headerInjection = this.GetInjectionHeaders(asset, htmlContent.DescendantNodes().OfType <XElement>().Any(o => o.Name == xs_xhtml + "ui-view")); // STRIP - OPENIZJS references var xel = htmlContent.Descendants().OfType <XElement>().Where(o => o.Name == xs_xhtml + "script" && o.Attribute("src")?.Value.Contains("openiz") == true).ToArray(); var head = htmlContent.DescendantNodes().OfType <XElement>().FirstOrDefault(o => o.Name == xs_xhtml + "head"); if (head == null) { head = new XElement(xs_xhtml + "head"); htmlContent.Add(head); } head.Add(headerInjection.Where(o => !head.Elements(o.Name).Any(e => (e.Attributes("src") != null && (e.Attributes("src") == o.Attributes("src"))) || (e.Attributes("href") != null && (e.Attributes("href") == o.Attributes("href")))))); // head.Add(headerInjection); break; } case "body": // The content is an HTML Body element, we must inject the HTML header { htmlContent = htmlAsset.Html as XElement; // Inject special headers var headerInjection = this.GetInjectionHeaders(asset, htmlContent.DescendantNodes().OfType <XElement>().Any(o => o.Name == xs_xhtml + "ui-view")); // Render the bundles var bodyElement = htmlAsset.Html as XElement; htmlContent = new XElement(xs_xhtml + "html", new XAttribute("ng-app", asset.Name), new XElement(xs_xhtml + "head", headerInjection), bodyElement); } break; default: { if (String.IsNullOrEmpty(htmlAsset.Layout)) { htmlContent = htmlAsset.Html as XElement; } else { // Get the layout var layoutAsset = this.ResolveAsset(htmlAsset.Layout, asset); if (layoutAsset == null) { throw new FileNotFoundException(String.Format("Layout asset {0} not found", htmlAsset.Layout)); } using (MemoryStream ms = new MemoryStream(this.RenderAssetContent(layoutAsset, preProcessLocalization))) htmlContent = XDocument.Load(ms).FirstNode as XElement; // Find the <!--#include virtual="content" --> tag var contentNode = htmlContent.DescendantNodes().OfType <XComment>().SingleOrDefault(o => o.Value.Trim() == "#include virtual=\"content\""); if (contentNode != null) { contentNode.AddAfterSelf(htmlAsset.Html as XElement); contentNode.Remove(); } // Injection headers var headerInjection = this.GetInjectionHeaders(asset, htmlContent.DescendantNodes().OfType <XElement>().Any(o => o.Name == xs_xhtml + "ui-view")); var headElement = (htmlContent.Element(xs_xhtml + "head") as XElement); headElement?.Add(headerInjection.Where(o => !headElement.Elements(o.Name).Any(e => (e.Attributes("src") != null && (e.Attributes("src") == o.Attributes("src"))) || (e.Attributes("href") != null && (e.Attributes("href") == o.Attributes("href")))))); } } break; } // switch // Process data bindings var dataBindings = htmlContent.DescendantNodes().OfType <XElement>().Where(o => o.Name.LocalName == "select" && o.Attributes().Any(a => a.Name.Namespace == xs_binding)); foreach (var db in dataBindings) { // Get the databinding data XAttribute source = db.Attributes(xs_binding + "source").FirstOrDefault(), filter = db.Attributes(xs_binding + "filter").FirstOrDefault(), key = db.Attributes(xs_binding + "key").FirstOrDefault(), value = db.Attributes(xs_binding + "value").FirstOrDefault(), orderByDescending = db.Attributes(xs_binding + "orderByDescending").FirstOrDefault(), orderBy = db.Attributes(xs_binding + "orderBy").FirstOrDefault(); var locale = preProcessLocalization; int i = 0; var valueSelector = value?.Value; while (i++ < 2) { try { // Fall back to english? if (value != null) { valueSelector = value.Value.Replace("{{ locale }}", locale); } if (source == null || filter == null) { continue; } // First we want to build the filter Type imsiType = typeof(Patient).GetTypeInfo().Assembly.ExportedTypes.FirstOrDefault(o => o.GetTypeInfo().GetCustomAttribute <XmlRootAttribute>()?.ElementName == source.Value); if (imsiType == null) { continue; } var expressionBuilderMethod = typeof(QueryExpressionParser).GetGenericMethod(nameof(QueryExpressionParser.BuildLinqExpression), new Type[] { imsiType }, new Type[] { typeof(NameValueCollection) }); var filterList = NameValueCollection.ParseQueryString(filter.Value); var expr = expressionBuilderMethod.Invoke(null, new object[] { filterList }); var filterMethod = typeof(IEntitySourceProvider).GetGenericMethod("Query", new Type[] { imsiType }, new Type[] { expr.GetType() }); var dataSource = (filterMethod.Invoke(EntitySource.Current.Provider, new object[] { expr })); // Sort expression if (orderBy != null || orderByDescending != null) { var orderProperty = imsiType.GetRuntimeProperties().FirstOrDefault(o => o.GetCustomAttribute <JsonPropertyAttribute>()?.PropertyName == (orderBy ?? orderByDescending).Value); ParameterExpression orderExpr = Expression.Parameter(dataSource.GetType()); var orderBody = orderExpr.Sort(orderProperty.Name, orderBy == null ? SortOrderType.OrderByDescending : SortOrderType.OrderBy); dataSource = Expression.Lambda(orderBody, orderExpr).Compile().DynamicInvoke(dataSource); } // Render expression Delegate keyExpression = null, valueExpression = null, dataExpression = null; ParameterExpression parameter = Expression.Parameter(imsiType); if (key == null) { keyExpression = Expression.Lambda(Expression.MakeMemberAccess(parameter, imsiType.GetRuntimeProperty(nameof(IIdentifiedEntity.Key))), parameter).Compile(); } else { var rawExpr = new BindingExpressionVisitor().RewriteLambda(expressionBuilderMethod.Invoke(null, new object[] { NameValueCollection.ParseQueryString(key.Value + "=RemoveMe") }) as LambdaExpression); keyExpression = Expression.Lambda(new BindingExpressionVisitor().Visit(rawExpr.Body), rawExpr.Parameters).Compile(); } if (value == null) { valueExpression = Expression.Lambda(Expression.Call(parameter, imsiType.GetRuntimeMethod("ToString", new Type[] { })), parameter).Compile(); } else { var rawExpr = new BindingExpressionVisitor().RewriteLambda(expressionBuilderMethod.Invoke(null, new object[] { NameValueCollection.ParseQueryString(valueSelector + "=RemoveMe") }) as LambdaExpression); valueExpression = Expression.Lambda(rawExpr.Body, rawExpr.Parameters).Compile(); } // Creation of the options foreach (var itm in dataSource as IEnumerable) { var optAtt = new XElement(xs_xhtml + "option"); var keyValue = keyExpression.DynamicInvoke(itm); var valueValue = valueExpression.DynamicInvoke(itm)?.ToString(); if (String.IsNullOrEmpty(valueValue)) { continue; } optAtt.Add(new XAttribute("value", keyValue), new XText(valueValue)); foreach (var dataBinding in db.Attributes().Where(c => c.Name.ToString().StartsWith((xs_binding + "data-").ToString()))) { if (dataBinding != null) { dataExpression = Expression.Lambda(Expression.MakeMemberAccess(parameter, imsiType.GetRuntimeProperty(dataBinding.Value)), parameter).Compile(); var dataValue = dataExpression?.DynamicInvoke(itm)?.ToString(); if (string.IsNullOrEmpty(dataValue)) { continue; } optAtt.Add(new XAttribute(dataBinding.Name.LocalName, dataValue)); } } db.Add(optAtt); } break; } catch { if (locale == "en") { throw; // We can't fallback } locale = "en"; // fallback to english } } } // Now process SSI directives - <!--#include virtual="XXXXXXX" --> var includes = htmlContent.DescendantNodes().OfType <XComment>().Where(o => o?.Value?.Trim().StartsWith("#include virtual=\"") == true).ToList(); foreach (var inc in includes) { String assetName = inc.Value.Trim().Substring(18); // HACK: Should be a REGEX if (assetName.EndsWith("\"")) { assetName = assetName.Substring(0, assetName.Length - 1); } if (assetName == "content") { continue; } var includeAsset = this.ResolveAsset(assetName, asset); if (includeAsset == null) { inc.AddAfterSelf(new XElement(xs_xhtml + "strong", new XText(String.Format("{0} NOT FOUND", assetName)))); inc.Remove(); } else { using (MemoryStream ms = new MemoryStream(this.RenderAssetContent(includeAsset, preProcessLocalization))) { try { var xel = XDocument.Load(ms).Elements().First() as XElement; if (xel.Name == xs_xhtml + "html") { inc.AddAfterSelf(xel.Element(xs_xhtml + "body").Elements()); } else { //var headerInjection = this.GetInjectionHeaders(includeAsset); //var headElement = htmlContent.Element(xs_xhtml + "head"); //headElement?.Add(headerInjection.Where(o => !headElement.Elements(o.Name).Any(e => (e.Attributes("src") != null && (e.Attributes("src") == o.Attributes("src"))) || (e.Attributes("href") != null && (e.Attributes("href") == o.Attributes("href")))))); inc.AddAfterSelf(xel); } inc.Remove(); } catch (Exception e) { throw new XmlException($"Error in Asset: {includeAsset}", e); } } } } // Re-write foreach (var itm in htmlContent.DescendantNodes().OfType <XElement>().SelectMany(o => o.Attributes()).Where(o => o.Value.StartsWith("~"))) { itm.Value = String.Format("/{0}/{1}", asset.Manifest.Info.Id, itm.Value.Substring(2)); //itm.Value = itm.Value.Replace(APPLET_SCHEME, this.AppletBase).Replace(ASSET_SCHEME, this.AssetBase).Replace(DRAWABLE_SCHEME, this.DrawableBase); } // Render Title var headTitle = htmlContent.DescendantNodes().OfType <XElement>().FirstOrDefault(o => o.Name == xs_xhtml + "head"); var title = htmlAsset.GetTitle(preProcessLocalization); if (headTitle != null && !String.IsNullOrEmpty(title)) { headTitle.Add(new XElement(xs_xhtml + "title", new XText(title))); } } // Render out the content using (StringWriter sw = new StringWriter()) using (XmlWriter xw = XmlWriter.Create(sw, new XmlWriterSettings() { OmitXmlDeclaration = true })) { htmlContent.WriteTo(xw); xw.Flush(); String retVal = sw.ToString(); if (!String.IsNullOrEmpty(preProcessLocalization)) { var assetString = this.GetStrings(preProcessLocalization); retVal = this.m_localizationRegex.Replace(retVal, (m) => assetString.FirstOrDefault(o => o.Key == m.Groups[1].Value).Value ?? m.Groups[1].Value); } var byteData = Encoding.UTF8.GetBytes(retVal); // Add to cache lock (s_syncLock) if (!s_cache.ContainsKey(assetPath)) { s_cache.Add(assetPath, byteData); } return(byteData); } } else { return(null); } }
/// <summary> /// Resolve the asset /// </summary> public AppletAsset ResolveAsset(String assetPath, AppletAsset relative = null, String language = null) { if (assetPath == null) { return(null); } // Is the asset start with ~ if (assetPath.StartsWith("~")) { assetPath = "/" + relative.Manifest.Info.Id + assetPath.Substring(1); } Uri path = null; if (!Uri.TryCreate(assetPath, UriKind.RelativeOrAbsolute, out path)) { return(null); } else { AppletManifest resolvedManifest = null; String pathLeft = path.IsAbsoluteUri ? path.AbsolutePath.Substring(1) : path.OriginalString.StartsWith("/") ? path.OriginalString.Substring(1) : path.OriginalString; // Is the host specified? if (path.IsAbsoluteUri && !String.IsNullOrEmpty(path.Host)) { resolvedManifest = this.FirstOrDefault(o => o.Info.Id == path.Host); } else { // We can accept /org.x.y.z or /org/x/y/z StringBuilder applId = new StringBuilder(); while (pathLeft.Contains("/")) { applId.AppendFormat("{0}.", pathLeft.Substring(0, pathLeft.IndexOf("/"))); pathLeft = pathLeft.Substring(pathLeft.IndexOf("/") + 1); resolvedManifest = this.FirstOrDefault(o => o.Info.Id == applId.ToString(0, applId.Length - 1)); if (resolvedManifest != null) { break; } } } if (resolvedManifest == null) { resolvedManifest = relative?.Manifest; } // Is there a resource? if (resolvedManifest != null) { if (pathLeft.EndsWith("/") || String.IsNullOrEmpty(pathLeft)) { pathLeft += "index.html"; } pathLeft = pathLeft.ToLower(); // case insensitive return(resolvedManifest.Assets.FirstOrDefault(o => o.Name == pathLeft)); } return(null); } }
/// <summary> /// Get the asset /// </summary> public Stream Get() { this.ThrowIfNotRunning(); try { if (!RestOperationContext.Current.Data.TryGetValue("lang", out object lang)) { lang = AuthenticationContext.Current.Principal.GetClaimValue(SanteDBClaimTypes.Language) ?? CultureInfo.CurrentUICulture.TwoLetterISOLanguageName; } else if (lang is String[] ls) { lang = ls[0]; } if (!RestOperationContext.Current.Data.TryGetValue(AgsAuthorizationServiceBehavior.SessionPropertyName, out object sessionId)) { sessionId = AuthenticationContext.Current.Principal.GetClaimValue(SanteDBClaimTypes.SanteDBSessionIdClaim); } else if (sessionId is ISession ses) { sessionId = BitConverter.ToString(ses.Id); } var etag = $"{ApplicationContext.Current.ExecutionUuid}.{lang}.{sessionId}"; if (RestOperationContext.Current.IncomingRequest.Headers["If-None-Match"] == etag) { RestOperationContext.Current.OutgoingResponse.StatusCode = 304; /// not modified return(null); } // Navigate asset AppletAsset navigateAsset = null; var appletManagerService = ApplicationContext.Current.GetService <IAppletManagerService>(); String appletPath = RestOperationContext.Current.IncomingRequest.Url.AbsolutePath.ToLower(); if (!m_cacheApplets.TryGetValue(appletPath, out navigateAsset)) { if (appletPath == "/") // startup asset { navigateAsset = appletManagerService.Applets.DefaultApplet?.Assets.FirstOrDefault(o => o.Name == "index.html"); } else { navigateAsset = appletManagerService.Applets.ResolveAsset(appletPath); } if (navigateAsset == null) { throw new FileNotFoundException(appletPath); } lock (m_lockObject) { if (!m_cacheApplets.ContainsKey(appletPath) && appletManagerService.Applets.CachePages) { m_cacheApplets.Add(appletPath, navigateAsset); } } } // Navigate policy? if (navigateAsset.Policies != null) { foreach (var policy in navigateAsset.Policies) { new PolicyPermission(System.Security.Permissions.PermissionState.Unrestricted, policy).Demand(); } } RestOperationContext.Current.OutgoingResponse.AddHeader("ETag", etag); RestOperationContext.Current.OutgoingResponse.ContentType = navigateAsset.MimeType; // Write asset var content = appletManagerService.Applets.RenderAssetContent(navigateAsset, lang?.ToString(), bindingParameters: new Dictionary <String, String>() { { "csp_nonce", RestOperationContext.Current.ServiceEndpoint.Behaviors.OfType <SecurityPolicyHeadersBehavior>().FirstOrDefault()?.Nonce }, #if DEBUG { "env_type", "debug" }, #else { "env_type", "release" }, #endif { "host_type", ApplicationServiceContext.Current.HostType.ToString() } }); return(new MemoryStream(content)); } catch (RemoteOperationException ex) // The page demanded something upstream but the upstream service isn't responding { throw new DomainStateException($"The remote server is currently unavailable", ex); } }
/// <summary> /// Render asset content /// </summary> public byte[] RenderAssetContent(AppletAsset asset, string preProcessLocalization = null, bool staticScriptRefs = true, bool allowCache = true, IDictionary <String, String> bindingParameters = null) { // TODO: This method needs to be cleaned up since it exists from the old/early OpenIZ days // First, is there an object already byte[] cacheObject = null; string assetPath = String.Format("{0}?lang={1}", asset.ToString(), preProcessLocalization); var cacheKey = $"{assetPath};{String.Join(";", bindingParameters?.Select(o => $"{o.Key}={o.Value}") ?? new string[0] { })}"; if (allowCache && this.CachePages && s_cache.TryGetValue(cacheKey, out cacheObject)) { return(cacheObject); } // Resolve content var content = asset.Content; if (content == null && this.Resolver != null) { content = this.Resolver(asset); } if (content is String) // Content is a string { // Inject CSP if (asset.MimeType == "text/javascript" || asset.MimeType == "application/json") { var retVal = content as String; if (bindingParameters != null) { retVal = this.m_bindingRegex.Replace(retVal, (m) => bindingParameters.TryGetValue(m.Groups[1].Value, out string v) ? v : m.ToString()); } cacheObject = Encoding.UTF8.GetBytes(retVal); if (allowCache) { s_cache.TryAdd(cacheKey, cacheObject); } return(cacheObject); } else { return(Encoding.UTF8.GetBytes(content as String)); } } else if (content is byte[]) // Content is a binary asset { // is the content compressed? if (Encoding.UTF8.GetString(content as byte[], 0, 4) == "LZIP") { using (var ms = new MemoryStream(content as byte[])) using (var ls = new SharpCompress.Compressors.LZMA.LZipStream(new NonDisposingStream(ms), SharpCompress.Compressors.CompressionMode.Decompress)) using (var oms = new MemoryStream()) { byte[] buffer = new byte[2048]; int br = 1; while (br > 0) { br = ls.Read(buffer, 0, 2048); oms.Write(buffer, 0, br); } content = oms.ToArray(); if (allowCache) { s_cache.TryAdd(cacheKey, content as byte[]); } return(content as byte[]); } } else { return(content as byte[]); } } else if (content is XElement) // Content is XML { using (MemoryStream ms = new MemoryStream()) using (XmlWriter xw = XmlWriter.Create(ms)) { (content as XElement).WriteTo(xw); xw.Flush(); ms.Flush(); return(ms.ToArray()); } } else if (content is AppletAssetHtml) // Content is HTML { // Is the content HTML? var sourceAsset = content as AppletAssetHtml; var htmlAsset = new AppletAssetHtml() { Html = new XElement(sourceAsset.Html), Layout = sourceAsset.Layout, Script = new List <AssetScriptReference>(sourceAsset.Script), Titles = new List <LocaleString>(sourceAsset.Titles), Style = new List <string>(sourceAsset.Style) }; XElement htmlContent = null; if (htmlAsset.Static) { htmlContent = htmlAsset.Html as XElement; } else { // Type of tag to render basic content switch (htmlAsset.Html.Name.LocalName) { case "html": // The content is a complete HTML page { htmlContent = htmlAsset.Html as XElement; var headerInjection = this.GetInjectionHeaders(asset, htmlContent.DescendantNodes().OfType <XElement>().Any(o => o.Name == xs_xhtml + "ui-view")); // STRIP - SanteDBJS references var xel = htmlContent.Descendants().OfType <XElement>().Where(o => o.Name == xs_xhtml + "script" && o.Attribute("src")?.Value.Contains("SanteDB") == true).ToArray(); var head = htmlContent.DescendantNodes().OfType <XElement>().FirstOrDefault(o => o.Name == xs_xhtml + "head"); if (head == null) { head = new XElement(xs_xhtml + "head"); htmlContent.Add(head); } head.Add(headerInjection.Where(o => !head.Elements(o.Name).Any(e => (e.Attributes("src") != null && (e.Attributes("src") == o.Attributes("src"))) || (e.Attributes("href") != null && (e.Attributes("href") == o.Attributes("href")))))); // Inject any business rules as static refs var body = htmlContent.DescendantNodes().OfType <XElement>().FirstOrDefault(o => o.Name == xs_xhtml + "body"); if (body != null) { body.Add( this.SelectMany(o => o.Assets.Where(a => a.Name.StartsWith("rules/"))).Select(o => new XElement(xs_xhtml + "script", new XAttribute("src", $"/{o.Manifest.Info.Id}/{o.Name}"), new XAttribute("type", "text/javascript"), new XAttribute("nonce", bindingParameters.TryGetValue("csp_nonce", out string nonce) ? nonce : ""), new XText("// Script reference"))) ); } // head.Add(headerInjection); break; } case "body": // The content is an HTML Body element, we must inject the HTML header { htmlContent = htmlAsset.Html as XElement; // Inject special headers var headerInjection = this.GetInjectionHeaders(asset, htmlContent.DescendantNodes().OfType <XElement>().Any(o => o.Name == xs_xhtml + "ui-view")); // Render the bundles var bodyElement = htmlAsset.Html as XElement; htmlContent = new XElement(xs_xhtml + "html", new XAttribute("ng-app", asset.Name), new XElement(xs_xhtml + "head", headerInjection), bodyElement); } break; default: { if (String.IsNullOrEmpty(htmlAsset.Layout)) { htmlContent = htmlAsset.Html as XElement; } else { // Get the layout var layoutAsset = this.ResolveAsset(htmlAsset.Layout, relativeAsset: asset); if (layoutAsset == null) { throw new FileNotFoundException(String.Format("Layout asset {0} not found", htmlAsset.Layout)); } using (MemoryStream ms = new MemoryStream(this.RenderAssetContent(layoutAsset, preProcessLocalization, bindingParameters: bindingParameters))) htmlContent = XDocument.Load(ms).FirstNode as XElement; // Find the <!--#include virtual="content" --> tag var contentNode = htmlContent.DescendantNodes().OfType <XComment>().SingleOrDefault(o => o.Value.Trim() == "#include virtual=\"content\""); if (contentNode != null) { contentNode.AddAfterSelf(htmlAsset.Html as XElement); contentNode.Remove(); } // Injection headers var headerInjection = this.GetInjectionHeaders(asset, htmlContent.DescendantNodes().OfType <XElement>().Any(o => o.Name == xs_xhtml + "ui-view")); var headElement = (htmlContent.Element(xs_xhtml + "head") as XElement); headElement?.Add(headerInjection.Where(o => !headElement.Elements(o.Name).Any(e => (e.Attributes("src") != null && (e.Attributes("src") == o.Attributes("src"))) || (e.Attributes("href") != null && (e.Attributes("href") == o.Attributes("href")))))); } } break; } // switch // Now process SSI directives - <!--#include virtual="XXXXXXX" --> var includes = htmlContent.DescendantNodes().OfType <XComment>().Where(o => o?.Value?.Trim().StartsWith("#include virtual=\"") == true).ToList(); foreach (var inc in includes) { String assetName = inc.Value.Trim().Substring(18); // HACK: Should be a REGEX if (assetName.EndsWith("\"")) { assetName = assetName.Substring(0, assetName.Length - 1); } if (assetName == "content") { continue; } var includeAsset = this.ResolveAsset(assetName, relativeAsset: asset); if (includeAsset == null) { inc.AddAfterSelf(new XElement(xs_xhtml + "strong", new XText(String.Format("{0} NOT FOUND", assetName)))); inc.Remove(); } else { using (MemoryStream ms = new MemoryStream(this.RenderAssetContent(includeAsset, preProcessLocalization, bindingParameters: bindingParameters))) { try { var xel = XDocument.Load(ms).Elements().First() as XElement; if (xel.Name == xs_xhtml + "html") { inc.AddAfterSelf(xel.Element(xs_xhtml + "body").Elements()); } else { //var headerInjection = this.GetInjectionHeaders(includeAsset); //var headElement = htmlContent.Element(xs_xhtml + "head"); //headElement?.Add(headerInjection.Where(o => !headElement.Elements(o.Name).Any(e => (e.Attributes("src") != null && (e.Attributes("src") == o.Attributes("src"))) || (e.Attributes("href") != null && (e.Attributes("href") == o.Attributes("href")))))); inc.AddAfterSelf(xel); } inc.Remove(); } catch (Exception e) { throw new XmlException($"Error in Asset: {includeAsset}", e); } } } } // Re-write foreach (var itm in htmlContent.DescendantNodes().OfType <XElement>().SelectMany(o => o.Attributes()).Where(o => o.Value.StartsWith("~"))) { itm.Value = String.Format("/{0}/{1}", asset.Manifest.Info.Id, itm.Value.Substring(2)); //itm.Value = itm.Value.Replace(APPLET_SCHEME, this.AppletBase).Replace(ASSET_SCHEME, this.AssetBase).Replace(DRAWABLE_SCHEME, this.DrawableBase); } // Render Title var headTitle = htmlContent.DescendantNodes().OfType <XElement>().FirstOrDefault(o => o.Name == xs_xhtml + "head"); var title = htmlAsset.GetTitle(preProcessLocalization); if (headTitle != null && !String.IsNullOrEmpty(title)) { headTitle.Add(new XElement(xs_xhtml + "title", new XText(title))); } } // Render out the content using (StringWriter sw = new StringWriter()) using (XmlWriter xw = XmlWriter.Create(sw, new XmlWriterSettings() { OmitXmlDeclaration = true })) { htmlContent.WriteTo(xw); xw.Flush(); String retVal = sw.ToString(); if (!String.IsNullOrEmpty(preProcessLocalization)) { var assetString = ApplicationServiceContext.Current.GetService <ILocalizationService>().GetStrings(preProcessLocalization); retVal = this.m_localizationRegex.Replace(retVal, (m) => assetString.FirstOrDefault(o => o.Key == m.Groups[1].Value).Value ?? m.Groups[1].Value); } // Binding objects if (bindingParameters != null) { retVal = this.m_bindingRegex.Replace(retVal, (m) => bindingParameters.TryGetValue(m.Groups[1].Value, out string v) ? v : m.ToString()); } var byteData = Encoding.UTF8.GetBytes(retVal); // Add to cache if (allowCache) { s_cache.TryAdd(cacheKey, byteData); } return(byteData); } } else if (content is AppletAssetVirtual virtualContent) // Virtual asset { if (!s_cache.TryGetValue(assetPath, out byte[] data))
/// <summary> /// Resolve the asset /// </summary> public AppletAsset ResolveAsset(String assetPath, AppletManifest relativeManifest = null, AppletAsset relativeAsset = null) { if (assetPath == null) { return(null); } // Manifest to search for asset AppletManifest searchManifest = null; // Is the asset start with ~ if (assetPath.StartsWith("/")) { // Absolute var pathRegex = new Regex(@"^\/(.*?)\/(.*)$"); var pathData = pathRegex.Match(assetPath); if (pathData.Success) { searchManifest = this.FirstOrDefault(o => o.Info.Id == pathData.Groups[1].Value); assetPath = pathData.Groups[2].Value; } else { throw new InvalidCastException("Absolute references must be in format /id.to.the.applet/path/to/the/file"); } } else if (assetPath.StartsWith("~")) { assetPath = assetPath.Substring(2); // it is in current path searchManifest = relativeManifest ?? relativeAsset?.Manifest; if (searchManifest == null) { throw new InvalidOperationException("Cannot search relative manifest with no reference/related asset"); } } else { searchManifest = relativeManifest ?? relativeAsset?.Manifest; } if (assetPath.EndsWith("/") || String.IsNullOrEmpty(assetPath)) { assetPath += "index.html"; } assetPath = assetPath.ToLower(); // case insensitive return(searchManifest?.Assets.FirstOrDefault(o => o.Name == assetPath)); }
/// <summary> /// Get applet asset /// </summary> public object ResolveAppletAsset(AppletAsset navigateAsset) { String itmPath = System.IO.Path.Combine( this.m_appletBaseDir[navigateAsset.Manifest], navigateAsset.Name); if (navigateAsset.MimeType == "text/html") { XElement xe = XElement.Load(itmPath); // Now we have to iterate throuh and add the asset\ AppletAssetHtml htmlAsset = null; if (xe.Elements().OfType <XElement>().Any(o => o.Name == xs_openiz + "widget")) { var widgetEle = xe.Elements().OfType <XElement>().FirstOrDefault(o => o.Name == xs_openiz + "widget"); htmlAsset = new AppletWidget() { Icon = widgetEle.Element(xs_openiz + "icon")?.Value, Type = (AppletWidgetType)Enum.Parse(typeof(AppletWidgetType), widgetEle.Attribute("type")?.Value), Scope = (AppletWidgetScope)Enum.Parse(typeof(AppletWidgetScope), widgetEle.Attribute("scope")?.Value), Description = widgetEle.Elements().Where(o => o.Name == xs_openiz + "description").Select(o => new LocaleString() { Value = o.Value, Language = o.Attribute("lang")?.Value }).ToList(), Name = widgetEle.Attribute("name")?.Value, Controller = widgetEle.Element(xs_openiz + "controller")?.Value, }; } else { htmlAsset = new AppletAssetHtml(); // View state data htmlAsset.ViewState = xe.Elements().OfType <XElement>().Where(o => o.Name == xs_openiz + "state").Select(o => new AppletViewState() { Name = o.Attribute("name")?.Value, Route = o.Elements().OfType <XElement>().FirstOrDefault(r => r.Name == xs_openiz + "url" || r.Name == xs_openiz + "route")?.Value, IsAbstract = Boolean.Parse(o.Attribute("abstract")?.Value ?? "False"), View = o.Elements().OfType <XElement>().Where(v => v.Name == xs_openiz + "view")?.Select(v => new AppletView() { Name = v.Attribute("name")?.Value, Title = v.Elements().OfType <XElement>().Where(t => t.Name == xs_openiz + "title")?.Select(t => new LocaleString() { Language = t.Attribute("lang")?.Value, Value = t?.Value }).ToList(), Controller = v.Element(xs_openiz + "controller")?.Value }).ToList() }).FirstOrDefault(); htmlAsset.Layout = ResolveName(xe.Attribute(xs_openiz + "layout")?.Value); htmlAsset.Static = xe.Attribute(xs_openiz + "static")?.Value == "true"; } htmlAsset.Titles = new List <LocaleString>(xe.Descendants().OfType <XElement>().Where(o => o.Name == xs_openiz + "title").Select(o => new LocaleString() { Language = o.Attribute("lang")?.Value, Value = o.Value })); htmlAsset.Bundle = new List <string>(xe.Descendants().OfType <XElement>().Where(o => o.Name == xs_openiz + "bundle").Select(o => ResolveName(o.Value))); htmlAsset.Script = new List <AssetScriptReference>(xe.Descendants().OfType <XElement>().Where(o => o.Name == xs_openiz + "script").Select(o => new AssetScriptReference() { Reference = ResolveName(o.Value), IsStatic = Boolean.Parse(o.Attribute("static")?.Value ?? "true") })); htmlAsset.Style = new List <string>(xe.Descendants().OfType <XElement>().Where(o => o.Name == xs_openiz + "style").Select(o => ResolveName(o.Value))); var demand = xe.DescendantNodes().OfType <XElement>().Where(o => o.Name == xs_openiz + "demand").Select(o => o.Value).ToList(); var includes = xe.DescendantNodes().OfType <XComment>().Where(o => o?.Value?.Trim().StartsWith("#include virtual=\"") == true).ToList(); foreach (var inc in includes) { String assetName = inc.Value.Trim().Substring(18); // HACK: Should be a REGEX if (assetName.EndsWith("\"")) { assetName = assetName.Substring(0, assetName.Length - 1); } if (assetName == "content") { continue; } var includeAsset = ResolveName(assetName); inc.AddAfterSelf(new XComment(String.Format("#include virtual=\"{0}\"", includeAsset))); inc.Remove(); } var xel = xe.Descendants().OfType <XElement>().Where(o => o.Name.Namespace == xs_openiz).ToList(); if (xel != null) { foreach (var x in xel) { x.Remove(); } } htmlAsset.Html = xe; return(htmlAsset); } else if (navigateAsset.MimeType == "text/javascript" || navigateAsset.MimeType == "text/css" || navigateAsset.MimeType == "application/json" || navigateAsset.MimeType == "text/xml") { var script = File.ReadAllText(itmPath); if (itmPath.Contains("openiz.js") || itmPath.Contains("openiz.min.js")) { script += this.GetShimMethods(); } return(script); } else { return(File.ReadAllBytes(itmPath)); } }