Итак, продолжение веселья началось здесь …
содержание
Часть 2
Где мы были так далеко …
Сначала краткое резюме того, что мы рассмотрели в первой части;
- Разделители выражений, например
/yes/
или%yes%
- Синтаксис модификатора шаблона, например,
/yes/i
- Метасимволы …
- Начальное и конечное утверждения:
^
и$
например/^yes$/
- Квантификаторы длины, которые применяются к предыдущему символу в шаблоне:
- Квантификатор «один или несколько»:
+
- «Мин / макс» количественно определяемые фигурные скобки: например,
{5,20}
- Квантификатор «один или несколько»:
- Введенные классы символов, например
[a-zA-Z0-9]
- Начальное и конечное утверждения:
Вы также столкнулись с функциями preg_match () и preg_match_all () .
Пришло время еще немного синтаксиса, в качестве примера …
Охота на .jp (e) gs
Некоторые приложения сохраняют файлы JPEG с расширением .jpeg
а все остальные используют .jpg
. Теперь, если у меня есть каталог, который, как я знаю, содержит несколько JPEG-файлов, которые можно назвать с использованием любого расширения файла, как мне их идентифицировать? И как мне отфильтровать все остальные типы файлов в каталоге одновременно?
<?php $dh = opendir('/home/harryf/gallery'); while ( ($file = readdir($dh)) !== FALSE ) { if ( preg_match('/^.*.jpe?g$/i', $file ) ) { print "$filen"; } } closedir($dh);
<?php $dh = opendir('/home/harryf/gallery'); while ( ($file = readdir($dh)) !== FALSE ) { if ( preg_match('/^.*.jpe?g$/i', $file ) ) { print "$filen"; } } closedir($dh);
Увеличение этого шаблона — /^.*.jpe?g$/i
что у меня есть? ОК метасимволы ^
и $
вы видели ранее, и знайте, что они соответствуют началу и концу строки. Также известный вам модификатор шаблона / i означает «без учета регистра» — имена файлов могут быть прописными или строчными. Что еще у меня есть здесь?
?
это еще один метасимвол: еще один квантификатор длины, аналогичный +
и фигурные скобки, которые вы уже видели. Это означает «ноль или точно один из предшествующих символов». Итак, эта часть примера: jpe?g
означает;
Я ищу последовательность символов, начинающуюся с буквы «j», затем «p», затем, необязательно, буквы «e» и, наконец, буквы «g».
Но это не единственный квантификатор длины, который я ввел в этот шаблон. В начале у меня также есть *
квантификатор:
/^. * .jpe?g$/i
Квантификатор *
означает «ноль или более предшествующего символа» — нет максимального ограничения, нет минимального ограничения.
Хорошо — но к чему применяется квантификатор *
— ну, предыдущий символ в шаблоне — точка:. который также является метасимволом, но больше похож на классы символов, которые вы видели в части 1. Это означает «любой символ» — он будет соответствовать чему угодно (есть исключение из того, к которому я вернусь позже). Итак, в сочетании с квантификатором «ноль или более» *
начало паттерна говорит…
Мне все равно, что начало имени файла — все разрешено любой длины 1 (меня интересует только расширение файла)
Что оставляет мне только необходимость объяснить, что .
в середине /^.* . jpe?g$/i
/^.* . jpe?g$/i
означает …
Избегающие метасимволы
Ну, это относится к буквальному разделителю имени файла, например, «mypicture . JPG». Поскольку точка обычно является метасимволом в регулярных выражениях, но, поскольку мне нужно, чтобы он соответствовал разделителю имени файла, я должен поставить обратную косую черту перед ним, чтобы избежать его. Размещение обратной косой черты в шаблоне указывает механизму регулярных выражений не рассматривать следующий символ как метасимвол.
Есть также функция preg_quote () , предназначенная для экранирования таких вещей, как пользовательский ввод, для встраивания в шаблон — подробнее об этом чуть позже .
Есть немного больше деталей относительно того, какие персонажи должны убежать. Один пример: внутри класса символов регулярных выражений нет необходимости (хотя это не больно, если вы это делаете) экранировать все метасимволы: некоторые метасимволы, такие как «+» и «*», автоматически принимают свое буквальное значение. Между тем другие символы должны быть экранированы в дополнение к обычному набору метасимволов, если они предназначены для буквального значения, такого как «-», которое обычно указывает диапазон в классе символов. Когда вы начнете запоминать синтаксис, это станет очевидным, когда и где вам нужно экранировать символы — не беспокойтесь сейчас.
Вы должны знать, однако, что когда дело доходит до чрезмерного экранирования, жизнь может доставлять удовольствие, потому что строки PHP также используют обратную косую черту для экранирования определенных символов, например;
print 'Tuesday's Child'; # Just a normal string
print 'Tuesday's Child'; # Just a normal string
И веселее, если вы используете двойные кавычки. В идеальном мире у нас были бы буквальные регулярные выражения в виде функции PHP, такие как Perl и Javascript. Но в любом случае … в большинстве случаев это не будет беспокоить вас, только когда это произойдет, это может свести вас с ума.
Поиск и замена
Пока мы только соответствовали. Как насчет замены?
Довольно популярная функция, добавляемая на сайт, хотя и немного «не модная» со времен AJAX, является «подсветкой» для посетителей, которые были направлены на ваш сайт поисковой системой. Вы определяете поисковый термин, который они использовали, глядя на реферер HTTP и выделяя соответствующие слова в своем HTML, используя что-то вроде тега span.
На самом деле, делать это — это, пожалуй, не самая умная идея — гораздо лучше использовать Javascript и сохранять некоторые циклы ЦП сервера, но это хороший пример для иллюстрации поиска и замены регулярных выражений, а также некоторые потенциальные ошибки безопасности.
Итак, начало, наивная реализация. Я не буду пытаться воспроизвести реферер HTTP, а сделаю это проще, используя URL-запрос, который будет помещен в переменную $_GET['q']
…
Важное примечание: — этот пример небезопасен (намеренно) — уберите эти пальцы с CTRL+C
!
<?php $text = 'The quick brown fox jumps over the lazy dog'; # Do we have a search term? if ( isset($_GET['q']) ) { # Escape the input - make sure it won't contain # any regex meta-characters $q = preg_quote($_GET['q'], '/'); # Replace and instances of the search term with the # same but nested in a span tag... $text = preg_replace( "/b($q)b/i", # Pattern '<span class="hilite">$1</span>', # Replacement $text # Subject ); } ?> <html><head><title>Hilite</title> <style type="text/css">.hilite { background-color: yellow }</style> </head> <body> <?php print $text; ?> </body> </html>
<?php $text = 'The quick brown fox jumps over the lazy dog'; # Do we have a search term? if ( isset($_GET['q']) ) { # Escape the input - make sure it won't contain # any regex meta-characters $q = preg_quote($_GET['q'], '/'); # Replace and instances of the search term with the # same but nested in a span tag... $text = preg_replace( "/b($q)b/i", # Pattern '<span class="hilite">$1</span>', # Replacement $text # Subject ); } ?> <html><head><title>Hilite</title> <style type="text/css">.hilite { background-color: yellow }</style> </head> <body> <?php print $text; ?> </body> </html>
Хорошо, позвольте мне сначала объяснить, что делает код, а затем объяснить, почему это небезопасно. Увеличение интересной части …
# Escape the input - make sure it won't contain # any regex meta-characters $q = preg_quote($_GET['q'], '/'); # Replace and instances of the search term with the # same but nested in a span tag... $text = preg_replace( "/b($q)b/i", # Pattern '<span class="hilite">$1</span>', # Replacement $text # Subject );
# Escape the input - make sure it won't contain # any regex meta-characters $q = preg_quote($_GET['q'], '/'); # Replace and instances of the search term with the # same but nested in a span tag... $text = preg_replace( "/b($q)b/i", # Pattern '<span class="hilite">$1</span>', # Replacement $text # Subject );
preg_quote ()
Первое, что я здесь делаю, это цитирование входящего параметра запроса, чтобы, если он содержит что-либо, похожее на метасимвол регулярного выражения или любой другой синтаксис регулярного выражения, он был экранирован обратной косой чертой (если вы вставите оператор print в пример $q
, вы сможете выяснить, что происходит).
Теперь preg_quote()
ставит обратную косую черту перед любым из следующих символов…
. + * ? [ ^ ] $ ( ) { } = ! | :
По сути, это все, что можно принять за синтаксис регулярных выражений… за исключением разделителя выражений. preg_quote()
что здесь делает второй аргумент preg_quote()
…
$q = preg_quote($_GET['q'], '/' );
Второй аргумент сообщает preg_quote()
какой разделитель выражений вы используете, и таким образом избегает его.
Доза страха и отвращения: если вам не удалось избежать пользовательского ввода, а затем внедрить его в регулярное выражение, вы открыли дверь для ввода команд — ваши пользователи смогут сообщить вашему движку регулярных выражений, что делать. В лучшем случае это приведет к появлению сообщений об ошибках (о которых, как мы надеемся, вы молчите ), в то время как сценарии наихудшего случая могут стать очень уродливыми, в зависимости от того, что вы делаете — не забывайте.
preg_replace ()
Так, что делает следующая часть этого сценария?
$text = preg_replace( "/b($q)b/i", '<span class="hilite">$1</span>', $text );
$text = preg_replace( "/b($q)b/i", '<span class="hilite">$1</span>', $text );
Он использует функцию preg_replace (), чтобы обернуть все совпадения входного поискового термина в тег span. Вы, вероятно, счастливы с str_replace (), верно? Хорошо, preg_replace()
— это, по сути, одно и то же, но вместо простой подстановки строк, оно упаковано с совершенством регулярных выражений.
Теперь шаблон нуждается в некотором объяснении …
"/b($q)b/i"
Модификатор шаблона / i в конце, который вы узнаете, означает «без учета регистра» — это позволяет мне выделить больше «хитов» для входящего поискового запроса.
Границы слова, символы слова … и все остальное
Как насчет b
который появляется дважды? Это метасимвол, означающий «утверждать границу слова». Это что-то вроде мета-символов ^
и $
вы видели раньше, но в то время как они утверждают начало и конец строки, метасимвол b
утверждает «край слова», например, точку, где есть пробел, пунктуация и т. Д. рядом с последовательностью символов слова. Вот как руководство по PHP определяет границу слова …
Граница слова — это позиция в строке темы, где текущий символ и предыдущий символ не совпадают одновременно с w или W (т. Е. Один соответствует w, а другой — с W), или с началом или концом строки, если первый или последний символ соответствует ш соответственно.
… Аллес клар? В руководстве определены границы слов в терминах двух других метасимволов, которые мы еще не рассмотрели: w
— «символ слова» и W
Не паникуйте — здесь нет ничего действительно нового. Обе они по сути являются сокращением для классов символов регулярных выражений, которые вы видели ранее, что избавляет вас от необходимости определять свои собственные. Вот ручное определение для w
;
Символ «слово» — это любая буква, цифра или символ подчеркивания
… и, как следствие, W
— это все остальное — все, что не является символом слова (например, знаки препинания, переводы строки и пробелы).
Перегрузка деталей !: теперь w
на самом деле не обязательно совпадает с классом символов [a-zA-Z0-9_]
— часть определения вручную, которую я пропустил;
Определение букв и цифр контролируется таблицами символов PCRE и может отличаться, если происходит сопоставление для конкретной локали. Например, в локали «fr» (французский) некоторые коды символов, превышающие 128, используются для букв с надстрочными знаками, и им соответствует w.
… другими словами, w
могли бы позволить этим дерзким иностранцам проникнуть не-ASCII-персонажей мимо ваших шаблонов проверки! Здесь есть длинная история, которую я пропущу, но вы можете получить еще один намек на понимание здесь — посмотрите примечание о локалях и знайте, что это относится к b
по расширению.
Так где мы были? Попытка выяснить, что означает «граница слова» b
: в основном начало или конец любой последовательности букв или цифр. Другими словами, это полезный инструмент, чтобы помочь определить слова. Итак, давайте снова посмотрим на полный шаблон …
Sub шаблоны
"/b($q)b/i"
Учитывая границы двух слов, мы, кажется, здесь сопоставляем слова. Но что это посередине: ($q)
? Ну, $q
— это просто обычная переменная PHP — поисковый запрос пользователя, который становится частью шаблона благодаря использованию двойных кавычек . Так, что круглые скобки делают вокруг этого? Они, как вы уже догадались, также являются метасимволами регулярных выражений: разделителями для каждой из сторон подшаблонов 2 .
Подшаблоны — это способ группировки частей шаблона. Они имеют ряд применений (подробнее о них я расскажу в другой раз), но для целей нашей операции поиска и замены они позволяют пометить часть шаблона как «особенно интересную» — мы не Мы хотим, чтобы этот пробел находился по обе стороны от поискового запроса, предоставленного пользователем — мы просто хотим захватить само слово и заменить его Чтобы увидеть, как это работает, вам нужно взглянуть на следующий аргумент preg_replace()
;
$text = preg_replace( "/b($q)b/i", '<span class="hilite">$1</span>', $text );
$text = preg_replace( "/b($q)b/i", '<span class="hilite">$1</span>', $text );
… '<span class="hilite">$1</span>'
. Это то, что мы хотим заменить поисковым термином. Вы можете увидеть тег span там, ожидая с ожиданием, но как же в него внедряется соответствующее слово? Через $1
— обратная ссылка на подшаблон . Вам придется прочитать документацию для полного описания синтаксиса обратных ссылок, но, вкратце, они являются «указателями», чтобы вы соответствовали вашему шаблону. Обратная ссылка $0
всегда будет доступна и соответствует полному совпадению. Дополнительные обратные ссылки, такие как $1
будут существовать, если вы будете использовать подшаблоны (чем больше подшаблонов, тем больше будет нумерованных обратных ссылок).
Итак, подытоживая все это, если я preg_replace()
слово «лиса», preg_replace()
найдет его здесь в $text
: brown fox jumped
и заменил его brown <span class="hilite">fox</span> jumped
.
Найди отверстие XSS
Есть небольшая проблема, которую вы, возможно, уже заметили. В то время как я мудро использовал preg_quote()
чтобы избежать любого ввода, которое похоже на синтаксис регулярных выражений, что, если поисковый термин содержит HTML / Javascript? Прямо сейчас это потенциально открыто для межсайтового скриптинга . Вместо того, чтобы просто заменить, мне нужно сначала выполнить поиск по шаблону через htmlspecialchars () . Но как?
Небольшое сканирование модификаторов шаблона и вы найдете модификатор / e;
Если этот модификатор установлен, preg_replace () выполняет обычную замену обратных ссылок в строке замены, оценивает ее как код PHP и использует результат для замены строки поиска. Одинарные и двойные кавычки экранируются обратной косой чертой в замещенных обратных ссылках.
Хорошо — проблема решена …
Предупреждение: это также потенциально небезопасно и имеет более серьезные последствия!
$text = preg_replace( "/b($q)b/ie", '"<span class="hilite">".htmlspecialchars("$1")."</span>"', $text );
$text = preg_replace( "/b($q)b/ie", '"<span class="hilite">".htmlspecialchars("$1")."</span>"', $text );
Eval () это зло!
С модификатором / e, прикрепленным к шаблону, замещающая строка перестает быть строкой и становится PHP-кодом, готовым для eval () . В тот момент, когда вы услышите eval()
слово «зло» должно появиться на кончике вашего языка. Вот рассказ о том, что может случиться .
Простое правило: не используйте модификатор / e . Помимо последствий для безопасности, заменяющий код становится чем-то, что необходимо анализировать, интерпретировать и выполнять при каждой замене (если вам нужен настоящий страх и отвращение, скажите «плохая производительность»). И, эй, написание кода для PHP eval — это умопомрачительное упражнение в кавычках.
Так какая альтернатива?
preg_replace_callback ()
Вместо использования preg_replace()
используйте preg_replace_callback()
, избегая уловок и предлагая значительно лучшую производительность. Вместо того, чтобы предоставлять ему строку кода, вы даете ему имя функции для выполнения. Каждый раз, когда необходимо произвести замену, функция будет вызываться при совпадении. Итак, вот окончательное решение;
function highlight_search($matches) { return sprintf( '<span class="hilite">%s</span>', htmlspecialchars($matches[1]) ); } $text = preg_replace_callback( "/b($q)b/i", 'highlight_search', $text );
function highlight_search($matches) { return sprintf( '<span class="hilite">%s</span>', htmlspecialchars($matches[1]) ); } $text = preg_replace_callback( "/b($q)b/i", 'highlight_search', $text );
Второй аргумент preg_replace_callback()
— это имя моей функции обратного вызова highlight_search()
, которая будет вызываться каждый раз при замене совпадения.
Функция обратного вызова должна обрабатывать один параметр: $matches
, который всегда будет индексированным массивом. И во многом так же, как и в обратных preg_replace()
, первый элемент в массиве всегда будет существовать и соответствовать полному совпадению — то есть $matches[0]
match $matches[0]
. Суб-шаблонам присваиваются более высокие индексы в массиве — в этом случае мой суб-шаблон имеет значение $matches[1]
match $matches[1]
поэтому все, что мне нужно сделать, это запустить его через htmlspecialchars () , внедрить его в тег span с помощью sprintf () , верни его и я закончу. Возвращаемое значение используется в качестве замены.
Помните, что использованный мною подход, заключающийся в простом размещении входящего поискового термина непосредственно в шаблоне (после экранирования), вероятно, не идеален с точки зрения удобства использования. Необходимо подумать о таких вещах, как пунктуация в поисковом запросе, и, возможно, было бы разумнее разбить поисковый запрос на куски и сначала немного его проанализировать. Таким образом, этот пример является неоптимальным решением, но я думаю, что ему удастся проиллюстрировать поиск регулярных выражений и заменить его достаточно хорошо.
Хорошо — это обертка для этой части. Еще как-нибудь в другой раз (и мне нужно отдохнуть от регулярных выражений на время).
1 И это действительно означает, что любая длина — включая ничего — .jpg
будет правильным именем файла, использующим это регулярное выражение — вероятно, разумнее использовать +
квантификатор и требовать хотя бы один символ, но мне нужно оправдание, чтобы проиллюстрировать *
.
2 На самом деле мне не нужен подшаблон для этого примера, потому что b
является утверждением — он на самом деле не становится частью соответствия, но это хорошая возможность проиллюстрировать подшаблоны, которые часто используются при поиске и замене. операции.