На что способен мозг студента, познающего компьютерный мир

в 15:24, , рубрики: bash-скриптинг, expect, openssl, ненормальное программирование, стихи, Учебный процесс в IT

Доброго времени суток.

Закончив писать очередной скрипт на Bash, понял, что всё должно быть совершенно иначе, однако всё работало. Хочу вам показать, какие непотребства и костыли написал я, дабы решить задачу, но пока не имея вагона знаний. Иначе говоря, каррикатура на программирование.

Задача


Стало нужно что-то, что бы:

  • Выводило множество рифм для слова, за исключением квадратов
  • Пересекало множества рифм двух слов

Для чего? Ну вот надо — и всё тут.
Кто не знает, квадратная рифма (в просторечии — квадрат) — два слова, у которых совпадают две последние буквы в написании, что (зачастую, только это) и делает их рифмой. Например, розы — морозы; шина — машина. Использование квадратов в современном стихосложении не особо одобряется людьми, ввиду их примитивности.

Решение


Самым простым решением мне показалось написать скрипт на Bash, использующий уже существующий генератор рифм — HOST, который в первую очередь подбирает их по созвучиям, а не по написанию. Что за HOST? Потому что если указать настоящее название сайта — скажут, что реклама. Почему бы не продолжить пользоваться им? Во-первых, несмотря на его преимущество подбора рифм по созвучиям, он-таки частенько выдаёт квадраты. Во-вторых, всё равно приходится думать мозгами, тратить время на переключение между вкладками, силы на запоминание повторяющихся слов в списках для нахождения рифмы к двум словам.

Получение сильных рифм

Что я знаю? Я знаю про утилиту wget, которая скачивает страницу по указанному URL. Хорошо, выполняем запрос — получаем HTML страницу в файле, который назван словом для рифмы. К примеру, поищем по слову «здесь»:

wget https://HOST/rifma/здесь

Но мне же нужен только список слов, как избавится от всего остального? Смотрим и видим, что список слов оформлен, как бы это ни было странно, в виде списка, и слова находятся в тегах &ltli&gt&lt/li&gt. Чтож, у нас есть прекрасная утилита sed — так и запишем:

cat $word | grep '<li>' | sed -e "s%<li>%%" | sed -e "s%</li>%%" | sed -e "s/ //g" | sed -e "/^$/d" 1> $word

Сначала из файла word выбираем строки, в которых содержится тег &ltli&gt — получаем кучу пустых тегов и строки со словами. Убираем сам тег и его закрывающий — здесь символы процента использованы вместо слешей потому, что в самом теге &lt/li&gt уже есть слеш, отчего sed немного вас не понимает. А с процентами всё хорошо. Убираем все пробелы из файла, удаляем пустые строки. Вуаля — готовый список слов.

Для того, чтобы убрать слова, рифмующиеся за счёт последних букв, выделим последние две буквы из исходного слова и почистим список:

