Сложные временные ограничения для строк в F #, аналогичные единицам измерения - возможно ли это?
Я разрабатываю веб-приложение, используя F #. Думая о защите пользовательских строк ввода от SQL, XSS и других уязвимостей.
В двух словах мне нужны ограничения времени компиляции, которые позволили бы мне отличать простые строки от представлений SQL, URL, XSS, XHTML и т.д.
У многих языков это, например. Свойство строковой интерполяции Rubys #{...}
.
С F # кажется, что Единицы измерения работают очень хорошо, но они доступны только для числовых типов.
Существует несколько решений, в которых используется runtime 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. Я пытаюсь избежать накладных расходов во время выполнения (кортежи, структуры, дискриминационные объединения и т.д.).
Ответы
Ответ 1
Немного поздно (я уверен, что есть формат времени, где между 23 февраля и 30 ноября есть только один бит), я считаю, что эти однострочные линии совместимы для вашей цели:
type string<[<Measure>] 'm> = string * int<'m>
type string<[<Measure>] 'm> = { Value : string }
type string<[<Measure>] 'm>(Value : string) = struct end
Ответ 2
В теории можно использовать "единицы" для предоставления различных видов проверок времени компиляции по строкам (является ли эта строка "испорченным" пользовательским вводом или дезинфицирована? является ли это имя файла относительным или абсолютным?...)
На практике я лично не счел это слишком практичным, так как существует так много существующих API, которые просто используют "строку", что вы должны обладать тонкой тщательностью и ручными преобразованиями данных сантехники отсюда до этого.
Я действительно думаю, что "строки" являются огромным источником ошибок, и системы типов, которые имеют дело с taintedness/canonicalization/etc в строках, станут одним из следующих скачков статической типизации для уменьшения ошибок, но я думаю, что 15-летний горизонт. Меня бы интересовали люди, пытающиеся подходить к F # UoM, чтобы узнать, принесут ли они какую-нибудь пользу!
Ответ 3
Простейшее решение неспособности сделать
"hello"<unsafe_user_input>
было бы написать тип, который имел некоторый числовой тип, чтобы обернуть строку, например
type mystring<'t>(s:string) =
let dummyint = 1<'t>
Затем у вас есть проверка времени компиляции в строках
Ответ 4
Трудно сказать, что вы пытаетесь сделать. Вы сказали, что вам нужны некоторые ограничения времени исполнения, но вы надеетесь решить это с помощью единиц измерения, которые строго компилируются. Я считаю, что простым решением является создание классов SafeXXXString
(где XXX
- Sql
, Xml
и т.д.), Которые проверяют их ввод.
type SafeSqlString(sql) =
do
//check `sql` for injection, etc.
//raise exception if validation fails
member __.Sql = sql
Это дает вам время выполнения, а не время компиляции, безопасность. Но он прост, самодокументирован и не требует чтения источника компилятора F #, чтобы он работал.
Но, чтобы ответить на ваш вопрос, я не вижу никакого способа сделать это с помощью единиц измерения. Что касается синтаксического сахара, вы можете инкапсулировать его в монаду, но я думаю, что это сделает его более неуклюжим, не менее.
Ответ 5
Вы можете использовать дискриминированные объединения:
type ValidatedString = ValidatedString of string
type SmellyString = SmellyString of string
let validate (SmellyString s) =
if (* ... *) then Some(ValidatedString s) else None
Вы получаете проверку времени компиляции, и добавление двух проверенных строк не будет генерировать проверенную строку (какие единицы измерения позволят).
Если добавленные служебные данные ссылочных типов слишком велики, вы можете использовать структуры вместо этого.