/* Copyright (c) Citrix Systems, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Text.RegularExpressions; using XenCenterLib; using XenAdmin.XenSearch; using XenAPI; using System.Net.Sockets; namespace XenAdmin.Plugins { /// /// A plugin can specify various URLs, file paths, or command arguments, any of which /// may contain placeholders such as {$session_id} or {$ip_address}. These are /// replaced at time of use with the appropriate value. /// /// Most values come from the parameters on the (first) selected object, but /// {$session_id} is handled specially, and is replaced with the OpaqueRef of a /// logged-in session. /// internal class Placeholders { private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); private static Regex PlaceholderRegex = new Regex(@"{\$([\w:]+)}"); private const string PlaceholderFormat = @"{{${0}}}"; private const string NULL_PLACEHOLDER_KEY = "null"; private const string MULTI_TARGET_PLACEHOLDER_KEY = "multi_target"; /// /// Gets the replacement for the specified placeholder for the specified object. /// /// The placeholder to be replaced. /// The objects that the placeholder replacements are for. /// A predicate indicating whether the specified placeholder should be replaced. /// The replacement for the specified placeholder. private static string GetPlaceholderReplacement(string placeholder, IList objs, Predicate match) { if (match(placeholder)) { if (objs == null || objs.Count == 0) return NULL_PLACEHOLDER_KEY; if (objs.Count > 1) return MULTI_TARGET_PLACEHOLDER_KEY; if (placeholder == "session_id") { if (objs[0].Connection == null || objs[0].Connection.Session == null) { return NULL_PLACEHOLDER_KEY; } return objs[0].Connection.Session.opaque_ref; } else { // otherwise update url with the latest info PropertyNames property = (PropertyNames)Enum.Parse(typeof(PropertyNames), placeholder); object val = PropertyAccessors.Get(property)(objs[0]); return val != null ? val.ToString() : NULL_PLACEHOLDER_KEY; } } return null; } /// /// Replaces all placeholders (e.g. {$ip_address}, {$session_id} in the specified text for the specified object array. /// Note that if there is more than one object in the array current behaviour is to sub in MULTI_TARGET_PLACEHOLDER_KEY for all situations /// /// The text that contains the placeholders to be replaced. /// The objects that the placeholder replacements are for. Can be null. /// A predicate indicating whether the specified placeholder should be replaced. /// The processed text. private static string Substitute(string text, IList objs, Predicate match) { return PlaceholderRegex.Replace(text, m => { try { return GetPlaceholderReplacement(m.Groups[1].Value, objs, match) ?? m.Value; } catch (Exception e) { // Leave the placeholder in; nothing we can do. log.Warn(string.Format("Bad placeholder '{0}' in '{1}'", m.Value, text), e); return m.Value; } }); } /// /// Replaces all placeholders (e.g. {$ip_address}, {$session_id} in the specified text for the specified obj. /// /// The text that contains the placeholders to be replaced. /// The object that the placeholder replacements are for. Can be null. /// A predicate indicating whether the specified placeholder should be replaced. /// The processed text. private static string Substitute(string text, IXenObject obj, Predicate match) { return Substitute(text, obj == null ? null : new List { obj }, match); } /// /// Replaces all placeholders (e.g. {$ip_address}, {$session_id} in the specified text for the specified object array. /// Note that if there is more than one object in the array current behaviour is to sub in MULTI_TARGET_PLACEHOLDER_KEY for all situations /// /// The text that contains the placeholders to be replaced. /// The objects that the placeholder replacements are for. Can be null. /// The processed text. public static string Substitute(string text, IList objs) { return Substitute(text, objs, s => true); } /// /// Replaces all placeholders (e.g. {$ip_address}, {$session_id} in the specified text for the specified obj. /// /// The text that contains the placeholders to be replaced. /// The object that the placeholder replacements are for. Can be null. /// The processed text. public static string Substitute(string text, IXenObject obj) { return Substitute(text, obj, s => true); } /// /// Replaces all placeholders (e.g. {$ip_address}, {$session_id} in the specified text for the specified obj. /// Since ip_address can take several values over different Networks, this method returns a list of Uri for /// each of the different IP addresses. /// /// The text that contains the placeholders to be replaced. /// The object that the placeholder replacements are for. /// A List of Uris. public static List SubstituteUri(string uri, IXenObject obj) { Util.ThrowIfParameterNull(uri, "uri"); string ipAddressName = Enum.GetName(typeof(PropertyNames), PropertyNames.ip_address); try { if (!uri.Contains(string.Format(PlaceholderFormat, ipAddressName))) return new List { new Uri(Substitute(uri, obj)) }; var ips = (List)PropertyAccessors.Get(PropertyNames.ip_address)(obj); if (ips == null || ips.Count == 0) { log.DebugFormat("Object {0} (opaque_ref {1}) has no IPs.", obj.Name(), obj.opaque_ref); return new List { new Uri("about:blank") }; } string u = Substitute(uri, obj, s => s != ipAddressName); return ips.ConvertAll(ip => { var ipstring = ip.AddressIP != null && ip.AddressIP.AddressFamily == AddressFamily.InterNetworkV6 ? string.Format("[{0}]", ip) : ip.ToString(); return new Uri(u.Replace(string.Format(PlaceholderFormat, ipAddressName), ipstring)); }); } catch (UriFormatException) { log.Warn(string.Format("Failed to parse url {0}", uri)); return new List { new Uri("about:blank") }; } } /// /// Returns a value indicating whether the specified object can supply values for all placeholders /// in the specified uri. /// /// The uri string with placeholders to be read. /// The IXenObject to be used for the placeholder substitution. /// A value indicating whether all place-holders could be replaced for this uri. public static bool UriValid(string uri, IXenObject obj) { return SubstituteUri(uri, obj).TrueForAll(u => u.AbsoluteUri != "about:blank"); } } }