squad=${word:((${#word}-2)):2}
cat $word | sed -e "/.$squad$/d" 1> $word

Смотрим, пробуем — всё работает… так, а где список для слова «играть»? А для слова «иду»? Файл пустой! А это всё потому, что эти слова — глаголы, и мы знаем, что делают с теми, кто рифмует на глаголы. Глагольная рифма хуже даже квадратной, ибо глаголов в русском языке больше всего, да ещё и все с одинаковыми окончаниями, из-за чего их и не оказалось в итоговом файле после проверки окончаний.

Однако не спешим. К каждому слову есть не только рифмы, но ещё и ассонансы, которые иногда звучат куда лучше, чем рифма — на то они и ассонансы (фр. assonance, от лат. assono — звучу в лад).

Получаем ассонансы

Здесь начинается самое интересное: ассонансы появляются по отдельному URL, а на этой же странице, путём выполнения скрипта, посылания HTTP запроса и получения ответа. Как же сказать wget'у нажать кнопочку? А вот никак. Печально.

Заметив, что URL в строке всё же как-то меняется, я скопировал то, что было там после перехода на ассонансы, и вставил в новой вкладке браузера — открылись сильные рифмы. Не то.

По сути, подумал я, для сервера должно быть всё равно, выполняется ли скрипт, отправляющий ему запрос, или же человек сам руками набирает его. Так? А кто его знает, пойдём проверять.

Куда отправлять? Что отправлять? HTTP запрос на IP сервера, там что-то вроде GET… там потом что-то HTTP/1.1… Надо посмотреть, что и куда отправляет браузер. Устанавливаем wireshark, смотрим трафик:

0040 37 5d a3 84 27 e7 fb 13 6d 93 ed cd 56 04 9d 82 7]£.'çû.m.íÍV...
0050 32 7c fb 67 46 71 dd 36 4d 42 3d f3 62 1b e0 ad 2|ûgFqÝ6MB=ób.à.
0060 ef 87 be 05 6a f9 e1 01 41 fc 25 5b c0 77 d3 94 ï.¾.jùá.Aü%[ÀwÓ.

Эм… что? Ах да, у нас же HTTPS. Что делать? Устроить MITM атаку на себя? Идеально, жертва сама будет нам помогать.

В общем, догадавшись полазить по браузеру, я-таки нашёл сам запрос, и адресата. Поехали:

Диалог с терминалом

telnet IP PORT
Trying IP...
Connected to IP.
Escape character is '^]'.
GET /rifma/%D0%BC%D0%B0%D1%82%D1%8C?mode=block&type=asn HTTP/1.1
Host: HOST
Accept-Language: en-US,en;q=0.5
X-Requested-With: XMLHttpRequest
Connection: close

HTTP/1.1 400 Bad Request
Server: nginx/1.8.0
Date: Sun, 03 Nov 2019 20:06:59 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 270
Connection: close

<html>
<head><title>400 The plain HTTP request was sent to HTTPS port</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<center>The plain HTTP request was sent to HTTPS port</center>
<hr><center>nginx/1.8.0</center>
</body>
</html>
Connection closed by foreign host.

Хы. Хы-хы. Действительно, что я ожидал, отправляя голый HTTP запрос на HTTPS порт. Разве шифровать теперь? Вся эта возня с RSA ключами, потом еще с SHA256. А зачем, есть же OpenSSL для таких дел. Чтож, уже знаем, что делать, только предварительно уберём поля Referer и Cookie — думаю, они не сильно повлияют на дело:

Диалог с терминалом

openssl s_client -connect IP:PORT
{Всякие ключи, сертификаты}
GET /rifma/%D0%B7%D0%B4%D0%B5%D1%81%D1%8C?mode=block&type=asn HTTP/1.1
Host: HOST
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:70.0) Gecko/20100101 Firefox/70.0
Accept: text/javascript,text/html,application/xml,text/xml,*/*
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
X-Requested-With: XMLHttpRequest
Connection: keep-alive

HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
Status: 200 OK
Date: Sun, 03 Nov 2019 20:34:33 GMT
Set-Cookie: COOKIE
X-Powered-By: Phusion Passenger 5.0.16
Server: nginx/1.8.0 + Phusion Passenger 5.0.16
Expires: Thu, 01 Jan 1970 00:00:01 GMT
Cache-Control: no-cache
Strict-Transport-Security: max-age=31536000
Content-Security-Policy: block-all-mixed-content
Content-Encoding: gzip

На что способен мозг студента, познающего компьютерный мир - 1

Это что, мат на серверном? Хорошо, по крайней мере мне ответили 200 OK, значит, куки и реферер ни на что не влияют. Сжатие gzip, но при копировании копируются ASCII символы. Точно, можно убрать строку Accept-encoding. Всё прекрасно — получаем HTML документ, теперь уже с ассонансами. Но вот два вопроса: как запускать OpenSSL и передавать ему данные скриптом? И как считывать вывод, если после получения ответа мы остаемся как бы в «оболочке» OpenSSL? Если со вторым можно что-то придумать, но вот с первым…

Как хорошо, что есть Хабр, где я прочитал про утилиту expect, которая автоматизирует процесс взаимодействия с программами, ожидающими взаимодействия с человеком. Ещё более привлекательно наличие команды autoexpect, генерирующей expect скрипт по вашим действиям. Чтож, запускаем, делаем всё это и вот готовый скрипт. Только уж очень он огромный, а всё потому, что OpenSSL выводит сертификаты, ключи, а expect ожидает вывода этого всего. Надо ли нам это? Нет. Сносим всё первое приглашение, оставляя только последний перенос строки 'r'. Также из нашего запроса убираем поля User-Agent и Accept — ни на что не влияют. Так, запускаем. Скрипт выполнился, но где заветный HTML документ? Expect съел его. Дабы заставить его выплюнуть, нужно положить:

set results $expect_out(buffer)

перед концом скрипта — так будет записан вывод исполняемой expect'ом команды и выведен на экран. По итогу, что-то вроде этого:

Скрипт expect'a

#!/usr/bin/expect -f

set timeout -1
spawn openssl s_client -connect IP:PORT
match_max 100000
expect -exact "
---r
"
send -- "GET /rifma/%d0%b7%d0%b4%d0%b5%d1%81%d1%8c?mode=block&type=asn HTTP/1.1rHost: HOSTrAccept-Language: en-US,en;q=0.5rX-Requested-With: XMLHttpRequestrConnection: close"
expect -exact "GET /rifma/%d0%b7%d0%b4%d0%b5%d1%81%d1%8c?mode=block&type=asn HTTP/1.1r
Host: HOSTr
Accept-Language: en-US,en;q=0.5r
X-Requested-With: XMLHttpRequestr
Connection: close"
send -- "r"
set results $expect_out(buffer)
expect -exact "r
"
send -- "r"
expect eof

Но и это ещё не всё! Как можно было заметить, во всех примерах URL запроса был статичным, однако именно он отвечает за то, к какому слову будут выведены ассонансы. А так получается, что мы постоянно будем искать по слову "%d0%b7%d0%b4%d0%b5%d1%81%d1%8c" в ASCII или «здесь» в UTF-8. Что делать? Конечно же просто напросто каждый раз генерировать новый скрипт, друзья! Только уже не autoexpect'ом, а с помощью echo, т.к. у нас в новом не меняется ничего, кроме слова. И да здравствует новая проблема: как бы нам этак по-умному перевести слово с кириллицы в URL формат? Что-то и для терминала-то особо нет ничего. Ну ничего, мы же можем? Можем:

Смотри, что могу!

function furl {
furl=$(echo "$word" | sed 's:А:%d0%90:g;s:Б:%d0%91:g;s:В:%d0%92:g;s:Г:%d0%93:g;s:Д:%d0%94:g;s:Е:%d0%95:g;s:Ж:%d0%96:g;s:З:%d0%97:g;s:И:%d0%98:g;s:Й:%d0%99:g;s:К:%d0%9a:g;s:Л:%d0%9b:g;s:М:%d0%9c:g;s:Н:%d0%9d:g;s:О:%d0%9e:g;s:П:%d0%9f:g;s:Р:%d0%a0:g;s:С:%d0%a1:g;s:Т:%d0%a2:g;s:У:%d0%a3:g;s:Ф:%d0%a4:g;s:Х:%d0%a5:g;s:Ц:%d0%a6:g;s:Ч:%d0%a7:g;s:Ш:%d0%a8:g;s:Щ:%d0%a9:g;s:Ъ:%d0%aa:g;s:Ы:%d0%ab:g;s:Ь:%d0%ac:g;s:Э:%d0%ad:g;s:Ю:%d0%ae:g;s:Я:%d0%af:g;s:а:%d0%b0:g;s:б:%d0%b1:g;s:в:%d0%b2:g;s:г:%d0%b3:g;s:д:%d0%b4:g;s:е:%d0%b5:g;s:ж:%d0%b6:g;s:з:%d0%b7:g;s:и:%d0%b8:g;s:й:%d0%b9:g;s:к:%d0%ba:g;s:л:%d0%bb:g;s:м:%d0%bc:g;s:н:%d0%bd:g;s:о:%d0%be:g;s:п:%d0%bf:g;s:р:%d1%80:g;s:с:%d1%81:g;s:т:%d1%82:g;s:у:%d1%83:g;s:ф:%d1%84:g;s:х:%d1%85:g;s:ц:%d1%86:g;s:ч:%d1%87:g;s:ш:%d1%88:g;s:щ:%d1%89:g;s:ъ:%d1%8a:g;s:ы:%d1%8b:g;s:ь:%d1%8c:g;s:э:%d1%8d:g;s:ю:%d1%8e:g;s:я:%d1%8f:g;s:ё:%d1%91:g;s:Ё:%d0%81:g')}

Итого имеем скрипт, преобразующий слово в ASCII текст, генерирующий уже другой скрипт, который запрашивает через OpenSSL у сервера страничку сайта с ассонансами. А дальше перенаправляем вывод последнего скрипта в файл и по-старинке пропускаем его через «фильтры» лишнего, квадратов и дозаписываем в файл.

Пересечение множеств. Итог

Собственно это именно то, что вызывает наименьшие проблемы. Выполняем вышеуказанные процедуры для двух слов, затем из двух списков сравниваем каждое слово с каждым и если совпадение найдено — выводим. Теперь у нас есть скрипт, который принимает на вход два слова и выводит список слов, рифмующихся и с тем и с другим, да ещё и с учётом ассонансов, и это все без ручного переключения между четырьмя вкладками и запоминанием слов «на глаз» — всё собрано, учтено и выброшено автоматически. Прекрасно.

Целью данной публикации было показать, что если человеку что-то нужно, то он это сделает в любом случае. Очень неэффективно, криво, жутко, но то будет работать.

Автор: CVEG21

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js