PageRenderTime 49ms CodeModel.GetById 3ms app.highlight 35ms RepoModel.GetById 2ms app.codeStats 0ms

/BlogEngine/DotNetSlave.BusinessLogic/Utils.cs

#
C# | 1637 lines | 939 code | 200 blank | 498 comment | 117 complexity | 7eb62246c17e01ec4a097b8d1bcecb4b MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1namespace BlogEngine.Core
   2{
   3    using System;
   4    using System.Collections.Generic;
   5    using System.Configuration;
   6    using System.Diagnostics;
   7    using System.Globalization;
   8    using System.IO;
   9    using System.Linq;
  10    using System.Net;
  11    using System.Net.Mail;
  12    using System.Reflection;
  13    using System.Security;
  14    using System.Security.Cryptography;
  15    using System.Text;
  16    using System.Text.RegularExpressions;
  17    using System.Threading;
  18    using System.Web;
  19    using System.Web.Configuration;
  20    using System.Web.Hosting;
  21    using System.Web.UI;
  22    using System.Web.UI.WebControls;
  23    using System.Web.UI.HtmlControls;
  24    using System.Xml;
  25
  26    using BlogEngine.Core.Web.Controls;
  27    using BlogEngine.Core.Web.Extensions;
  28    using System.Net.Sockets;
  29
  30    /// <summary>
  31    /// Utilities for the entire solution to use.
  32    /// </summary>
  33    public static class Utils
  34    {
  35        #region Constants and Fields
  36
  37        /// <summary>
  38        /// The cookie name for forcing the main theme on a mobile device.
  39        /// </summary>
  40        public const String ForceMainThemeCookieName = "forceMainTheme";
  41
  42        /// <summary>
  43        /// The pattern.
  44        /// </summary>
  45        private const string Pattern = "<head.*<link( [^>]*title=\"{0}\"[^>]*)>.*</head>";
  46
  47        /// <summary>
  48        /// The application's relative web root.
  49        /// </summary>
  50        private static string applicationRelativeWebRoot;
  51
  52        /// <summary>
  53        /// The href regex.
  54        /// </summary>
  55        private static readonly Regex HrefRegex = new Regex(
  56            "href=\"(.*)\"", RegexOptions.IgnoreCase | RegexOptions.Compiled);
  57
  58        /// <summary>
  59        /// The regex between tags.
  60        /// </summary>
  61        private static readonly Regex RegexBetweenTags = new Regex(@">\s+", RegexOptions.Compiled);
  62
  63        /// <summary>
  64        /// The regex line breaks.
  65        /// </summary>
  66        private static readonly Regex RegexLineBreaks = new Regex(@"\n\s+", RegexOptions.Compiled);
  67
  68        /// <summary>
  69        /// The regex mobile.
  70        /// </summary>
  71        private static readonly Regex RegexMobile =
  72            new Regex(
  73                ConfigurationManager.AppSettings.Get("BlogEngine.MobileDevices"), 
  74                RegexOptions.IgnoreCase | RegexOptions.Compiled);
  75
  76        /// <summary>
  77        /// The regex strip html.
  78        /// </summary>
  79        private static readonly Regex RegexStripHtml = new Regex("<[^>]*>", RegexOptions.Compiled);
  80
  81        /// <summary>
  82        ///     Boolean for returning whether or not BlogEngine is currently running on Mono.
  83        /// </summary>
  84        private static readonly bool isMono = (Type.GetType("Mono.Runtime") != null);
  85
  86        #endregion
  87
  88        #region Events
  89
  90        /// <summary>
  91        ///     Occurs after an e-mail has been sent. The sender is the MailMessage object.
  92        /// </summary>
  93        public static event EventHandler<EventArgs> EmailFailed;
  94
  95        /// <summary>
  96        ///     Occurs after an e-mail has been sent. The sender is the MailMessage object.
  97        /// </summary>
  98        public static event EventHandler<EventArgs> EmailSent;
  99
 100        /// <summary>
 101        ///     Occurs when a message will be logged. The sender is a string containing the log message.
 102        /// </summary>
 103        public static event EventHandler<EventArgs> OnLog;
 104
 105        #endregion
 106
 107        #region Properties
 108
 109        /// <summary>
 110        ///     Gets the absolute root of the website.
 111        /// </summary>
 112        /// <value>A string that ends with a '/'.</value>
 113        public static Uri AbsoluteWebRoot
 114        {
 115            get
 116            {
 117                var context = HttpContext.Current;
 118                if (context == null)
 119                {
 120                    throw new WebException("The current HttpContext is null");
 121                }
 122
 123                var absoluteurl = context.Items["absoluteurl"];
 124                if (absoluteurl == null)
 125                {
 126                    absoluteurl = new Uri(context.Request.Url.GetLeftPart(UriPartial.Authority) + RelativeWebRoot);
 127                    context.Items["absoluteurl"] = absoluteurl;
 128                }
 129
 130                return absoluteurl as Uri;
 131
 132            }
 133        }
 134
 135        /// <summary>
 136        ///     Gets the relative URL of the blog feed. If a Feedburner username
 137        ///     is entered in the admin settings page, it will return the 
 138        ///     absolute Feedburner URL to the feed.
 139        /// </summary>
 140        public static string FeedUrl
 141        {
 142            get
 143            {
 144                return !string.IsNullOrEmpty(BlogSettings.Instance.AlternateFeedUrl)
 145                           ? BlogSettings.Instance.AlternateFeedUrl
 146                           : string.Format("{0}syndication.axd", AbsoluteWebRoot);
 147            }
 148        }
 149
 150        /// <summary>
 151        ///     Gets a value indicating whether we're running under Linux or a Unix variant.
 152        /// </summary>
 153        /// <value><c>true</c> if Linux/Unix; otherwise, <c>false</c>.</value>
 154        public static bool IsLinux
 155        {
 156            get
 157            {
 158                var p = (int)Environment.OSVersion.Platform;
 159                return (p == 4) || (p == 128);
 160            }
 161        }
 162
 163        /// <summary>
 164        ///     Gets a value indicating whether the client is a mobile device.
 165        /// </summary>
 166        /// <value><c>true</c> if this instance is mobile; otherwise, <c>false</c>.</value>
 167        public static bool IsMobile
 168        {
 169            get
 170            {
 171                var context = HttpContext.Current;
 172                if (context != null)
 173                {
 174                    var request = context.Request;
 175                    if (request.Browser.IsMobileDevice)
 176                    {
 177                        return true;
 178                    }
 179
 180                    if (!string.IsNullOrEmpty(request.UserAgent) && RegexMobile.IsMatch(request.UserAgent))
 181                    {
 182                        return true;
 183                    }
 184                }
 185
 186                return false;
 187            }
 188        }
 189
 190        /// <summary>
 191        ///     Gets a value indicating whether we're running under Mono.
 192        /// </summary>
 193        /// <value><c>true</c> if Mono; otherwise, <c>false</c>.</value>
 194        public static bool IsMono
 195        {
 196            get
 197            {
 198                return isMono;
 199            }
 200        }
 201
 202        /// <summary>
 203        ///     Gets the relative root of the current blog instance.
 204        /// </summary>
 205        /// <value>A string that ends with a '/'.</value>
 206        public static string RelativeWebRoot
 207        {
 208            get
 209            {
 210                return Blog.CurrentInstance.RelativeWebRoot;
 211            }
 212        }
 213
 214        /// <summary>
 215        ///     Gets the application's relative root.
 216        /// </summary>
 217        /// <value>A string that ends with a '/'.</value>
 218        public static string ApplicationRelativeWebRoot
 219        {
 220            get
 221            {
 222                return applicationRelativeWebRoot ??
 223                       (applicationRelativeWebRoot =
 224                        VirtualPathUtility.ToAbsolute(BlogConfig.VirtualPath));
 225            }
 226        }
 227
 228        /// <summary>
 229        ///     Gets if the current HTTP request is a homepage request, taking the blog
 230        ///     instance into consideration.
 231        /// </summary>
 232        public static bool IsCurrentRequestForHomepage
 233        {
 234            get
 235            {
 236                HttpContext context = HttpContext.Current;
 237
 238                // because the homepage uses querystring values to render non-homepage content
 239                // such as posts by tag, posts by date range, posts by author, etc, if there
 240                // are any QS parameters, this will be considered not a homepage request.
 241                if (context.Request.QueryString.Count != 0) { return false; }
 242
 243                string path = context.Request.Path;
 244
 245                // If this is a virtual folder for a blog instance, unless "default.aspx" is
 246                // actually in the URL, default.aspx won't be reported in path, so we check
 247                // to see if path is the root of RelativeWebRoot (the blog instance's
 248                // relative web root).
 249
 250                if (RelativeWebRoot.Equals(VirtualPathUtility.AppendTrailingSlash(path), StringComparison.OrdinalIgnoreCase))
 251                    return true;
 252                else if (path.Equals(string.Format("{0}default.aspx", RelativeWebRoot), StringComparison.OrdinalIgnoreCase))
 253                    return true;
 254
 255                return false;
 256            }
 257        }
 258
 259        #endregion
 260
 261        #region Public Methods
 262
 263        /// <summary>
 264        /// Returns whether the main theme should be forced for the current mobile device.
 265        /// </summary>
 266        /// <param name="request">The request.</param>
 267        /// <returns>
 268        /// A <see cref="Boolean"/> instance.
 269        /// </returns>
 270        public static Boolean ShouldForceMainTheme(HttpRequest request)
 271        {
 272            var forceMainThemeCookie = request.Cookies[ForceMainThemeCookieName];
 273
 274            if (forceMainThemeCookie == null)
 275            {
 276                return false;
 277            }
 278
 279            if (String.IsNullOrEmpty(forceMainThemeCookie.Value))
 280            {
 281                return false;
 282            }
 283
 284            Boolean forceMainTheme;
 285
 286            if (Boolean.TryParse(forceMainThemeCookie.Value, out forceMainTheme))
 287            {
 288                return forceMainTheme;
 289            }
 290
 291            return false;
 292        }
 293
 294        /// <summary>
 295        /// Parse the string representation of an enum field to a strongly typed enum value.
 296        /// </summary>
 297        public static T ParseEnum<T>(string value, T defaultValue) where T : struct, IConvertible
 298        {
 299            if (!typeof(T).IsEnum)
 300                throw new ArgumentException("T must be an enumerated type");
 301
 302            if (string.IsNullOrEmpty(value))
 303                return defaultValue;
 304
 305            foreach (T item in Enum.GetValues(typeof(T)))
 306            {
 307                if (item.ToString().Equals(value, StringComparison.OrdinalIgnoreCase))
 308                    return item;
 309            }
 310            return defaultValue;
 311        }
 312
 313        private static Regex _identifierForDisplayRgx = new Regex(
 314            @"  (?<=[A-Z])(?=[A-Z][a-z])    # UC before me, UC lc after me
 315             |  (?<=[^A-Z])(?=[A-Z])        # Not UC before me, UC after me
 316             |  (?<=[A-Za-z])(?=[^A-Za-z])  # Letter before me, non letter after me
 317            ", RegexOptions.IgnorePatternWhitespace
 318        );
 319        /// <summary>
 320        /// Format's an identifier (e.g. variable, enum field value) for display purposes,
 321        /// by adding a space before each capital letter.  A value like EditOwnUser becomes
 322        /// "Edit Own User".  If multiple capital letters are in identifier, these letters
 323        /// will be treated as one word (e.g. XMLEditor becomes "XML Editor", not
 324        /// "X M L Editor").
 325        /// </summary>
 326        /// <param name="fieldName">An identifier ready to be formatted for display.</param>
 327        /// <returns>The identifier for display purposes.</returns>
 328        /// <remarks>Credit: http://stackoverflow.com/questions/3103730</remarks>
 329        public static string FormatIdentifierForDisplay(string fieldName)
 330        {
 331            StringBuilder sb = new StringBuilder();
 332
 333            foreach (string part in _identifierForDisplayRgx.Split(fieldName))
 334            {
 335                if (sb.Length > 0) { sb.Append(" "); }
 336                sb.Append(part);
 337            }
 338
 339            return sb.ToString();
 340        }
 341
 342        /// <summary>
 343        /// Selects a listitem by value, case insensitively.
 344        /// </summary>
 345        /// <param name="control">The ListControl</param>
 346        /// <param name="value">The value to select</param>
 347        /// <returns>The ListItem found and selected</returns>
 348        public static ListItem SelectListItemByValue(ListControl control, string value)
 349        {
 350            control.ClearSelection();
 351            control.SelectedIndex = -1;
 352
 353            foreach (ListItem li in control.Items)
 354            {
 355                if (string.Equals(value, li.Value, StringComparison.OrdinalIgnoreCase))
 356                {
 357                    li.Selected = true;
 358                    return li;
 359                }
 360            }
 361
 362            return null;
 363        }
 364
 365        /// <summary>
 366        /// Converts an object to its JSON representation.
 367        /// </summary>
 368        /// <param name="obj"></param>
 369        /// <returns></returns>
 370        public static string ConvertToJson(object obj)
 371        {
 372            return new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(obj);
 373        }
 374
 375        /// <summary>
 376        /// Resolves the script URL.
 377        /// </summary>
 378        /// <param name="url">
 379        /// The URL string.
 380        /// </param>
 381        /// <param name="minify"></param>
 382        /// <returns>
 383        /// The resolve script url.
 384        /// </returns>
 385        public static string ResolveScriptUrl(string url, bool minify)
 386        {
 387            var minifyQuery = (minify ? "&amp;minify=" : String.Empty);
 388            return string.Format("{0}js.axd?path={1}{2}", 
 389                                    Utils.RelativeWebRoot,
 390                                    HttpUtility.UrlEncode(url),
 391                                    minifyQuery);
 392        }
 393
 394           /// <summary>
 395        /// Adds a JavaScript reference to the HTML head tag.
 396        /// </summary>
 397        /// <param name="page">
 398        /// The page to add the JavaScript include to.
 399        /// </param>
 400        /// <param name="url">
 401        /// The url string.
 402        /// </param>
 403        /// <param name="placeInBottom">
 404        /// The place In Bottom.
 405        /// </param>
 406        /// <param name="addDeferAttribute">
 407        /// The add Defer Attribute.
 408        /// </param>
 409        public static void AddJavaScriptInclude(System.Web.UI.Page page, string url, bool placeInBottom, bool addDeferAttribute) {
 410            AddJavaScriptInclude(page, url, placeInBottom, addDeferAttribute, false);
 411        }
 412
 413        /// <summary>
 414        /// Adds a JavaScript reference to the HTML head tag.
 415        /// </summary>
 416        /// <param name="page">
 417        /// The page to add the JavaScript include to.
 418        /// </param>
 419        /// <param name="url">
 420        /// The url string.
 421        /// </param>
 422        /// <param name="placeInBottom">
 423        /// The place In Bottom.
 424        /// </param>
 425        /// <param name="addDeferAttribute">
 426        /// The add Defer Attribute.
 427        /// </param>
 428        /// <param name="minify">Set to true if the minify querystring parameter should be added to this script.</param>
 429        public static void AddJavaScriptInclude(System.Web.UI.Page page, string url, bool placeInBottom, bool addDeferAttribute, bool minify)
 430        {
 431            if (placeInBottom)
 432            {
 433                var deferAttr = (addDeferAttribute ? " defer=\"defer\"" : string.Empty);
 434                var script = string.Format("<script type=\"text/javascript\"{0} src=\"{1}\"></script>", deferAttr, ResolveScriptUrl(url, minify));
 435                page.ClientScript.RegisterStartupScript(page.GetType(), url.GetHashCode().ToString(), script);
 436            }
 437            else
 438            {
 439                var script = new HtmlGenericControl("script");
 440                script.Attributes["type"] = "text/javascript";
 441                script.Attributes["src"] = ResolveScriptUrl(url,minify);
 442                if (addDeferAttribute)
 443                {
 444                    script.Attributes["defer"] = "defer";
 445                }
 446
 447                string itemsKey = "next-script-insert-position";
 448                HttpContext context = HttpContext.Current;
 449
 450                // Inserting scripts in the beginning of the HEAD tag so any user
 451                // scripts that may rely on our scripts (jQuery, etc) have these
 452                // scripts available to them.  Also maintaining order so subsequent
 453                // scripts are added after scripts we already added.
 454
 455                int? nextInsertPosition = context == null ? null : context.Items[itemsKey] as int?;
 456                if (!nextInsertPosition.HasValue) { nextInsertPosition = 0; }
 457
 458                page.Header.Controls.AddAt(nextInsertPosition.Value, script);
 459                if (context != null) { context.Items[itemsKey] = ++nextInsertPosition; }
 460            }
 461        }
 462
 463        /// <summary>
 464        /// Represents a JS, CSS or other external content type.
 465        /// </summary>
 466        private sealed class ExternalContentItem
 467        {
 468            public string ItemName { get; set; }
 469            public int Priority { get; set; }
 470            public bool Defer { get; set; }
 471            public bool AddAtBottom { get; set; }
 472            public bool IsFrontEndOnly { get; set; }
 473            public bool Minify { get; set; }
 474        }
 475
 476        /// <summary>
 477        /// Add JavaScript files from a folder.
 478        /// </summary>
 479        public static void AddFolderJavaScripts(
 480            System.Web.UI.Page page,
 481            string pathFromRoot,
 482            bool isFrontEnd)
 483        {
 484            // record that this script has been added during this request, so any
 485            // subsequent calls to AddFolderJavaScripts doesn't result in the same
 486            // script being added more than once.
 487            
 488            string itemsKey = "scripts-added-during-request";
 489            Dictionary<string, bool> scriptsAddedDuringRequest = HttpContext.Current.Items[itemsKey] as Dictionary<string, bool>;
 490            if (scriptsAddedDuringRequest == null)
 491            {
 492                scriptsAddedDuringRequest = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
 493                HttpContext.Current.Items[itemsKey] = scriptsAddedDuringRequest;
 494            }
 495
 496            // common rules for sorting, etc.
 497            // Files that are already minified shouldn't be re-minified as it could cause problems.
 498            List<ExternalContentItem> knownItems = new List<ExternalContentItem>()
 499            {
 500                new ExternalContentItem() { ItemName = "jquery.js", Priority = 0, Defer = false, Minify=false },
 501                new ExternalContentItem() { ItemName = "blog.js", Priority = 1, Defer = false, IsFrontEndOnly = true, AddAtBottom = true, Minify = true },
 502            };
 503
 504            Dictionary<string, ExternalContentItem> contentItems = knownItems.ToDictionary(i => i.ItemName, i => i, StringComparer.OrdinalIgnoreCase);
 505
 506            // remove leading slash, if present
 507            if (pathFromRoot.StartsWith("/"))
 508                pathFromRoot = pathFromRoot.Substring(1);
 509
 510            // remove trailing slash, if present.
 511            if (pathFromRoot.EndsWith("/"))
 512                pathFromRoot = pathFromRoot.Remove(pathFromRoot.Length - 1);
 513
 514            var fileEntries = Directory.GetFiles(HostingEnvironment.MapPath("~/" + pathFromRoot))
 515                .Where(file =>
 516                    !scriptsAddedDuringRequest.ContainsKey(file) &&
 517                    file.EndsWith(".js", StringComparison.OrdinalIgnoreCase) &&
 518                    !file.EndsWith("-vsdoc.js", StringComparison.OrdinalIgnoreCase)).ToList();
 519
 520            fileEntries.Sort((x, y) =>
 521            {
 522                string xFile = Path.GetFileName(x);
 523                string yFile = Path.GetFileName(y);
 524
 525                int xPriority = contentItems.ContainsKey(xFile) ? contentItems[xFile].Priority : int.MaxValue;
 526                int yPriority = contentItems.ContainsKey(yFile) ? contentItems[yFile].Priority : int.MaxValue;
 527
 528                if (xPriority == yPriority)
 529                    return xFile.CompareTo(yFile);
 530                else
 531                    return xPriority.CompareTo(yPriority);
 532            });
 533
 534            foreach (var file in fileEntries)
 535            {
 536                string fileName = Utils.ExtractFileNameFromPath(file);
 537                ExternalContentItem externalContentItem = contentItems.ContainsKey(fileName) ? contentItems[fileName] : null;
 538                bool defer = false;
 539                bool addItem = true;
 540                bool addAtBottom = false;
 541                bool minify = false;
 542
 543                if (externalContentItem != null)
 544                {
 545                    defer = externalContentItem.Defer;
 546                    addAtBottom = externalContentItem.AddAtBottom;
 547                    minify = externalContentItem.Minify;
 548                    if (externalContentItem.IsFrontEndOnly && !isFrontEnd)
 549                    {
 550                        addItem = false;
 551                    }
 552                }
 553
 554                if (addItem)
 555                {
 556                    scriptsAddedDuringRequest.Add(file, true);
 557
 558                    AddJavaScriptInclude(page,
 559                        string.Format("{0}{1}/{2}", Utils.ApplicationRelativeWebRoot, pathFromRoot, fileName), addAtBottom, defer, minify);
 560                }
 561            }
 562
 563           
 564        }
 565
 566        /// <summary>
 567        /// This method adds the resource handler script responsible for loading up BlogEngine's culture-specific resource strings
 568        /// on the client side. This needs to be called at a point after blog.js has been added to the page, otherwise this script
 569        /// will fail at load time.
 570        /// </summary>
 571        /// <param name="page">The System.Web.UI.Page instance the resources will be added to.</param>
 572        public static void AddJavaScriptResourcesToPage(System.Web.UI.Page page)
 573        {
 574            var resourcePath = Web.HttpHandlers.ResourceHandler.GetScriptPath(new CultureInfo(BlogSettings.Instance.Language));
 575            var script = string.Format("<script type=\"text/javascript\" src=\"{0}\"></script>", resourcePath);
 576            page.ClientScript.RegisterStartupScript(page.GetType(), resourcePath.GetHashCode().ToString(), script);
 577        }
 578
 579        /// <summary>
 580        /// This method returns all code assemblies in app_code
 581        ///     If app_code has subdirectories for c#, vb.net etc
 582        ///     Each one will come back as a separate assembly
 583        ///     So we can support extensions in multiple languages
 584        /// </summary>
 585        /// <returns>
 586        /// List of code assemblies
 587        /// </returns>
 588        public static IEnumerable<Assembly> CodeAssemblies()
 589        {
 590            var codeAssemblies = new List<Assembly>();
 591            CompilationSection s = null;
 592            var assemblyName = "__code";
 593            try
 594            {
 595                try
 596                {
 597                    s = (CompilationSection)WebConfigurationManager.GetSection("system.web/compilation");
 598                }
 599                catch (SecurityException)
 600                {
 601                    // No read permissions on web.config due to the trust level (must be High or Full)
 602                }
 603
 604                if (s != null && s.CodeSubDirectories != null && s.CodeSubDirectories.Count > 0)
 605                {
 606                    for (var i = 0; i < s.CodeSubDirectories.Count; i++)
 607                    {
 608                        assemblyName = string.Format("App_SubCode_{0}", s.CodeSubDirectories[i].DirectoryName);
 609                        codeAssemblies.Add(Assembly.Load(assemblyName));
 610                    }
 611                }
 612                else
 613                {
 614                    assemblyName = "App_Code";
 615                    codeAssemblies.Add(Assembly.Load(assemblyName));
 616                }
 617            }
 618            catch (FileNotFoundException)
 619            {
 620                /*ignore - code directory has no files*/
 621            }
 622
 623            try
 624            {
 625                codeAssemblies.AddRange(GetCompiledExtensions());
 626            }
 627            catch (Exception ex)
 628            {
 629                Log("Error loading compiled assemblies: " + ex.Message);
 630            }
 631
 632            return codeAssemblies;
 633        }
 634
 635        /// <summary>
 636        /// Converts a relative URL to an absolute one.
 637        /// </summary>
 638        /// <param name="relativeUri">
 639        /// The relative Uri.
 640        /// </param>
 641        /// <returns>
 642        /// The absolute Uri.
 643        /// </returns>
 644        public static Uri ConvertToAbsolute(Uri relativeUri)
 645        {
 646            return ConvertToAbsolute(relativeUri.ToString());
 647        }
 648
 649        /// <summary>
 650        /// Converts a relative URL to an absolute one.
 651        /// </summary>
 652        /// <param name="relativeUri">
 653        /// The relative Uri.
 654        /// </param>
 655        /// <returns>
 656        /// The absolute Uri.
 657        /// </returns>
 658        public static Uri ConvertToAbsolute(string relativeUri)
 659        {
 660            if (String.IsNullOrEmpty(relativeUri))
 661            {
 662                throw new ArgumentNullException("relativeUri");
 663            }
 664
 665            var absolute = AbsoluteWebRoot.ToString();
 666            var index = absolute.LastIndexOf(RelativeWebRoot);
 667
 668            return new Uri(absolute.Substring(0, index) + relativeUri);
 669        }
 670
 671        /// <summary>
 672        /// Downloads a web page from the Internet and returns the HTML as a string. .
 673        /// </summary>
 674        /// <param name="url">
 675        /// The URL to download from.
 676        /// </param>
 677        /// <returns>
 678        /// The HTML or null if the URL isn't valid.
 679        /// </returns>
 680        [Obsolete("Use the RemoteFile class instead.")]
 681        public static string DownloadWebPage(Uri url)
 682        {
 683            try
 684            {
 685                var remoteFile = new RemoteFile(url, true);
 686                return remoteFile.GetFileAsString();
 687            }
 688            catch (Exception)
 689            {
 690                return null;
 691            }
 692
 693        }
 694
 695        /// <summary>
 696        /// Extract file name from given physical server path
 697        /// </summary>
 698        /// <param name="path">
 699        /// The Server path.
 700        /// </param>
 701        /// <returns>
 702        /// The File Name.
 703        /// </returns>
 704        public static string ExtractFileNameFromPath(string path)
 705        {
 706            return Path.GetFileName(path);
 707        }
 708
 709        /// <summary>
 710        /// Finds semantic links in a given HTML document.
 711        /// </summary>
 712        /// <param name="type">
 713        /// The type of link. Could be foaf, apml or sioc.
 714        /// </param>
 715        /// <param name="html">
 716        /// The HTML to look through.
 717        /// </param>
 718        /// <returns>
 719        /// A list of Uri.
 720        /// </returns>
 721        public static List<Uri> FindLinks(string type, string html)
 722        {
 723            var matches = Regex.Matches(
 724                html, string.Format(Pattern, type), RegexOptions.IgnoreCase | RegexOptions.Singleline);
 725            var urls = new List<Uri>();
 726
 727            foreach (var hrefMatch in
 728                matches.Cast<Match>().Where(match => match.Groups.Count == 2).Select(match => match.Groups[1].Value).
 729                    Select(link => HrefRegex.Match(link)).Where(hrefMatch => hrefMatch.Groups.Count == 2))
 730            {
 731                Uri url;
 732                var value = hrefMatch.Groups[1].Value;
 733                if (Uri.TryCreate(value, UriKind.Absolute, out url))
 734                {
 735                    urls.Add(url);
 736                }
 737            }
 738
 739            return urls;
 740        }
 741
 742        /// <summary>
 743        /// Finds the semantic documents from a URL based on the type.
 744        /// </summary>
 745        /// <param name="url">
 746        /// The URL of the semantic document or a document containing semantic links.
 747        /// </param>
 748        /// <param name="type">
 749        /// The type. Could be "foaf", "apml" or "sioc".
 750        /// </param>
 751        /// <returns>
 752        /// A dictionary of the semantic documents. The dictionary is empty if no documents were found.
 753        /// </returns>
 754        public static Dictionary<Uri, XmlDocument> FindSemanticDocuments(Uri url, string type)
 755        {
 756            var list = new Dictionary<Uri, XmlDocument>();
 757
 758            // ignoreRemoteDownloadSettings is set to true for the
 759            // RemoteFile instance for backwards compatibility.
 760            var remoteFile = new RemoteFile(url, true);
 761            var content = remoteFile.GetFileAsString();
 762            if (!string.IsNullOrEmpty(content))
 763            {
 764                var upper = content.ToUpperInvariant();
 765
 766                if (upper.Contains("</HEAD") && upper.Contains("</HTML"))
 767                {
 768                    var urls = FindLinks(type, content);
 769                    foreach (var xmlUrl in urls)
 770                    {
 771                        var doc = LoadDocument(url, xmlUrl);
 772                        if (doc != null)
 773                        {
 774                            list.Add(xmlUrl, doc);
 775                        }
 776                    }
 777                }
 778                else
 779                {
 780                    var doc = LoadDocument(url, url);
 781                    if (doc != null)
 782                    {
 783                        list.Add(url, doc);
 784                    }
 785                }
 786            }
 787
 788            return list;
 789        }
 790
 791        /// <summary>
 792        /// Returns the default culture.  This is either the culture specified in the blog settings,
 793        ///     or the default culture installed with the operating system.
 794        /// </summary>
 795        /// <returns>
 796        /// The default culture.
 797        /// </returns>
 798        public static CultureInfo GetDefaultCulture()
 799        {
 800            var settingsCulture = BlogSettings.Instance.Culture;
 801            if (Utils.StringIsNullOrWhitespace(settingsCulture) ||
 802                settingsCulture.Equals("Auto", StringComparison.OrdinalIgnoreCase))
 803            {
 804                return CultureInfo.InstalledUICulture;
 805            }
 806
 807            return CultureInfo.CreateSpecificCulture(settingsCulture);
 808        }
 809
 810        /// <summary>
 811        /// Gets the sub domain.
 812        /// </summary>
 813        /// <param name="url">
 814        /// The URL to get the sub domain from.
 815        /// </param>
 816        /// <returns>
 817        /// The sub domain.
 818        /// </returns>
 819        public static string GetSubDomain(Uri url)
 820        {
 821           
 822            if (url.HostNameType == UriHostNameType.Dns)
 823            {
 824                var host = url.Host;
 825                if (host.Split('.').Length > 2)
 826                {
 827                    var lastIndex = host.LastIndexOf(".");
 828                    var index = host.LastIndexOf(".", lastIndex - 1);
 829                    return host.Substring(0, index);
 830                }
 831            }
 832
 833            return null;
 834        }
 835
 836        /// <summary>
 837        /// Encrypts a string using the SHA256 algorithm.
 838        /// </summary>
 839        /// <param name="plainMessage">
 840        /// The plain Message.
 841        /// </param>
 842        /// <returns>
 843        /// The hash password.
 844        /// </returns>
 845        public static string HashPassword(string plainMessage)
 846        {
 847            var data = Encoding.UTF8.GetBytes(plainMessage);
 848            using (HashAlgorithm sha = new SHA256Managed())
 849            {
 850                sha.TransformFinalBlock(data, 0, data.Length);
 851                return Convert.ToBase64String(sha.Hash);
 852            }
 853        }
 854
 855        /// <summary>
 856        /// The body regex.
 857        /// </summary>
 858        private static readonly Regex BodyRegex = new Regex(@"\[UserControl:(.*?)\]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
 859
 860        /// <summary>
 861        /// Injects any user controls if one is referenced in the text.
 862        /// </summary>
 863        /// <param name="containerControl">The control that the user controls will be injected in to.</param>
 864        /// <param name="content">The string to parse for user controls.</param>
 865        public static void InjectUserControls(System.Web.UI.Control containerControl, string content)
 866        {
 867            var currentPosition = 0;
 868            var matches = BodyRegex.Matches(content);
 869            var containerControls = containerControl.Controls;
 870            var page = containerControl.Page;
 871
 872            foreach (Match match in matches)
 873            {
 874                // Add literal for content before custom tag should it exist.
 875                if (match.Index > currentPosition)
 876                {
 877                    containerControls.Add(new LiteralControl(content.Substring(currentPosition, match.Index - currentPosition)));
 878                }
 879
 880                // Now lets add our user control.
 881                try
 882                {
 883                    var all = match.Groups[1].Value.Trim();
 884                    Control usercontrol;
 885
 886                    if (!all.EndsWith(".ascx", StringComparison.OrdinalIgnoreCase))
 887                    {
 888                        var index = all.IndexOf(".ascx", StringComparison.OrdinalIgnoreCase) + 5;
 889                        usercontrol = page.LoadControl(all.Substring(0, index));
 890
 891                        var parameters = page.Server.HtmlDecode(all.Substring(index));
 892                        var type = usercontrol.GetType();
 893                        var paramCollection = parameters.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
 894
 895                        foreach (var param in paramCollection)
 896                        {
 897                            var paramSplit = param.Split('=');
 898                            var name = paramSplit[0].Trim();
 899                            var value = paramSplit[1].Trim();
 900                            var property = type.GetProperty(name);
 901                            property.SetValue(
 902                                usercontrol,
 903                                Convert.ChangeType(value, property.PropertyType, CultureInfo.InvariantCulture),
 904                                null);
 905                        }
 906                    }
 907                    else
 908                    {
 909                        usercontrol = page.LoadControl(all);
 910                    }
 911
 912                    containerControls.Add(usercontrol);
 913
 914                    // Now we will update our position.
 915                    // currentPosition = myMatch.Index + myMatch.Groups[0].Length;
 916                }
 917                catch (Exception)
 918                {
 919                    // Whoopss, can't load that control so lets output something that tells the developer that theres a problem.
 920                    containerControls.Add(
 921                        new LiteralControl(string.Format("ERROR - UNABLE TO LOAD CONTROL : {0}", match.Groups[1].Value)));
 922                }
 923
 924                currentPosition = match.Index + match.Groups[0].Length;
 925            }
 926
 927            // Finally we add any trailing static text.
 928            containerControls.Add(
 929                new LiteralControl(content.Substring(currentPosition, content.Length - currentPosition)));
 930        }
 931
 932        private static readonly Regex emailRegex = new Regex(
 933            @"^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$", RegexOptions.IgnoreCase);
 934
 935        private static readonly Regex validIpV4AddressRegex = new Regex(@"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$", RegexOptions.IgnoreCase);
 936        private static readonly Regex validHostnameRegex = new Regex(@"^(([a-z]|[a-z][a-z0-9\-]*[a-z0-9])\.)*([a-z]|[a-z][a-z0-9\-]*[a-z0-9])$", RegexOptions.IgnoreCase);
 937
 938        /// <summary>
 939        /// Email address by user name
 940        /// </summary>
 941        /// <param name="userName">User name</param>
 942        /// <returns>Email Address</returns>
 943        public static string GetUserEmail(string userName)
 944        {
 945            int count;
 946            var userCollection = System.Web.Security.Membership.Provider.GetAllUsers(0, 999, out count);
 947            var users = userCollection.Cast<System.Web.Security.MembershipUser>().ToList();
 948            var user = users.FirstOrDefault(u => u.UserName.Equals(userName));
 949
 950            if(user != null)
 951            {
 952                return user.Email;
 953            }
 954            return userName;
 955        }
 956
 957        /// <summary>
 958        /// Validates an email address.
 959        /// </summary>
 960        /// <param name="email"></param>
 961        /// <returns></returns>
 962        public static bool IsEmailValid(string email)
 963        {
 964            if (!string.IsNullOrWhiteSpace(email))
 965            {
 966                return emailRegex.IsMatch(email.Trim());
 967            }
 968
 969            return false;
 970        }
 971
 972        /// <summary>
 973        /// Validates a host name.
 974        /// </summary>
 975        /// <param name="hostname"></param>
 976        /// <returns></returns>
 977        public static bool IsHostnameValid(string hostname)
 978        {
 979            if (!string.IsNullOrWhiteSpace(hostname))
 980            {
 981                return validHostnameRegex.IsMatch(hostname.Trim());
 982            }
 983
 984            return false;
 985        }
 986
 987        /// <summary>
 988        /// Validates an IPv4 address.
 989        /// </summary>
 990        /// <param name="address"></param>
 991        /// <returns></returns>
 992        public static bool IsIpV4AddressValid(string address)
 993        {
 994            if (!string.IsNullOrWhiteSpace(address))
 995            {
 996                return validIpV4AddressRegex.IsMatch(address.Trim());
 997            }
 998
 999            return false;
1000        }
1001
1002        /// <summary>
1003        /// Validates an IPv6 address.
1004        /// </summary>
1005        /// <param name="address"></param>
1006        /// <returns></returns>
1007        public static bool IsIpV6AddressValid(string address)
1008        {
1009            if (!string.IsNullOrWhiteSpace(address))
1010            {
1011                IPAddress ip;
1012                if (IPAddress.TryParse(address, out ip))
1013                {
1014                    return ip.AddressFamily == AddressFamily.InterNetworkV6;
1015                }
1016            }
1017            return false;
1018        }
1019
1020        /// <summary>
1021        /// Run through all code assemblies and creates object
1022        ///     instance for types marked with extension attribute
1023        /// </summary>
1024        public static void LoadExtensions()
1025        {
1026            var codeAssemblies = CodeAssemblies();
1027
1028            var sortedExtensions = new List<SortedExtension>();
1029
1030            foreach (Assembly a in codeAssemblies)
1031            {
1032                var types = a.GetTypes();
1033                
1034                Type extensionsAttribute = typeof(ExtensionAttribute);
1035
1036                sortedExtensions.AddRange(
1037                    from type in types
1038                    let attributes = type.GetCustomAttributes(extensionsAttribute, false)
1039                    from attribute in attributes
1040                    where (attribute.GetType() == extensionsAttribute)
1041                    let ext = (ExtensionAttribute)attribute
1042                    select new SortedExtension(ext.Priority, type.Name, type.FullName));
1043
1044                sortedExtensions.Sort(
1045                    (e1, e2) =>
1046                    e1.Priority == e2.Priority
1047                        ? string.CompareOrdinal(e1.Name, e2.Name)
1048                        : e1.Priority.CompareTo(e2.Priority));
1049
1050                foreach (var x in sortedExtensions.Where(x => ExtensionManager.ExtensionEnabled(x.Name)))
1051                {
1052                    a.CreateInstance(x.Type);
1053                }
1054            }
1055
1056            // initialize comment rules and filters
1057            CommentHandlers.Listen();
1058        }
1059
1060        /// <summary>
1061        /// Sends a message to any subscribed log listeners.
1062        /// </summary>
1063        /// <param name="message">
1064        /// The message to be logged.
1065        /// </param>
1066        public static void Log(object message)
1067        {
1068            if (OnLog != null)
1069            {
1070                OnLog(message, new EventArgs());
1071            }
1072        }
1073
1074        /// <summary>
1075        /// Sends a message to any subscribed log listeners.
1076        /// </summary>
1077        /// <param name="methodName"></param>
1078        /// <param name="ex"></param>
1079        public static void Log(string methodName, Exception ex)
1080        {
1081            Log(String.Format("{0}: {1}", methodName, ex.Message));
1082        }
1083
1084        /// <summary>
1085        /// Generates random password for password reset
1086        /// </summary>
1087        /// <returns>
1088        /// Random password
1089        /// </returns>
1090        public static string RandomPassword()
1091        {
1092            var chars = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
1093            var password = string.Empty;
1094            var random = new Random();
1095
1096            for (var i = 0; i < 8; i++)
1097            {
1098                var x = random.Next(1, chars.Length);
1099                if (!password.Contains(chars.GetValue(x).ToString()))
1100                {
1101                    password += chars.GetValue(x);
1102                }
1103                else
1104                {
1105                    i--;
1106                }
1107            }
1108
1109            return password;
1110        }
1111
1112        /// <summary>
1113        /// Removes the HTML whitespace.
1114        /// </summary>
1115        /// <param name="html">
1116        /// The HTML to remove the whitespace from.
1117        /// </param>
1118        /// <returns>
1119        /// The html with the whitespace removed.
1120        /// </returns>
1121        public static string RemoveHtmlWhitespace(string html)
1122        {
1123            if (string.IsNullOrEmpty(html))
1124            {
1125                return string.Empty;
1126            }
1127
1128            html = RegexBetweenTags.Replace(html, "> ");
1129            html = RegexLineBreaks.Replace(html, string.Empty);
1130
1131            return html.Trim();
1132        }
1133
1134        /// <summary>
1135        /// Renders a control to a string.
1136        /// </summary>
1137        /// <param name="control"></param>
1138        /// <returns></returns>
1139        public static string RenderControl(System.Web.UI.Control control)
1140        {
1141            if (control == null)
1142            {
1143                throw new ArgumentNullException("control");
1144            }
1145
1146            using (var sWriter = new System.IO.StringWriter())
1147            {
1148                using (var hWriter = new System.Web.UI.HtmlTextWriter(sWriter))
1149                {
1150                    control.RenderControl(hWriter);
1151                }
1152
1153                return sWriter.ToString();
1154            }
1155
1156        }
1157
1158        /// <summary>
1159        /// Strips all illegal characters from the specified title.
1160        /// </summary>
1161        /// <param name="text">
1162        /// The text to strip.
1163        /// </param>
1164        /// <returns>
1165        /// The remove illegal characters.
1166        /// </returns>
1167        public static string RemoveIllegalCharacters(string text)
1168        {
1169            if (string.IsNullOrEmpty(text))
1170            {
1171                return text;
1172            }
1173
1174            text = text.Replace(":", string.Empty);
1175            text = text.Replace("/", string.Empty);
1176            text = text.Replace("?", string.Empty);
1177            text = text.Replace("#", string.Empty);
1178            text = text.Replace("[", string.Empty);
1179            text = text.Replace("]", string.Empty);
1180            text = text.Replace("@", string.Empty);
1181            text = text.Replace("*", string.Empty);
1182            text = text.Replace(".", string.Empty);
1183            text = text.Replace(",", string.Empty);
1184            text = text.Replace("\"", string.Empty);
1185            text = text.Replace("&", string.Empty);
1186            text = text.Replace("'", string.Empty);
1187            text = text.Replace(" ", "-");
1188            text = RemoveDiacritics(text);
1189            text = RemoveExtraHyphen(text);
1190
1191            return HttpUtility.HtmlEncode(text).Replace("%", string.Empty);
1192        }
1193
1194        /// <summary>
1195        /// Sends a MailMessage object using the SMTP settings.
1196        /// </summary>
1197        /// <param name="message">
1198        /// The message.
1199        /// </param>
1200        /// <returns>
1201        /// Error message, if any.
1202        /// </returns>
1203        public static string SendMailMessage(MailMessage message)
1204        {
1205            if (message == null)
1206            {
1207                throw new ArgumentNullException("message");
1208            }
1209
1210            StringBuilder errorMsg = new StringBuilder();
1211
1212            try
1213            {
1214                message.IsBodyHtml = true;
1215                message.BodyEncoding = Encoding.UTF8;
1216                var smtp = new SmtpClient(BlogSettings.Instance.SmtpServer);
1217
1218                // don't send credentials if a server doesn't require it,
1219                // linux smtp servers don't like that 
1220                if (!string.IsNullOrEmpty(BlogSettings.Instance.SmtpUserName))
1221                {
1222                    smtp.Credentials = new NetworkCredential(
1223                        BlogSettings.Instance.SmtpUserName, BlogSettings.Instance.SmtpPassword);
1224                }
1225
1226                smtp.Port = BlogSettings.Instance.SmtpServerPort;
1227                smtp.EnableSsl = BlogSettings.Instance.EnableSsl;
1228                smtp.Send(message);
1229                OnEmailSent(message);
1230            }
1231            catch (Exception ex)
1232            {
1233                OnEmailFailed(message);
1234
1235                errorMsg.Append("Error sending email in SendMailMessage: ");
1236                Exception current = ex;
1237
1238                while (current != null)
1239                {
1240                    if (errorMsg.Length > 0) { errorMsg.Append(" "); }
1241                    errorMsg.Append(current.Message);
1242                    current = current.InnerException;
1243                }
1244
1245                Utils.Log(errorMsg.ToString());
1246            }
1247            finally
1248            {
1249                // Remove the pointer to the message object so the GC can close the thread.
1250                message.Dispose();
1251            }
1252
1253            return errorMsg.ToString();
1254        }
1255
1256        /// <summary>
1257        /// Sends the mail message asynchronously in another thread.
1258        /// </summary>
1259        /// <param name="message">
1260        /// The message to send.
1261        /// </param>
1262        public static void SendMailMessageAsync(MailMessage message)
1263        {
1264            // Before entering a BG thread, retrieve the current instance blog settings.
1265            Guid blogId = Blog.CurrentInstance.Id;
1266            ThreadPool.QueueUserWorkItem(delegate
1267            {
1268                // because HttpContext is not available within this BG thread
1269                // needed to determine the current blog instance,
1270                // set override value here.
1271                Blog.InstanceIdOverride = blogId;
1272
1273                SendMailMessage(message);
1274            });
1275        }
1276
1277        /// <summary>
1278        /// Writes ETag and Last-Modified headers and sets the conditional get headers.
1279        /// </summary>
1280        /// <param name="date">
1281        /// The date for the headers.
1282        /// </param>
1283        /// <returns>
1284        /// The set conditional get headers.
1285        /// </returns>
1286        public static bool SetConditionalGetHeaders(DateTime date)
1287        {
1288            // SetLastModified() below will throw an error if the 'date' is a future date.
1289            // If the date is 1/1/0001, Mono will throw a 404 error
1290            if (date > DateTime.Now || date.Year < 1900)
1291            {
1292                date = DateTime.Now;
1293            }
1294
1295            var response = HttpContext.Current.Response;
1296            var request = HttpContext.Current.Request;
1297
1298            var etag = string.Format("\"{0}\"", date.Ticks);
1299            var incomingEtag = request.Headers["If-None-Match"];
1300
1301            DateTime incomingLastModifiedDate;
1302            DateTime.TryParse(request.Headers["If-Modified-Since"], out incomingLastModifiedDate);
1303
1304            response.Cache.SetLastModified(date);
1305            response.Cache.SetCacheability(HttpCacheability.Public);
1306            response.Cache.SetETag(etag);
1307
1308            if (String.Compare(incomingEtag, etag) == 0 || incomingLastModifiedDate == date)
1309            {
1310                response.Clear();
1311                response.StatusCode = (int)HttpStatusCode.NotModified;
1312                return true;
1313            }
1314
1315            return false;
1316        }
1317
1318        /// <summary>
1319        /// Returns whether a string is null, empty, or whitespace. Same implementation as in String.IsNullOrWhitespace in .Net 4.0
1320        /// </summary>
1321        /// <param name="value"></param>
1322        /// <returns></returns>
1323        public static bool StringIsNullOrWhitespace(string value)
1324        {
1325            return ((value == null) || (value.Trim().Length == 0));
1326        }
1327
1328        /// <summary>
1329        /// Strips all HTML tags from the specified…

Large files files are truncated, but you can click here to view the full file