Last active
August 29, 2015 13:57
-
-
Save jedahu/9881337 to your computer and use it in GitHub Desktop.
Safe strings
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Diagnostics.Contracts; | |
[ContractClass(typeof(ILanguageContract<>))] | |
public interface ILanguage<A> | |
{ | |
A Literal(string s); | |
string Escape(string s); | |
string Render(A a); | |
} | |
[ContractClassFor(typeof(ILanguage<>))] | |
public abstract class ILanguageContract<A> | |
: ILanguage<A> | |
{ | |
A ILanguage<A>.Literal(string s) | |
{ | |
Contract.Requires(Pred.NotNull(s)); | |
Contract.Ensures(Pred.NotNull(Contract.Result<A>())); | |
throw new System.NotImplementedException(); | |
} | |
string ILanguage<A>.Escape(string s) | |
{ | |
Contract.Requires(Pred.NotNull(s)); | |
Contract.Ensures(Pred.NotNull(Contract.Result<string>())); | |
throw new System.NotImplementedException(); | |
} | |
string ILanguage<A>.Render(A a) | |
{ | |
Contract.Requires(Pred.NotNull(a)); | |
Contract.Ensures(Pred.NotNull(Contract.Result<string>())); | |
throw new System.NotImplementedException(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using System.Diagnostics.Contracts; | |
using System.IO; | |
using System.Linq; | |
[Pure] | |
public sealed class SafeString<A> | |
{ | |
private readonly ILanguage<A> lang; | |
private readonly Fragment frag; | |
private SafeString(ILanguage<A> lang, Fragment frag) | |
{ | |
Contract.Requires(Pred.NotNull(lang)); | |
Contract.Requires(Pred.NotNull(frag)); | |
Contract.Requires( | |
typeof (A).IsDefined(typeof (PureAttribute), false), | |
"SafeString must be used with an immutable type. " | |
+ "Make sure the type is immutable, then add the " | |
+ "System.Diagnostics.Contracts.PureAttribute to its class."); | |
Contract.Requires( | |
typeof (A).GetConstructors().Length == 0, | |
"SafeString must be used with a type without public constructors."); | |
this.lang = lang; | |
this.frag = frag; | |
} | |
internal static SafeString<A> Literal(ILanguage<A> lang, string s) | |
{ | |
return new SafeString<A>(lang, new Single(lang.Literal(s))); | |
} | |
internal static SafeString<A> Escape(ILanguage<A> lang, string s) | |
{ | |
return Literal(lang, lang.Escape(s)); | |
} | |
public void Render(TextWriter w) | |
{ | |
foreach (var s in frag.Render(lang)) w.Write(s); | |
} | |
public string Render() | |
{ | |
return string.Concat(frag.Render(lang)); | |
} | |
public SafeString<A> Append(SafeString<A> ss) | |
{ | |
return new SafeString<A>(lang, new Concat(frag, ss.frag)); | |
} | |
public SafeString<A> Lit(string s) | |
{ | |
return new SafeString<A>( | |
lang, | |
new Concat(frag, new Single(lang.Literal(s)))); | |
} | |
public SafeString<A> Esc(string s) | |
{ | |
return Lit(lang.Escape(s)); | |
} | |
private interface Fragment | |
{ | |
IEnumerable<string> Render(ILanguage<A> lang); | |
} | |
private struct Single : Fragment | |
{ | |
private readonly A a; | |
public Single(A a) | |
{ | |
this.a = a; | |
} | |
public IEnumerable<string> Render(ILanguage<A> lang) | |
{ | |
yield return lang.Render(a); | |
} | |
} | |
private struct Concat : Fragment | |
{ | |
private readonly Fragment frag1; | |
private readonly Fragment frag2; | |
public Concat(Fragment frag1, Fragment frag2) | |
{ | |
this.frag1 = frag1; | |
this.frag2 = frag2; | |
} | |
public IEnumerable<string> Render(ILanguage<A> lang) | |
{ | |
return frag1.Render(lang).Concat(frag2.Render(lang)); | |
} | |
} | |
} | |
public static class SafeString | |
{ | |
public static SafeString<A> Literal<A>(ILanguage<A> lang, string s) | |
{ | |
return SafeString<A>.Literal(lang, s); | |
} | |
public static SafeString<A> Escape<A>(ILanguage<A> lang, string s) | |
{ | |
return SafeString<A>.Escape(lang, s); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using FsCheck.Xunit; | |
public abstract class LanguageLaws<T> | |
{ | |
protected abstract ILanguage<T> Language { get; } | |
[Property] | |
public bool Literal_Render_is_identity(string s) | |
{ | |
return s == Language.Render(Language.Literal(s)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using FsCheck.Xunit; | |
public abstract class SafeStringLaws<A> | |
{ | |
static SafeStringLaws() | |
{ | |
TestSetup.Init(); | |
} | |
protected abstract SafeString<A> Literal(string s); | |
protected abstract SafeString<A> Escape(string s); | |
protected abstract ILanguage<A> Language { get; } | |
[Property] | |
public bool Literal_Render_is_identity(string s) | |
{ | |
return s == Literal(s).Render(); | |
} | |
[Property] | |
public bool Escape_Render_is_escape(string s) | |
{ | |
return Language.Escape(s) == Escape(s).Render(); | |
} | |
[Property] | |
public bool | |
Append_distributes_over_string_concatenation( | |
string s1, string s2) | |
{ | |
return | |
s1 + s2 == Literal(s1).Append(Literal(s2)).Render() | |
&& Language.Escape(s1 + s2) == Escape(s1).Append(Escape(s2)).Render() | |
&& s1 + Language.Escape(s2) == Literal(s1).Append(Escape(s2)).Render() | |
&& Language.Escape(s1) + s2.Value == Escape(s1).Append(Literal(s2)).Render(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Diagnostics.Contracts; | |
using system.Net; | |
[Pure] | |
public sealed class Html | |
{ | |
private readonly string htmlString; | |
private Html(string s) | |
{ | |
htmlString = s; | |
} | |
public static SafeString<Html> Literal(string s) | |
{ | |
return SafeString.Literal(Language, s); | |
} | |
public static SafeString<Html> Escape(string s) | |
{ | |
return SafeString.Escape(Language, s); | |
} | |
public static readonly ILanguage<Html> Language = new HtmlLanguage(); | |
private struct HtmlLanguage | |
: ILanguage<Html> | |
{ | |
Html ILanguage<Html>.Literal(string s) | |
{ | |
return new Html(s); | |
} | |
public string Escape(string s) | |
{ | |
return WebUtility.HtmlEncode(s); | |
} | |
public string Render(Html a) | |
{ | |
return a.htmlString; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The only way to construct a SafeString<Html> is using one of Html.Literal() or Html.Escape(). | |
// The only way to append strings is using .Lit() or .Esc(). | |
SafeString<Html> msg = Html.Escape("A Dangerous < message."); | |
SafeString<Html> html = Html.Literal("<message>").Esc("Hello & read ").Append(msg).Lit("</message>"); | |
var tw = new TextWriter(); | |
html.Render(tw); | |
// Using the type-system to our advantage: | |
public void MethodWithEscapedHtmlStringPrecondition(SafeString<Html> str) | |
{ | |
// Assuming the caller has used Literal(), Escape(), Lit(), and Esc() judiciously, | |
// at this point we know we have a correctly escaped HTML string. A stronger | |
// guarantee than if the argument was a string. | |
var htmlString = html.Render(); | |
... | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment