Добрага часу сутак.
Скончыўшы пісаць чарговы скрыпт на Bash, зразумеў, што ўсё павінна быць зусім інакш, аднак усё працавала. Жадаю вам паказаць, якія непатрэбствы і мыліцы напісаў я, каб вырашыць задачу, але пакуль не маючы вагона ведаў. Інакш кажучы, карыкатура на праграмаваньне.
Задача
Стала трэба нешта, каб:
- Выводзіла мноства рыфмаў для слова, за выключэннем квадратаў.
- Перасякала мноства рыфмаў двух слоў
Для чаго? Ну вось трэба - і ўсё тут.
Хто не ведае, квадратная рыфма (у прастамоўі - квадрат) - два словы, у якіх супадаюць дзве апошнія літары ў напісанні, што (часта, толькі гэта) і робіць іх рыфмай. Напрыклад, ружы - маразы; шына - машына. Выкарыстанне квадратаў у сучасным вершаскладанні не асабліва ўхваляецца людзьмі, з прычыны іх прымітыўнасці.
рашэнне
Самым простым рашэннем мне падалося напісаць скрыпт на Bash, які выкарыстоўвае ўжо існуючы генератар рыфмаў - HOST, які ў першую чаргу падбірае іх па сугучча, а не па напісанні. Што за HOST? Таму што калі паказаць сапраўдную назву сайта - скажуць, што рэклама. Чаму б не працягнуць карыстацца ім? Па-першае, нягледзячы на яго перавагу падбору рыфмаў па сугуччах, ён-ткі часцяком выдае квадраты. Па-другое, усё роўна даводзіцца думаць мазгамі, марнаваць час на пераключэнне паміж укладкамі, сілы на запамінанне паўтаральных слоў у спісах для знаходжання рыфмы да двух слоў.
Атрыманне моцных рыфмаў
Што я ведаю? Я ведаю пра ўтыліту Wget, Якая спампоўвае старонку па паказаным URL. Добра, выконваем запыт - атрымліваем HTML старонку ў файле, які названы словам для рыфмы. Да прыкладу, пашукаем па слове «тут»:
wget https://HOST/rifma/здесь
Але мне ж патрэбен толькі спіс слоў, як пазбавіцца ад усяго астатняга? Глядзім і бачым, што спіс слоў аформлены, як бы гэта ні было дзіўна, у выглядзе спісу, і словы знаходзяцца ў тэгах . Што ж, у нас ёсць цудоўная ўтыліта sed - так і запішам:
cat $word | grep '<li>' | sed -e "s%<li>%%" | sed -e "s%</li>%%" | sed -e "s/ //g" | sed -e "/^$/d" 1> $word
Спачатку з файла word выбіраемы радкі, у якіх утрымоўваецца тэг - атрымліваем кучу пустых тэгаў і радкі са словамі. Прыбіраны сам тэг і яго які зачыняе - тут знакі адсотка скарыстаны замест слэшаў таму, што ў самім тэгу ужо ёсць слеш, чаму 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
Гэта што, мацюк на серверным? Добра, прынамсі мне адказалі 200 OK, значыць, печыва і рэферэр ні на што не ўплываюць. Сціск gzip, але пры капіяванні капіююцца ASCII знакі. Дакладна, можна прыбраць радок Прыняць-кадаванне. Усё выдатна - атрымліваем HTML дакумент, зараз ужо з асанансамі. Але вось два пытанні: як запускаць OpenSSL і перадаваць яму дадзеныя скрыптам? І як счытваць выснову, калі пасля атрымання адказу мы застаемся як бы ў "абалонцы" OpenSSL? Калі з другім можна нешта прыдумаць, але вось з першым...
Як добра, што ёсць хабр, дзе я прачытаў пра ўтыліту чакаць, якая аўтаматызуе працэс узаемадзеяння з праграмамі, якія чакаюць узаемадзеяння з чалавекам. Яшчэ больш прывабна наяўнасць каманды autoexpect, якая генеруе чакаць скрыпт па вашых дзеяннях. Што ж, запускаем, які робіцца ўсё гэта і вось гатовы скрыпт. Толькі ўжо вельмі ён вялізны, а ўсё таму, што OpenSSL выводзіць сертыфікаты, ключы, а чакаць чакае вываду гэтага ўсяго. Ці трэба нам гэта? Не. Зносім усё першае запрашэнне, пакідаючы толькі апошні перанос радка 'r'. Таксама з нашага запыту прыбіраем палі User-Agent і Accept - ні на што не ўплываюць. Так, запускаем. Скрыпт выканаўся, але дзе запаветны HTML дакумент? Чакаць з'еў яго. Каб прымусіць яго выплюнуць, трэба пакласці:
set results $expect_out(buffer)
перад канцом скрыпту — так будзе запісаная выснова выкананай чакацьТым каманды і выведзены на экран. Па выніку, нешта накшталт гэтага:
Скрыпт 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Тым, а з дапамогай сумаваць, т.я. у нас у новым не мяняецца нічога, акрамя слова. І хай жыве новая праблема: як бы нам гэтак разумна перавесці слова з кірыліцы ў 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 у сервера старонку сайта з асанансамі. А далей перанакіроўваем выснову апошняга скрыпту ў файл і па-старому прапускаем яго праз
Скрыжаванне мностваў. Вынік
Уласна гэта менавіта тое, што выклікае найменшыя праблемы. Выконваем вышэйпаказаныя працэдуры для двух слоў, затым з двух спісаў параўноўваем кожнае слова з кожным і калі супадзенне знойдзена - выводны. Цяпер у нас ёсць скрыпт, які прымае на ўваход два словы і выводзіць спіс слоў, якія рыфмуюцца і з тым і з іншым, ды яшчэ і з улікам асанансаў, і гэта ўсё без ручнога пераключэння паміж чатырма ўкладкамі і запамінаннем слоў "на вока" - усё сабрана, улічана і выкінута аўтаматычна. Выдатна.
Мэтай дадзенай публікацыі было паказаць, што калі чалавеку нешта патрэбна, дык ён гэта зробіць у любым выпадку. Вельмі неэфектыўна, крыва, жудасна, але тое будзе працаваць.
Крыніца: habr.com