Ограничения времени компиляции для строк в F #, аналогично единицам измерения - возможно ли это? - PullRequest
14 голосов
/ 23 февраля 2012

Я разрабатываю веб-приложение с использованием F #. Думая о защите пользовательских строк ввода от SQL, XSS и других уязвимостей.

В двух словах, мне нужны некоторые ограничения времени компиляции, которые позволили бы мне отличать простые строки от тех, которые представляют SQL, URL, XSS, XHTML и т. Д.

У многих языков есть, например. Встроенная в Ruby функция интерполяции строк #{...}.
В F # кажется, что единицы измерения работают очень хорошо, но они доступны только для числовых типов.
Есть несколько решений, использующих время выполнения UoM (ссылка) , однако я думаю, что это накладные расходы для моей цели.

Я посмотрел на 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 : мне нужны ограничения во время компиляции, спасибо Даниэль.

Обновление 2 : я пытаюсь избежать любых накладных расходов во время выполнения (кортежей, структур, различающихся объединений и т. Д.).

Ответы [ 5 ]

7 голосов
/ 30 ноября 2012

Немного опоздал (я уверен, что есть формат времени, в котором разница между 23 февраля и 30 ноября отличается только на один бит), я считаю, что эти строки соответствуют вашей цели:

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

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

type string<[<Measure>] 'm>(Value : string) = struct end
3 голосов
/ 23 февраля 2012

Теоретически можно использовать «единицы» для обеспечения различных видов проверок во время компиляции строк (эта строка «испорчена» пользовательским вводом или очищена? Это имя файла относительное или абсолютное? ...)

На практике я лично не нашел, чтобы это было слишком практичным, поскольку существует так много существующих API, которые просто используют 'string', что вам нужно проявить массу заботы и вручную преобразовать данные отсюда сюда.

Я действительно считаю, что «строки» являются огромным источником ошибок, и что системы типов, которые имеют дело с запятнанностью / канонизацией / т. Д. В строках, станут одним из следующих скачков в статической типизации для уменьшения ошибок, но ядумаю, что это похоже на 15-летний горизонт.Мне было бы интересно, если бы люди пытались использовать F # UoM, чтобы узнать, получат ли они какую-либо выгоду!

3 голосов
/ 24 февраля 2012

Самое простое решение для невозможности сделать

"hello"<unsafe_user_input>

- написать тип с числовым типом для переноса строки, например

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

Тогда у вас естьпроверка времени компиляции ваших строк

2 голосов
/ 05 мая 2012

Вы можете использовать дискриминационные союзы:

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

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

Вы получаете проверку во время компиляции, и добавление двух проверенных строк не сгенерирует проверенную строку (какие единицы измерения позволят).

Если добавленные издержки ссылочных типов слишком велики, вы можете использовать структуры вместо.

2 голосов
/ 23 февраля 2012

Трудно сказать, что вы пытаетесь сделать.Вы сказали, что вам «нужны некоторые ограничения времени выполнения», но вы надеетесь решить эту проблему с помощью единиц измерения, которые строго соответствуют времени компиляции.Я думаю, что простое решение заключается в создании SafeXXXString классов (где XXX - Sql, Xml и т. Д.), Которые проверяют их ввод.

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

Это дает вам время выполнения,не время компиляции, безопасность.Но это просто, самодокументируется и не требует чтения исходного кода компилятора F #, чтобы заставить его работать.

Но, чтобы ответить на ваш вопрос, я не вижу способа сделать это с единицами измерения,Что касается синтаксического сахара, вы можете заключить его в монаду, но я думаю, что он сделает его более неуклюжим, а не меньше.

...