Skip to content

Commit 437a6e5

Browse files
committed
Support for converting outgoing URLs where un-rooted paths into rooted paths (common in ASP.NET WebForms). #286
1 parent 856804f commit 437a6e5

File tree

2 files changed

+58
-2
lines changed

2 files changed

+58
-2
lines changed

Diff for: src/i18n.Tests/Tests/ResponseFilterTests.cs

+31-2
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,53 @@
33
using System.Linq;
44
using System.Text;
55
using System.Text.RegularExpressions;
6+
using System.Web;
67
using Microsoft.VisualStudio.TestTools.UnitTesting;
78
using i18n.Domain.Concrete;
9+
using i18n.Helpers;
10+
using NSubstitute;
811

912
namespace i18n.Tests
1013
{
1114
[TestClass]
1215
public class ResponseFilterTests
1316
{
14-
void Helper_ResponseFilter_can_patch_html_urls(string suffix, string pre, string expectedPatched, Uri requestUrl = null)
17+
void Helper_ResponseFilter_can_patch_html_urls(string suffix, string pre, string expectedPatched, string requestUrl = null)
1518
{
19+
HttpRequestBase fakeRequest = Substitute.For<HttpRequestBase>();
20+
HttpResponseBase fakeResponse = Substitute.For<HttpResponseBase>();
21+
HttpContextBase fakeContext = Substitute.For<HttpContextBase>();
22+
23+
fakeRequest.Url.Returns(requestUrl.IsSet() ? new Uri(requestUrl) : null);
24+
fakeResponse.Headers.Returns(new System.Net.WebHeaderCollection
25+
{
26+
//{ "Authorization", "xyz" }
27+
});
28+
29+
fakeContext.Request.Returns(fakeRequest);
30+
fakeContext.Response.Returns(fakeResponse);
31+
1632
i18n.EarlyUrlLocalizer obj = new i18n.EarlyUrlLocalizer(new UrlLocalizer());
17-
string post = obj.ProcessOutgoing(pre, suffix, null);
33+
string post = obj.ProcessOutgoing(pre, suffix, fakeContext);
1834
Assert.AreEqual(expectedPatched, post);
1935
}
2036

2137
[TestMethod]
2238
public void ResponseFilter_can_patch_html_urls()
2339
{
40+
// Non-rooted path as href/src url. This should become rooted based on the path of the current request url.
41+
// See impl. details in EarlyUrlLocalizer.LocalizeUrl. Reference issue #286.
42+
Helper_ResponseFilter_can_patch_html_urls("fr", "<img src=\"123\"></img>" , "<img src=\"/fr/123\"></img>" , "http://example.com/Default.aspx");
43+
Helper_ResponseFilter_can_patch_html_urls("fr", "<img src=\"123\"></img>" , "<img src=\"/fr/blogs/123\"></img>" , "http://example.com/blogs/Default.aspx");
44+
Helper_ResponseFilter_can_patch_html_urls("fr", "<img src=\"123\"></img>" , "<img src=\"/fr/blogs/123\"></img>" , "http://example.com/blogs/");
45+
Helper_ResponseFilter_can_patch_html_urls("fr", "<img src=\"123\"></img>" , "<img src=\"/fr/123\"></img>" , "http://example.com/blogs");
46+
// NB: for the following we use .txt rather than .jpg because the defaule outgoing URL filter excludes .jpg urls.
47+
Helper_ResponseFilter_can_patch_html_urls("fr", "<img src=\"content/fred.txt\"></img>" , "<img src=\"/fr/content/fred.txt\"></img>" , "http://example.com/blog");
48+
Helper_ResponseFilter_can_patch_html_urls("fr", "<img src=\"content/fred.txt\"></img>" , "<img src=\"/fr/blog/content/fred.txt\"></img>" , "http://example.com/blog/");
49+
Helper_ResponseFilter_can_patch_html_urls("fr", "<img src=\"/content/fred.txt\"></img>" , "<img src=\"/fr/content/fred.txt\"></img>" , "http://example.com/blog/");
50+
Helper_ResponseFilter_can_patch_html_urls("fr", "<img src=\"http://example.com/content/fred.txt\"></img>", "<img src=\"http://example.com/fr/content/fred.txt\"></img>" , "http://example.com/blog/");
51+
Helper_ResponseFilter_can_patch_html_urls("fr", "<img src=\"http://other.com/content/fred.txt\"></img>" , "<img src=\"http://other.com/content/fred.txt\"></img>" , "http://example.com/blog/"); // NB: foreign site so no langtag added
52+
2453
// One attribute.
2554
Helper_ResponseFilter_can_patch_html_urls(
2655
"fr",

Diff for: src/i18n/Concrete/EarlyUrlLocalizer.cs

+27
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,33 @@ protected string LocalizeUrl(System.Web.HttpContextBase context, string url, str
232232
if (!m_urlLocalizer.FilterOutgoing(url, requestUrl)) {
233233
return null; } // original
234234

235+
// If url is un-rooted...make it rooted based on any current request URL.
236+
// This is to resolve problems caused to i18n with resource URL paths relative to the
237+
// current page (see issue #286).
238+
// By un-rooted we mean the url is not absolute (i.e. it starts from the path component),
239+
// and that that path component itself is a relative path (i.e. it doesn't start with a /).
240+
// In doing so we also eliminate any "../..", "./", etc.
241+
// Examples:
242+
// url (before) requestUrl url (after)
243+
// -------------------------------------------------------------------------------------------------------------------
244+
// content/fred.jpg http://example.com/blog/ /blog/content/fred.jpg (changed)
245+
// content/fred.jpg http://example.com/blog /content/fred.jpg (changed)
246+
// /content/fred.jpg http://example.com/blog/ /content/fred.jpg (unchanged)
247+
// http://example.com/content/fred.jpg http://example.com/blog/ http://example.com/content/fred.jpg (unchanged)
248+
// See also test cases in ResponseFilterTests.ResponseFilter_can_patch_html_urls.
249+
//
250+
if (requestUrl != null) {
251+
bool urlIsUnrooted = !url.StartsWith("/")
252+
&& (!url.Contains(":") || !Uri.IsWellFormedUriString(url, UriKind.Absolute));
253+
// NB: the above is somewhat elaborate so as to avoid an object allocation
254+
// in all but edge cases. Note also that Uri.IsWellFormedUriString internally
255+
// simply does a Uri.TryCreate (at least as of .NET 4.6.1).
256+
if (urlIsUnrooted) {
257+
Uri newUri = new Uri(requestUrl, url);
258+
url = newUri.PathAndQuery;
259+
}
260+
}
261+
235262
// Localize the URL.
236263
return m_urlLocalizer.SetLangTagInUrlPath(context, url, UriKind.RelativeOrAbsolute, langtag);
237264
}

0 commit comments

Comments
 (0)