diff --git a/README.md b/README.md index 3e9be6cb5f..1d42543eb3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Umbraco CMS =========== -The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 350,000 websites worldwide: [https://umbraco.com](https://umbraco.com) +The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 390,000 websites worldwide: [https://umbraco.com](https://umbraco.com) [![ScreenShot](vimeo.png)](https://vimeo.com/172382998/) diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index 1c8b3dabee..2464e1ba7b 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -33,6 +33,12 @@ namespace Umbraco.Core private string _connectionString; private string _providerName; private DatabaseSchemaResult _result; + private DateTime? _connectionLastChecked = null; + + /// + /// The number of minutes to throttle the checks to CanConnect + /// + private const int ConnectionCheckMinutes = 1; [Obsolete("Use the constructor specifying all dependencies instead")] public DatabaseContext(IDatabaseFactory factory) @@ -148,13 +154,28 @@ namespace Umbraco.Core { get { - if (IsDatabaseConfigured == false) return false; - var canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider); - LogHelper.Info("CanConnect = " + canConnect); - return canConnect; + if (IsDatabaseConfigured == false) + return false; + + //Don't check again if the timeout period hasn't elapsed + //this ensures we don't keep checking the connection too many times in a row like during startup. + //Do check if the _connectionLastChecked is null which means we're just initializing or it could + //not connect last time it was checked. + if ((_connectionLastChecked.HasValue && (DateTime.Now - _connectionLastChecked.Value).TotalMinutes > ConnectionCheckMinutes) + || _connectionLastChecked.HasValue == false) + { + var canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider); + LogHelper.Info("CanConnect = " + canConnect); + + _connectionLastChecked = canConnect == false ? null : (DateTime?) DateTime.Now; + return canConnect; + } + + return _connectionLastChecked.HasValue; } } + /// /// Gets the configured umbraco db connection string. /// diff --git a/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs b/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs index c0c7b2a777..86072c45d5 100644 --- a/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs +++ b/src/Umbraco.Core/Persistence/DbConnectionExtensions.cs @@ -4,9 +4,11 @@ using System.Configuration; using System.Data; using System.Data.Common; using System.Data.SqlClient; +using System.Data.SqlServerCe; using System.Linq; using System.Text; using System.Threading.Tasks; +using MySql.Data.MySqlClient; using Umbraco.Core.Logging; namespace Umbraco.Core.Persistence @@ -69,6 +71,38 @@ namespace Umbraco.Core.Persistence } } + public static string GetConnStringExSecurityInfo(this IDbConnection connection) + { + try + { + if (connection is SqlConnection) + { + var builder = new SqlConnectionStringBuilder(connection.ConnectionString); + return string.Format("DataSource: {0}, InitialCatalog: {1}", builder.DataSource, builder.InitialCatalog); + } + + if (connection is SqlCeConnection) + { + var builder = new SqlCeConnectionStringBuilder(connection.ConnectionString); + return string.Format("DataSource: {0}", builder.DataSource); + } + + if (connection is MySqlConnection) + { + var builder = new MySqlConnectionStringBuilder(connection.ConnectionString); + return string.Format("Server: {0}, Database: {1}", builder.Server, builder.Database); + } + } + catch (Exception ex) + { + LogHelper.WarnWithException(typeof(DbConnectionExtensions), + "Could not resolve connection string parameters", ex); + return "(Could not resolve)"; + } + + throw new ArgumentException(string.Format("The connection type {0} is not supported", connection.GetType())); + } + public static bool IsAvailable(this IDbConnection connection) { try @@ -79,7 +113,8 @@ namespace Umbraco.Core.Persistence catch (DbException exc) { // Don't swallow this error, the exception is super handy for knowing "why" its not available - LogHelper.WarnWithException("Configured database is reporting as not being available!", exc); + LogHelper.WarnWithException(typeof(DbConnectionExtensions), + "Configured database is reporting as not being available! {0}", exc, connection.GetConnStringExSecurityInfo); return false; } diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 0a47b428c7..f4bd24aa64 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -852,7 +852,8 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder"; if (withCache) { var cached = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); - if (cached != null && cached.Published) + //only use this cached version if the dto returned is also the publish version, they must match + if (cached != null && cached.Published && dto.Published) { content[i] = cached; continue; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js index 5dd7d266df..60bb4957ae 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js @@ -172,6 +172,55 @@ angular.module("umbraco.directives") }); + // pin toolbar to top of screen if we have focus and it scrolls off the screen + var pinToolbar = function () { + + var _toolbar = $(editor.editorContainer).find(".mce-toolbar"); + var toolbarHeight = _toolbar.height(); + + var _tinyMce = $(editor.editorContainer); + var tinyMceRect = _tinyMce[0].getBoundingClientRect(); + var tinyMceTop = tinyMceRect.top; + var tinyMceBottom = tinyMceRect.bottom; + var tinyMceWidth = tinyMceRect.width; + + var _tinyMceEditArea = _tinyMce.find(".mce-edit-area"); + + // set padding in top of mce so the content does not "jump" up + _tinyMceEditArea.css("padding-top", toolbarHeight); + + if (tinyMceTop < 160 && ((160 + toolbarHeight) < tinyMceBottom)) { + _toolbar + .css("visibility", "visible") + .css("position", "fixed") + .css("top", "160px") + .css("margin-top", "0") + .css("width", tinyMceWidth); + } else { + _toolbar + .css("visibility", "visible") + .css("position", "absolute") + .css("top", "auto") + .css("margin-top", "0") + .css("width", tinyMceWidth); + } + + }; + + // unpin toolbar to top of screen + var unpinToolbar = function() { + + var _toolbar = $(editor.editorContainer).find(".mce-toolbar"); + var _tinyMce = $(editor.editorContainer); + var _tinyMceEditArea = _tinyMce.find(".mce-edit-area"); + + // reset padding in top of mce so the content does not "jump" up + _tinyMceEditArea.css("padding-top", "0"); + + _toolbar.css("position", "static"); + + }; + //when we leave the editor (maybe) editor.on('blur', function (e) { editor.save(); @@ -185,6 +234,9 @@ angular.module("umbraco.directives") scope.onBlur(); } + unpinToolbar(); + $('.umb-panel-body').off('scroll', pinToolbar); + }); }); @@ -196,6 +248,9 @@ angular.module("umbraco.directives") scope.onFocus(); } + pinToolbar(); + $('.umb-panel-body').on('scroll', pinToolbar); + }); }); @@ -207,6 +262,9 @@ angular.module("umbraco.directives") scope.onClick(); } + pinToolbar(); + $('.umb-panel-body').on('scroll', pinToolbar); + }); }); diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2-Fluid.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2-Fluid.cshtml index f6b93139ce..d8e9f40e15 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2-Fluid.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2-Fluid.cshtml @@ -66,7 +66,7 @@ if(cfg != null) foreach (JProperty property in cfg.Properties()) { - var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); + var propertyValue = HttpUtility.HtmlAttributeEncode(property.Value.ToString()); attrs.Add(property.Name + "=\"" + propertyValue + "\""); } @@ -76,7 +76,7 @@ var cssVals = new List(); foreach (JProperty property in style.Properties()) { - var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); + var propertyValue = property.Value.ToString(); if (string.IsNullOrWhiteSpace(propertyValue) == false) { cssVals.Add(property.Name + ":" + propertyValue + ";"); @@ -84,7 +84,7 @@ } if (cssVals.Any()) - attrs.Add("style='" + string.Join(" ", cssVals) + "'"); + attrs.Add("style='" + HttpUtility.HtmlAttributeEncode(string.Join(" ", cssVals)) + "'"); } return new MvcHtmlString(string.Join(" ", attrs)); diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml index c5fabe2abf..c8f9ab7cd1 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap2.cshtml @@ -66,7 +66,7 @@ if(cfg != null) foreach (JProperty property in cfg.Properties()) { - var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); + var propertyValue = HttpUtility.HtmlAttributeEncode(property.Value.ToString()); attrs.Add(property.Name + "=\"" + propertyValue + "\""); } @@ -76,7 +76,7 @@ var cssVals = new List(); foreach (JProperty property in style.Properties()) { - var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); + var propertyValue = property.Value.ToString(); if (string.IsNullOrWhiteSpace(propertyValue) == false) { cssVals.Add(property.Name + ":" + propertyValue + ";"); @@ -84,7 +84,7 @@ } if (cssVals.Any()) - attrs.Add("style=\"" + string.Join(" ", cssVals) + "\""); + attrs.Add("style=\"" + HttpUtility.HtmlAttributeEncode(string.Join(" ", cssVals)) + "\""); } return new MvcHtmlString(string.Join(" ", attrs)); diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml index b7e8ef34fb..defe59d808 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml @@ -62,7 +62,7 @@ if(cfg != null) foreach (JProperty property in cfg.Properties()) { - var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); + var propertyValue = HttpUtility.HtmlAttributeEncode(property.Value.ToString()); attrs.Add(property.Name + "=\"" + propertyValue + "\""); } @@ -72,7 +72,7 @@ var cssVals = new List(); foreach (JProperty property in style.Properties()) { - var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); + var propertyValue = property.Value.ToString(); if (string.IsNullOrWhiteSpace(propertyValue) == false) { cssVals.Add(property.Name + ":" + propertyValue + ";"); @@ -80,7 +80,7 @@ } if (cssVals.Any()) - attrs.Add("style='" + string.Join(" ", cssVals) + "'"); + attrs.Add("style='" + HttpUtility.HtmlAttributeEncode(string.Join(" ", cssVals)) + "'"); } return new MvcHtmlString(string.Join(" ", attrs)); diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml index 3a4fa3b8e2..6ab5c1355a 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml @@ -66,17 +66,17 @@ if(cfg != null) foreach (JProperty property in cfg.Properties()) { - var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); + var propertyValue = HttpUtility.HtmlAttributeEncode(property.Value.ToString()); attrs.Add(property.Name + "=\"" + propertyValue + "\""); } - + JObject style = contentItem.styles; if (style != null) { var cssVals = new List(); foreach (JProperty property in style.Properties()) { - var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString()); + var propertyValue = property.Value.ToString(); if (string.IsNullOrWhiteSpace(propertyValue) == false) { cssVals.Add(property.Name + ":" + propertyValue + ";"); @@ -84,7 +84,7 @@ } if (cssVals.Any()) - attrs.Add("style=\"" + string.Join(" ", cssVals) + "\""); + attrs.Add("style=\"" + HttpUtility.HtmlAttributeEncode(string.Join(" ", cssVals)) + "\""); } return new MvcHtmlString(string.Join(" ", attrs)); diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml index 5a570efdb5..8c92ca0d83 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/TextString.cshtml @@ -4,9 +4,9 @@ @if (Model.editor.config.markup != null) { string markup = Model.editor.config.markup.ToString(); - var UmbracoHelper = new UmbracoHelper(UmbracoContext.Current); + var umbracoHelper = new UmbracoHelper(UmbracoContext.Current); - markup = markup.Replace("#value#", UmbracoHelper.ReplaceLineBreaksForHtml(TemplateUtilities.CleanForXss(Model.value.ToString()))); + markup = markup.Replace("#value#", umbracoHelper.ReplaceLineBreaksForHtml(HttpUtility.HtmlEncode(Model.value.ToString()))); markup = markup.Replace("#style#", Model.editor.config.style.ToString()); diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index ec7948a09a..09df754e74 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -119,7 +119,7 @@ - + diff --git a/src/Umbraco.Web/Controllers/UmbLoginController.cs b/src/Umbraco.Web/Controllers/UmbLoginController.cs index e86e48a18b..d446df5683 100644 --- a/src/Umbraco.Web/Controllers/UmbLoginController.cs +++ b/src/Umbraco.Web/Controllers/UmbLoginController.cs @@ -11,11 +11,11 @@ namespace Umbraco.Web.Controllers public class UmbLoginController : SurfaceController { [HttpPost] - public ActionResult HandleLogin([Bind(Prefix="loginModel")]LoginModel model) + public ActionResult HandleLogin([Bind(Prefix = "loginModel")]LoginModel model) { if (ModelState.IsValid == false) { - return CurrentUmbracoPage(); + return CurrentUmbracoPage(); } if (Members.Login(model.Username, model.Password) == false) @@ -30,11 +30,20 @@ namespace Umbraco.Web.Controllers //if there is a specified path to redirect to then use it if (model.RedirectUrl.IsNullOrWhiteSpace() == false) { - return Redirect(model.RedirectUrl); + // validate the redirect url + if (Url.IsLocalUrl(model.RedirectUrl)) + { + return Redirect(model.RedirectUrl); + } + else + { + // if it's not a local url we'll redirect to the root of the current site + return Redirect(base.CurrentPage.Site().Url); + } } //redirect to current page by default - + return RedirectToCurrentUmbracoPage(); //return RedirectToCurrentUmbracoUrl(); } diff --git a/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs b/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs index 51622ed504..2f8ec85075 100644 --- a/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs +++ b/src/Umbraco.Web/Media/EmbedProviders/AbstractOEmbedProvider.cs @@ -1,8 +1,10 @@ -using System.Text; +using System; +using System.Text; using System.Xml; using System.Collections.Generic; using System.Net; using System.Net.Http; +using System.Web; using Newtonsoft.Json; using Umbraco.Core.Media; @@ -27,10 +29,13 @@ namespace Umbraco.Web.Media.EmbedProviders public virtual string BuildFullUrl(string url, int maxWidth, int maxHeight) { + if (Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute) == false) + throw new ArgumentException("Not a valid Url"); + var fullUrl = new StringBuilder(); fullUrl.Append(APIEndpoint); - fullUrl.Append("?url=" + url); + fullUrl.Append("?url=" + HttpUtility.UrlEncode(url)); foreach (var p in RequestParams) fullUrl.Append(string.Format("&{0}={1}", p.Key, p.Value));