/// <summary> /// Detects a loop in the redirects list given the new redirect. /// Uses Floyd's cycle-finding algorithm. /// </summary> /// <param name="oldUrl">Old URL for new redirect</param> /// <param name="newUrl">New URL for new redirect</param> /// <returns>True if loop detected, false if no loop detected</returns> private static bool DetectLoop(string oldUrl, string newUrl) { // quick check for any links to this new redirect if (!_redirects.ContainsKey(newUrl) && !_redirects.Any(x => x.Value.NewUrl.Equals(oldUrl))) return false; // clone redirect list var linkedList = _redirects.ToDictionary(entry => entry.Key, entry => entry.Value); var redirect = new Redirect() { OldUrl = oldUrl, NewUrl = newUrl }; // add new redirect to cloned list for traversing if (!linkedList.ContainsKey(oldUrl)) linkedList.Add(oldUrl, redirect); // Use Floyd's cycle finding algorithm to detect loops in a linked list var slowP = redirect; var fastP = redirect; while (slowP != null && fastP != null && linkedList.ContainsKey(fastP.NewUrl)) { slowP = linkedList[slowP.NewUrl]; fastP = linkedList[linkedList[fastP.NewUrl].NewUrl]; if (slowP == fastP) { return true; } } return false; }
/// <summary> /// Update a given redirect /// </summary> /// <param name="redirect">Redirect to update</param> /// <returns>Updated redirect if successful</returns> public static Redirect UpdateRedirect(Redirect redirect) { if (redirect == null) throw new ArgumentNullException("redirect"); if (!redirect.OldUrl.IsSet()) throw new ArgumentNullException("redirect.OldUrl"); if (!redirect.NewUrl.IsSet()) throw new ArgumentNullException("redirect.NewUrl"); //Ensure starting slash redirect.OldUrl = redirect.OldUrl.EnsurePrefix("/").ToLower(); redirect.NewUrl = redirect.NewUrl.EnsurePrefix("/").ToLower(); var existingRedirect = _redirects.ContainsKey(redirect.OldUrl) ? _redirects[redirect.OldUrl] : null; if (existingRedirect != null && existingRedirect.Id != redirect.Id) throw new ArgumentException("A redirect for " + redirect.OldUrl + " already exists"); //get DB Context, set update time, and persist var db = ApplicationContext.Current.DatabaseContext.Database; redirect.LastUpdated = DateTime.Now.ToUniversalTime(); db.Update(redirect); //Update in-memory list _redirects[redirect.OldUrl] = redirect; //return updated redirect return redirect; }
/// <summary> /// Update a given redirect /// </summary> /// <param name="redirect">Redirect to update</param> /// <returns>Updated redirect if successful</returns> public static Redirect UpdateRedirect(Redirect redirect) { if (redirect == null) throw new ArgumentNullException("redirect"); if (!redirect.OldUrl.IsSet()) throw new ArgumentNullException("redirect.OldUrl"); if (!redirect.NewUrl.IsSet()) throw new ArgumentNullException("redirect.NewUrl"); //Ensure starting slash if(!redirect.IsRegex) redirect.OldUrl = redirect.OldUrl.EnsurePrefix("/").ToLower(); // Allow external redirects and ensure slash if not absolute redirect.NewUrl = Uri.IsWellFormedUriString(redirect.NewUrl, UriKind.Absolute) ? redirect.NewUrl.ToLower() : redirect.NewUrl.EnsurePrefix("/").ToLower(); var existingRedirect = _redirects.ContainsKey(redirect.OldUrl) ? _redirects[redirect.OldUrl] : null; if (existingRedirect != null && existingRedirect.Id != redirect.Id) throw new ArgumentException("A redirect for " + redirect.OldUrl + " already exists"); if (!redirect.IsRegex && DetectLoop(redirect.OldUrl, redirect.NewUrl)) throw new ApplicationException("Adding this redirect would cause a redirect loop"); //get DB Context, set update time, and persist var db = ApplicationContext.Current.DatabaseContext.Database; redirect.LastUpdated = DateTime.Now.ToUniversalTime(); db.Update(redirect); //if we are changing the oldUrl property, let's move things around var oldRedirect = _redirects.FirstOrDefault(x => x.Value.Id.Equals(redirect.Id)); if (oldRedirect.Value != null && redirect.OldUrl != oldRedirect.Value.OldUrl) _redirects.Remove(oldRedirect.Value.OldUrl); //Update in-memory list _redirects[redirect.OldUrl] = redirect; //return updated redirect return redirect; }