I just see three issues:
- You're making the view harder to read and you're missing a chance to reuse your code.
- You're not properly escaping e-mail address (see RFC5322 and Wikipedia). If you don't then you're open to Code Injection attack (see later).
- Some (older) browsers won't accept the trailing comma after the last array item (see MDN).
To solve the first issue you may write an extension method to build a JavaScript array string from an IEnumerable<T>
:
static string AsJsArrayString<T>(this IEnumerable<T> items)
{
var array = new StringBuilder();
array.Append('[');
array.Append(String.Join(",", items.Select(x => $"'{x}'")));
array.Append(']');
return array.ToString();
}
This code could be optimized and make more terse but I guess you understand the point:
$("#email").emailautocomplete({
domains: @Html.Raw(Model.Organisations.Select(x => x.EmailDomain).AsJsArrayString())
});
This function may also be reused elsewhere.
Now we can look at second issue. E-mail addresses may contain invalid characters for a JavaScript string (for example "
or '
), JavaScript escaping is not the same as JSON escaping (done with Json.Encode()
) but fortunately we have an ad-hoc function HttpUtility.JavaScriptStringEncode()
(JavaScriptEncoder
for .NET Core, see later):
static string AsJsArrayString(this IEnumerable<string> items)
=> "[" + String.Join(",", items.Select(x => "'" + HttpUtility.JavaScriptStringEncode(x) + "'")) + "]";
Using String.Join()
we also quickly fixed last issue in the list. If you can't/don't want to use an extension method you may also inline this function.
Last thing to change is that @Html.Raw()
declaration into something less error-prone (I don't know you but I often forget @Html.Raw()
if it does not always produce an invalid output):
@functions {
public static string ToJsArrayString(IEnumerable<string> items)
{
return "["
+ String.Join(",", items.Select(x => "'" + HttpUtility.JavaScriptStringEncode(x) + "'"))
+ "]";
}
}
Now we may simply write:
$("#email").emailautocomplete({
domains: @ToJsArrayString(Model.Organisations.Select(x => x.EmailDomain))
});
HttpUtility
is a .NET thing, in .NET Core you have a brand you class JavaScriptEncoder
. Let's write an extension method for that:
public static string Encode<T>(this JavaScriptEncoder encoder, IEnumerable<T> items)
{
return '[' + String.Join(",", items.Select(x => EncodeAndQuote(x))) + ']';
string EncodeAndQuote(string text)
=> '"' + encoder.Encode(text) + '"';
}
Used like this:
$("#email").emailautocomplete({
domains: @JavaScriptEncoder.Encode(Model.Organisations.Select(x => x.EmailDomain))
});
Code injection. Unfortunately I've not been clear, domain names can contain characters that need to be escaped. In case you think it's not an issue: would you allow your users to have a perfectly valid and usable email address that, however, will break your Javascript code (I guess in an administrative page) with a chance to execute arbitrary code? I would not.
Let's pick this perfectly valid e-mail address: john.smith@(here\I\can\write\anything)example.com
. If not escaped then it will break your JavaScript code. An easy way to block your service until you will find the issue and fix the problem.
Worse that that, they can execute code with the privileges of the user of that page (and it seems an administrative page). Imagine they write this perfectly valid e-mail address:
john.smith@(",$.post('/api/customers/delete'),")example.com
Your JavaScript code isn't broken but you're executing code they injected, in this case an innocent AJAX POST.
In short: do not EVER trust user input, even when you think you do not need escaping.