B9254a3eaa9b34627e7bf617b32b0ed9

This function changes all URLs inside a CSS file which are relative to the path of that CSS file into URLs which are relative to the web application. For instance, if the CSS file path is /content/site.css, then:
- Any occurence of url(path/to/image.gif) will become url(/content/path/to/image.gif)
- Any occurence of url(../path/to/image.gif) will become url(/path/to/image.gif)
- ...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public string Transform(string cssPath, string cssContent)
{
        return Regex.Replace(cssContent, @"url\((?<url>.*?)\)", 
			match => FixUrl(cssPath, match),
			RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
}

/// <summary>
/// Assume cssPath is /content/site.css:
///     * in: path/to/image.gif     -> out: /content/path/to/image.gif
///     * in: ../path/to/image.gif  -> out: /path/to/image.gif
///     * in: /path/to/image.gif    -> out: /path/to/image.gif
/// </summary>
private static string FixUrl(string cssPath, Match match)
{
	try
	{
		var url = match.Groups["url"].Value;
		const string template = "url({0})";
		if (url.StartsWith("/"))
			return url;
		var adjustedResourceFolder = cssPath.Substring(0, resourcePath.LastIndexOf("/"));
		var backFolderCount = Regex.Matches(url, @"\.\./").Count;
		for (int i = 0; i < backFolderCount; i++)
		{
			url = url.Substring(3);
			adjustedResourceFolder = adjustedResourceFolder.Substring(0,
				adjustedResourceFolder.LastIndexOf("/"));
		}
		return string.Format(template, (adjustedResourceFolder + "/" + url));
	}
	catch (Exception ex)
	{
		return match.Value;
	}
}

Refactorings

No refactoring yet !

F9a9ba6663645458aa8630157ed5e71e

Ants

October 31, 2009, October 31, 2009 08:04, permalink

No rating. Login to rate!

Looks like a bug in line 21 where it'll return "/path/to/image.gif" instead of "url(/path/to/image.gif)". Or is this intentional?

The logic in lines 22-29 doesn't seem like it'll handle the case when url = "path/to/../to/image.gif". Or is this kind of input illegal?

B9254a3eaa9b34627e7bf617b32b0ed9

Buu Nguyen

October 31, 2009, October 31, 2009 09:52, permalink

No rating. Login to rate!

@Ants: Thanks. You're right about the bug in line 21. Re. 22-29, I don't think "path/to/../to/image.gif" is a valid CSS URL. How do you come up with that in the first place :)?

The newly refactored code is below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static string FixUrl(string cssPath, Match match)
{
	try
	{
		const string template = "url(\"{0}\")";
		var url = match.Groups["url"].Value.Trim('\"', '\'');
		if (url.StartsWith("/"))
			return string.Format(template, url);

		var cssFolder = cssPath.Substring(0, cssPath.LastIndexOf("/"));
		var backFolderCount = Regex.Matches(url, @"\.\./").Count;
		for (int i = 0; i < backFolderCount; i++)
		{
			url = url.Substring(3); // skip 1 '../'
			cssFolder = cssFolder.Substring(0, cssFolder.LastIndexOf("/")); // move back 1 folder
		}
		return string.Format(template, cssFolder + "/" + url);
	}
	catch (Exception ex)
	{
		return match.Value;
	}
}
F9a9ba6663645458aa8630157ed5e71e

Ants

October 31, 2009, October 31, 2009 18:53, permalink

No rating. Login to rate!

@buu: I came up with "path/to/../to/image.gif" from experience because this is how people used to hack older versions of IIS by having a path like: "images/../../windows/system32/notepad.exe". Newer versions of IIS won't let you move up past the root anymore.

I think that having the "../" embedded in the path is legal. I didn't see anything in the CSS grammar nor in HTTP RFC saying that if a URL is relative, all the "../" have to be at the beginning of the path. Do you have a reference that says this? I do know that often FireFox and IE can't agree on how to interpret a relative path, but often it's already broken even without the "../".

BTW, did you see this article about fixing up relative URL paths in CSS?
http://devtoolshed.com/content/fixing-relative-paths-c-aspnet-when-using-url-rewriting

B9254a3eaa9b34627e7bf617b32b0ed9

Buu Nguyen

November 1, 2009, November 01, 2009 06:52, permalink

No rating. Login to rate!

@Ants: that's an interesting observation. I am not aware of any source saying "../" is not allowed in the middle/end either. I'm thinking whether this is typical enough to tackle it in the code though.

Thanks for the link, it's a super cool technique.

F9a9ba6663645458aa8630157ed5e71e

Ants

November 1, 2009, November 01, 2009 11:54, permalink

No rating. Login to rate!

This handles relative paths in both the cssPath as well as the url.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
    class PathBuilder
    {
        List<string> _segments = new List<string>();

        public IEnumerable<string> Segments
        {
            get { return _segments.ToArray<string>(); }
        }

        public void Add(IEnumerable<string> segments)
        {
            foreach (string segment in segments)
                Add(segment);
        }

        public void Add(string segment)
        {
            switch (segment)
            {
            case "":
            case ".":
                // Do nothing
                break;

            case "..":
                RemoveEnd();
                break;

            default:
                _segments.Add(segment);
                break;
            }
        }

        public void RemoveEnd()
        {
            if (_segments.Count > 0)
                _segments.RemoveAt(_segments.Count - 1);
        }

        public override string ToString()
        {
            return "/" + String.Join("/", _segments.ToArray());
        }
    }

    static IEnumerable<string> GetSegments(string path)
    {
        if (String.IsNullOrEmpty(path))
            return new List<string>();
        return path.Split('/');
    }

    static string FixUrl(IEnumerable<string> baseSegments, Match match)
    {
        var builder = new PathBuilder();
        var url = match.Groups["url"].Value.Trim('\"', '\'');
        if (!url.StartsWith("/"))
            builder.Add(baseSegments);
        builder.Add(GetSegments(url));
        return string.Format("url(\"{0}\")", builder.ToString());
    }

    static IEnumerable<string> GetBaseSegments(string cssPath)
    {
        var baseBuilder = new PathBuilder();
        baseBuilder.Add(GetSegments(cssPath));
        baseBuilder.RemoveEnd();
        return baseBuilder.Segments;
    }

    public static string Transform(string cssPath, string cssContent)
    {
        var baseSegments = GetBaseSegments(cssPath);
        return Regex.Replace(cssContent,
                             @"url\((?<url>.*?)\)",
                             match => FixUrl(baseSegments, match),
                             RegexOptions.IgnoreCase
                             | RegexOptions.Singleline 
                             | RegexOptions.ExplicitCapture);
    }

Your refactoring





Format Copy from initial code

or Cancel