F#中字符串的编译时限制,类似于度量单位 - 是否可能?

时间:2012-02-23 16:34:34

标签: string f# units-of-measurement

我正在使用F#开发Web应用程序。考虑保护用户输入字符串不受SQL,XSS和其他漏洞的影响。

用两个词来说,我需要一些编译时约束,这些约束允许我区分普通字符串和表示SQL,URL,XSS,XHTML等的字符串。

许多语言都有它,例如Ruby的原生字符串插值功能#{...} 使用F#,似乎度量单位表现非常好,但它们仅适用于数字类型 有几种解决方案采用运行时 UoM (link),但我认为这是我的目标的开销。

我查看了FSharpPowerPack,似乎很有可能为字符串提出类似的东西:

[<MeasureAnnotatedAbbreviation>] type string<[<Measure>] 'u> = string
// Similarly to Core.LanguagePrimitives.IntrinsicFunctions.retype
[<NoDynamicInvocation>]
let inline retype (x:'T) : 'U = (# "" x : 'U #)
let StringWithMeasure (s: string) : string<'u> = retype s

[<Measure>] type plain
let fromPlain (s: string<plain>) : string =
    // of course, this one should be implemented properly
    // by invalidating special characters and then assigning a proper UoM
    retype s

// Supposedly populated from user input
let userName:string<plain> = StringWithMeasure "John'); DROP TABLE Users; --"
// the following line does not compile
let sql1 = sprintf "SELECT * FROM Users WHERE name='%s';" userName
// the following line compiles fine
let sql2 = sprintf "SELECT * FROM Users WHERE name='%s';" (fromPlain userName)

注意:这只是一个样本;不建议使用SqlParameter。 : - )

我的问题是:有没有一个体面的图书馆呢?是否有可能添加语法糖?
感谢。

更新1 :感谢Daniel。我需要编译时限制。

更新2 :我正在努力避免任何运行时开销(元组,结构,有区别的联合等)。

5 个答案:

答案 0 :(得分:7)

有点晚了(我确定有一种时间格式,在2月23日到11月30日之间只有一点不同),我相信这些单行符合您的目标:

type string<[<Measure>] 'm> = string * int<'m>

type string<[<Measure>] 'm> = { Value : string }

type string<[<Measure>] 'm>(Value : string) = struct end

答案 1 :(得分:3)

理论上可以使用'units'来对字符串进行各种编译时检查(这个字符串'被污染了'用户输入,还是已经消毒?这个文件名是相对的还是绝对的?...)

在实践中,我个人并没有发现它太实用,因为有太多现有的API只使用'字符串',你需要进行大量的处理和手动转换从这里到那里的管道数据。

我认为'字符串'是一个巨大的错误来源,那些处理字符串上的污点/规范化等的类型系统将成为静态类型的下一个跳跃之一,以减少错误,但我认为这就像15年的历史。我会对尝试使用F#UoM的方法感兴趣,看看他们是否能获得任何好处!

答案 2 :(得分:3)

无法做到的最简单的解决方案

"hello"<unsafe_user_input>

将编写一个具有某种数字类型的类型来包装字符串,如

type mystring<'t>(s:string) =
    let dummyint = 1<'t>

然后你对字符串进行编译时检查

答案 3 :(得分:2)

很难说出你想要做什么。你说你“需要一些运行时约束”,但是你希望用测量单位来解决这个问题,它们是严格的编译时间。我认为简单的解决方案是创建验证其输入的SafeXXXString类(XXXSqlXml等)。

type SafeSqlString(sql) =
  do
    //check `sql` for injection, etc.
    //raise exception if validation fails
  member __.Sql = sql

它为您提供运行时,而不是编译时的安全性。但它很简单,自我记录,并且不需要读取F#编译器源来使其工作。

但是,为了回答你的问题,我认为没有办法用计量单位做到这一点。语法糖就可以了,你或许可以把它封装成monad,但我认为它会让它变得更笨重而不是更少。

答案 4 :(得分:2)

您可以使用受歧视的联盟:

type ValidatedString = ValidatedString of string
type SmellyString = SmellyString of string

let validate (SmellyString s) =
  if (* ... *) then Some(ValidatedString s) else None

您将获得编译时检查,并且添加两个经过验证的字符串不会生成经过验证的字符串(这些度量单位将允许)。

如果引用类型的额外开销太大,则可以改为使用结构。