Форма на сайті, обробник на сервері, приватний файл із відповідями і одне число на лендингу.
Досі ти тільки розповідав про продукт: бриф, пітч, дек, лендинг. Сьогодні все навпаки. Продукт починає приймати відповіді від людей.
На твоєму сервері зʼявиться файл. У ньому лежатимуть імена й слова живих людей. Тримати цей файл у безпеці — тепер твоя робота.
За 120 хвилин ти збереш ланцюг із чотирьох ланок. Перша — анкета:
самі питання, які ти справді поставиш людям. Друга — форма
feedback.html: сторінка з цими питаннями й кнопкою «Надіслати».
Третя ланка — обробник ~/bin/form-handler. Це маленька
програма на сервері. Вона приймає відповіді, перевіряє їх і дописує один рядок
у файл.
Четверта ланка — сам файл ~/data/responses.csv. Саме тут
проходить головна межа уроку. Обробник і файл лежать поза текою
~/www/, тому завантажити їх з інтернету не вийде.
<label> ти вже знаєш.
Різницю між GET і POST теж. І те, що required у браузері —
зручність для людини, а не захист від зловмисника.
Сьогодні до цього додається три речі. Як скласти питання, відповіді
на які тобі справді знадобляться. Як програма на сервері безпечно
записує чужий текст у файл. І як із того файлу виходить одне число
для лендинга.feedback.html, яка відкривається
за твоєю адресою;curl;| Етап | Хв | Що робимо |
|---|---|---|
| Розігрів: погана анкета | 10 | Шукаємо навідні й подвійні питання в 6 прикладах |
| Типи питань і валідація | 15 | Шкала / множинний вибір / відкрите — проєктуємо 7 питань своєї анкети |
| Етика й приватність | 15 | Персональні дані, згода, анонімність — викреслюємо зайві поля |
| Google Forms і збір | 20 | Публікуємо форму, збираємо ≥10 відповідей одне в одного |
| Потік даних | 15 | Конвеєр «Форма → Обробник → CSV», читаємо сирий POST |
feedback.html | 20 | Верстаємо форму на токенах бренду з <label> до кожного поля |
| Практика: обробник | 15 | Дописуємо валідацію й дедуплікацію в bin/form-handler |
| Автоперевірка й підсумок | 10 | check11 7 (частина) з маркером __autocheck__ |
| Разом | 120 |
Слова CGI, агрегат і CSV-injection поки нічого не означають — розберемо кожне тоді, коли воно знадобиться.
| Файл | Де лежить | Хто бачить |
|---|---|---|
feedback.html | ~/www/ | усі в інтернеті — так і задумано |
form-handler | ~/bin/ | ніхто; сервер його запускає, але не віддає |
responses.csv | ~/data/ | тільки ти й учитель |
feedback-stats.json | ~/www/data/ | усі — але там лише кількість і середнє |
Однокласник каже: «Навіщо морочитися з ~/bin/ і ~/data/?
Покладу обробник і CSV поруч із feedback.html, воно ж однаково
працює». Що саме зламається?
Анкета — це не список того, що тобі цікаво. Це бюджет чужої уваги.
Компанія SurveyMonkey виміряла час відповіді на понад 100 000 опитувань. Закономірність вийшла проста: що далі питання від початку, то менше секунд людина на нього витрачає. Не тому, що питання простіші, — тому, що вона втомилася.
| Позиція питання | Секунд на питання | Що це означає |
|---|---|---|
| 1-ше | 75 | читає уважно, вникає в контекст |
| 2-ге | 40 | уже зорієнтувався |
| 3–10-те | 30 | робочий темп |
| 11–15-те | 25 | починає поспішати |
| 16–25-те | 21 | клікає, майже не читаючи |
| 26–30-те | 19 | відповіді вже не варті довіри |
Порахуй: анкета з 10 питань — це приблизно 5 хвилин. За даними того самого дослідження, після 7–8 хвилин заповнення частка тих, хто дійшов до кінця, падає на 5–20 %. І падає нерівномірно: першими зникають ті, кому не дуже цікаво, — тобто саме ті, чия думка тобі найпотрібніша.
Для відгуку про продукт робоча межа — 5–7 питань. За таблицею вище це приблизно чотири хвилини. Це вміщає одну оцінку, один вибір, одне відкрите питання і мінімум контексту. Усе інше — наступна анкета, після того як ти щось зміниш у продукті.
Учениця зробила анкету на 22 питання. Прийшло 40 людей, дійшли до кінця 12, середня оцінка продукту — 4,8 з 5. Вона пише в пітч: «Продукт подобається користувачам, середня оцінка 4,8». Що з цим висновком не так?
Форма питання змінює відповідь сильніше, ніж сам продукт.
Закрите питання дає перелік варіантів. Його легко порахувати, легко порівняти між людьми — і воно нічого не розповість поза списком, який ти сам склав. Відкрите дає текст своїми словами: там живуть причини, назви й формулювання, до яких ти б не додумався.
Pew Research Center поставив одне й те саме питання двома способами у передвиборчому опитуванні 2008 року. Коли «економіку» запропонували у списку, її обрали 58 %. Коли те саме питання лишили відкритим, економіку самі назвали 35 %. Питання одне, різниця — 23 пункти, і вся вона в формі.
У відкритих питань є ціна: на них частіше не відповідають зовсім. У панелі Pew частка пропусків для відкритих питань коливається від 3 % до понад 50 % залежно від формулювання. Тому в анкеті на 5–7 пунктів відкрите питання лишай одне. І хай це буде головне питання, а не «щось іще?» наприкінці.
«Вам сподобалось?» — найпопулярніше і найбезкорисніше питання світу. Воно дає «так» від 90 % людей, бо відповідати «ні» незручно, і не дає жодної підказки, що робити далі. Порівняй його з питанням «що вас зупинило?»:
Відповідь: «так», «норм», «класно».
Що робити завтра: невідомо.
Соціальний тиск: людині незручно сказати «ні» тому, хто стоїть поруч.
Відповідь: «не зрозумів, як почати», «дорого», «немає української мови».
Що робити завтра: перший екран, ціна, локалізація.
Питання дає дозвіл на критику — і тому її отримує.
Різниця не в тоні, а в напрямку. Перше питання просить оцінити тебе, друге — описати перешкоду. Перешкода — це те, що ти можеш прибрати, оцінка — ні.
Навідне питання підказує «правильну» реакцію інтонацією або словом. Ось класичний приклад із опитувань центру Pew. Одне й те саме рішення назвали двома способами.
Перше формулювання: «зробити законним для лікарів надавати невиліковно хворим засіб завершити життя». Його підтримали 51 %. Друге: «допомога в самогубстві». Підтримали 44 %. Змінилося одне слово.
| Дефект | Як звучить у твоїй анкеті | Як переписати |
|---|---|---|
| Навідне | «Погодьтеся, наш зручний сервіс економить час?» | «Скільки часу ви витратили, щоб зробити перше замовлення?» |
| Подвійне | «Чи зручний і швидкий застосунок?» | Два окремі питання: про зручність і про швидкість |
| Порожнє | «Вам сподобалось?» | «Що вас зупинило?» |
| «Погоджуюсь / не погоджуюсь» | «Продукт корисний: так чи ні?» | Вибір між двома твердженнями: «радше зекономив час» / «радше додав роботи» |
| Про майбутнє | «Ви б платили 200 грн на місяць?» | «Скільки ви платите зараз за те, чим користуєтесь замість нас?» |
Останній рядок найважчий. Люди щиро й помилково передбачають власну майбутню поведінку: «звісно, платив би» коштує нуль гривень. Питання про минуле («що ви робили», «скільки платите») дає перевірюваний факт.
В анкеті стоїть питання «Чи погоджуєтесь ви, що наш застосунок зручніший за паперовий щоденник?». 84 % відповіли «так». Учень несе це число в пітч як доказ переваги над конкурентом. Де помилка?
Ти вперше збираєш відгуки й не знаєш, яких функцій людям бракує. Яке питання поставити в цьому першому циклі?
Не тому, що «пʼять — класика». Тому, що в тебе буде 15 відповідей, а не 15 тисяч.
Класичне дослідження Престона й Колмана (2000) дало 149 людям оцінити ті самі речі. Шкали брали різні: від 2 поділок до 11.
Результат такий. Шкали на 2–4 поділки працюють погано. Далі якість оцінок швидко росте приблизно до 7 поділок. А після сімки майже не змінюється: між 7, 8, 9 і 10 різниці практично немає. Найбільше самим людям сподобалася 10-бальна.
У розмітці це одне поле: <input type="number" name="rating"
min="1" max="5" step="1" required>. Автоперевірка дивиться саме
на min і max. Якщо тут стоїть текстове поле
замість числового, система поки не прийме роботу — заміни тип поля
й запусти перевірку ще раз. Спроби не обмежені.
Учень зібрав 14 відгуків за 10-бальною шкалою, середня — 7,4. Наступного тижня додав 2 відгуки по 9, середня стала 7,6. Він пише в звіті: «Після оновлення інтерфейсу задоволеність зросла». Що тут не так насамперед?
Не «що ще спитати», а «без чого саме я не зроблю висновок».
Правило називається мінімізацією даних, і воно записане в законі. У GDPR, стаття 5(1)(c): дані мають бути «адекватними, релевантними й обмеженими тим, що необхідно для цілей обробки». В українському законі «Про захист персональних даних», стаття 6: склад і зміст даних мають бути «не надмірними стосовно визначеної мети їх обробки».
Формулювання юридичне, застосування просте. Для кожного поля відповідай на одне питання: яке рішення про продукт я не зможу ухвалити без нього? Якщо відповіді немає — поля немає.
| Поле | Яке рішення дає | Вердикт |
|---|---|---|
| Оцінка 1–5 | чи стало краще після зміни | потрібне |
| Якої функції бракує | що робити наступним | потрібне |
| Що зупинило | що прибрати з дороги | потрібне |
| Імʼя або нік | подякувати, побачити повтори | потрібне, але не обовʼязкове |
| Прізвище | жодного | зайве |
| Клас | жодного — сегментів у тебе немає | зайве |
| Телефон, месенджер | жодного | зайве й небезпечне |
| Вік, дата народження | жодного на цьому етапі | зайве |
Прізвище й клас разом дають конкретну людину в конкретній школі. Кожне поле окремо здається невинним — а разом вони складаються. Файл із думками перетворюється на файл про людей, і статус усього файлу міняється назавжди.
Різниця відчутна на простому порівнянні. Пароль після витоку міняють за хвилину. Номер телефону — місяцями. А прізвище не міняють узагалі.
Тека ~/www/ — це буквально те, що вебсервер віддає в інтернет.
Усе, що туди потрапило, доступне за прямою адресою будь-кому, хто її вгадає,
і може потрапити в пошуковий індекс. Ти вже клав туди market.csv —
і правильно: там знеособлені числа про ринок.
Відповіді людей туди класти не можна. Тому сьогоднішній ланцюг перетинає межу рівно двічі — і обидва рази свідомо.
Перший перетин — униз. Людина натиснула «Надіслати», і сервер запускає
~/bin/form-handler. Відповідь іде з публічної адреси
/submit у приватну теку й лягає рядком у
~/data/responses.csv.
Другий перетин — угору. Скрипт читає той самий файл і виносить із нього
два числа: скільки відгуків усього і яка середня оцінка. Тільки ці два числа
потрапляють у ~/www/data/feedback-stats.json і на лендинг.
Більше межу не перетинає ніщо. Ані імʼя, ані коментар, ані оцінка окремої людини нагору не піднімаються.
Три запити, три різні відповіді. Виконай їх після того, як усе залив на сервер, — це і є доказ, що межа стоїть.
curl — це псевдонім
Invoke-WebRequest, і ключі -s, -o,
-w, -X, -d там не працюють.
Пиши curl.exe — тоді запуститься справжній curl, який є
у Windows 10 і 11.Чому 404, а не 403? Бо для сервера цих шляхів просто не існує.
Веб-корінь — це ~/www/. Усе, що поза ним, не має адреси взагалі.
403 ти побачив би в іншому випадку: файл лежить усередині веб-кореня, а сервер відмовляє в доступі. Ця ситуація гірша. Файл там, і одна помилка в налаштуваннях робить його публічним.
Однокласниця пропонує додати в анкету поле «клас» — «щоб потім побачити, кому продукт заходить більше». Відповідей планується 15. Що відповісти?
Учень скопіював responses.csv у ~/www/data/,
«щоб зручніше дивитися з телефона», і перейменував на d2.txt.
Що змінилося?
feedback.htmlСторінка не вгадує змісту твого продукту. Вона ловить дефекти формулювань, рахує час і збирає розмітку.
Стартовий набір уже лежить у конструкторі — це мінімальна робоча анкета відгуку. Твоє завдання: замінити питання на свої, додати одне-два під свій продукт і втримати сім полів. Конструктор перевіряє кожне питання за пʼятьма ознаками й показує, що з ним не так.
| Що ловить | За чим упізнає | Чому це важливо |
|---|---|---|
| Навідне | «погодьтеся», «чи не правда», «зручний», «чудовий», «наш кращий» | відповідь уже вписана в питання |
| Подвійне | два прикметники через «і» / «та» в одному питанні | незрозуміло, що саме оцінили |
| Порожнє | «сподобал», «як вам», «оцініть нас» | дає «так» і жодного рішення |
| Про майбутнє | «чи будете», «чи платили б», «порекомендуєте» | люди помиляються про себе завтрашніх |
| Персональне поле | прізвище, телефон, клас, адреса, вік, пошта | міняє статус усього файлу |
Швидко додати для проби:
Згенерований файл — це чернетка, а не готова робота. Підключи до нього
brand.css. Впиши під формою свій текст про те, як ти використаєш
дані. І перевір, що action указує на адресу твого обробника.
Конструктор позначив питання «Чи будете ви рекомендувати наш зручний сервіс друзям?» одразу трьома мітками. Якими — і яка з них найгірша для дослідження?
Браузер підказує людині. Сервер вирішує. Це різні задачі, і другу за тебе ніхто не зробить.
required, min, max і
type="email" у формі зроблені для зручності. Людина бачить
помилку одразу, не чекаючи відповіді сервера.
Але POST-запит можна зібрати й руками, без твоєї сторінки. Досить однієї
команди curl — і в обробник прилітає порожнє імʼя й оцінка
«сто».
Файл лежить у ~/bin/, поза веб-корінням. Учитель один раз
прописав у конфігурації сервера, що адреса /submit означає
«запусти цей скрипт». Тому адреса є, а файлу за нею немає: завантажити
form-handler не можна, запустити — можна.
Тепер про те, як сервер передає скриптові дані з форми. Домовленість тут дуже стара й дуже проста, зветься вона CGI. Сервер не викликає жодних функцій — він просто запускає твій файл, як ти запускаєш будь-яку команду.
А потрібне скрипт бере з трьох місць. Хто прийшов і яким методом —
зі змінних оточення REQUEST_METHOD і CONTENT_LENGTH.
Самі відповіді з форми — зі стандартного вводу, того самого, куди ти
зазвичай друкуєш із клавіатури. А те, що скрипт друкує на екран, сервер
віддає браузерові як відповідь.
Усе інше в обробнику — звичайний bash, який ти вже бачив.
CSV виглядає простим, поки в коментарі немає коми. Стандарт RFC 4180 описує рівно три правила, і всі три має виконувати той, хто пише файл.
| У відповіді людини | Що робимо | Як лягає у файл |
|---|---|---|
дорого, але зручно | кома всередині — беремо в лапки | "дорого, але зручно" |
сказав "супер" | кожну лапку подвоюємо | "сказав ""супер""" |
| текст із переносом рядка | переноси міняємо на пробіл | один запис = один рядок |
report.sh наступного уроку читатиме файл
порядково, як і wc -l, і tail. Один коментар
із двома абзацами перетворить 12 відгуків на 14 рядків. Прибрати переноси
дешевше, ніж лагодити кожен інструмент.Після успішного POST обробник не малює сторінку «дякую». Він відповідає 303 See Other і дає адресу, на яку піти. Браузер робить туди звичайний GET.
Такий обхід зветься POST → Redirect → GET. Потрібен він з однієї причини. Після редиректа кнопка «оновити» повторює вже GET, а не POST. Без нього кожне натискання F5 додавало б ще один відгук.
| Код | Коли | Що бачить людина |
|---|---|---|
| 303 | відгук записано | сторінку подяки; оновлення нічого не додає |
| 303 | дубль за 10 хвилин | ту саму подяку — про дубль їй знати не треба |
| 400 | порожній коментар, оцінка не 1–5 | текст помилки й що виправити |
| 405 | прийшов GET замість POST | нічого корисного — це не людина, а бот |
Читай його зверху вниз: кожен блок — одна відповідальність. Це той самий файл, який ти доробиш на практиці.
#!/bin/bash # обробник форми відгуків. Запускається вебсервером за адресою /submit. set -u CSV="$HOME/data/responses.csv" SEEN="$HOME/data/.seen" MAX=500 # --- відповіді ------------------------------------------------ fail(){ printf 'Status: 400 Bad Request\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n%s\n' "$1"; exit 0; } deny(){ printf 'Status: 405 Method Not Allowed\r\nAllow: POST\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n%s\n' "$1"; exit 0; } back(){ printf 'Status: 303 See Other\r\nLocation: /feedback.html?ok=%s\r\n\r\n' "$1"; exit 0; } # --- 1. метод і розмір ---------------------------------------- [ "${REQUEST_METHOD:-}" = POST ] || deny 'приймаю тільки POST' len=${CONTENT_LENGTH:-0} [ "$len" -gt 0 ] && [ "$len" -le 8192 ] || fail 'порожнє або завелике тіло' body=$(head -c "$len") # --- 2. розкодування %XX і «+» -------------------------------- urldecode(){ local s=${1//+/ } s=${s//\\/\\\\} # інакше printf %b зʼїсть зворотну скісну printf '%b' "${s//%/\\x}" } field(){ local key=$1 pair pairs IFS='&' read -ra pairs <<< "$body" for pair in "${pairs[@]}"; do [ "${pair%%=*}" = "$key" ] && { urldecode "${pair#*=}"; return; } done } # --- 3. валідація: усе, що прийшло, — заявка, а не факт -------- name=$(field name); rating=$(field rating) feature=$(field feature); comment=$(field comment) [ -n "$comment" ] || fail 'коментар порожній' case "$rating" in 1|2|3|4|5) ;; *) fail 'оцінка має бути числом 1–5';; esac [ -n "$name" ] || name='анонім' # --- 4. екранування одного значення для CSV ------------------- csv(){ local v=$1 v=${v//$'\r'/ }; v=${v//$'\n'/ }; v=${v//$'\t'/ } # один запис = один рядок v=${v:0:$MAX} # межа довжини case "$v" in [=+@-]*) v="'$v" ;; esac # гасимо формулу Excel v=${v//\"/\"\"} # RFC 4180: лапка подвоюється printf '"%s"' "$v" } # --- 5. дедуплікація: той самий відгук за 10 хвилин ------------ now=$(date +%s) fp=$(printf '%s|%s|%s|%s' "$name" "$rating" "$feature" "$comment" | sha256sum | cut -c1-16) touch "$SEEN" awk -v t=$((now-600)) '$1 > t' "$SEEN" > "$SEEN.tmp" && mv "$SEEN.tmp" "$SEEN" grep -q " $fp\$" "$SEEN" && back dup printf '%s %s\n' "$now" "$fp" >> "$SEEN" # --- 6. запис: заголовок один раз, рядок — під замком ---------- ts=$(date -u +%Y-%m-%dT%H:%M:%SZ) [ -s "$CSV" ] || echo 'ts,name,rating,feature,comment' > "$CSV" { flock 9 printf '%s,%s,%s,%s,%s\n' "$(csv "$ts")" "$(csv "$name")" \ "$(csv "$rating")" "$(csv "$feature")" "$(csv "$comment")" >> "$CSV" } 9>> "$CSV" chmod 600 "$CSV" back 1
flock
Два відгуки можуть прийти в одну секунду. Без замка два процеси пишуть
у файл одночасно, і рядки перемішуються посеред слова. Замок коштує
мілісекунду і рятує файл.Обробник пише значення у файл «як є», через
echo "$name,$rating,$comment". Усе працює тижнями, аж поки
хтось не написав у коментарі дорого, але зручно.
Що тепер у файлі?
Учень зробив форму без action на обробник: сторінка
показує «Дякуємо!» скриптом на самій сторінці, нікуди нічого не надсилаючи.
Тестує — усе гарно. Що він побачить у responses.csv?
Файл лежить смирно. Небезпечним його робить програма, у якій його відкриють.
CSV — це просто текст. Але майже ніхто не читає його як текст. Учитель, інвестор і ти сам двічі клацнете по файлу. Він відкриється в Excel, LibreOffice Calc або Google Sheets.
У цю мить правила міняються. Для таблиці клітинка, що починається
зі знака =, — уже не текст, а формула. І програма її
виконує.
OWASP описує це як окремий клас уразливості — CSV Injection, вона ж
Formula Injection. Формулу вмикають не тільки =:
| Символ на початку клітинки | Приклад | Що зробить таблиця |
|---|---|---|
= | =1+1 | покаже 2 |
+ | +1+1 | покаже 2 |
- | -1+1 | покаже 0 |
@ | @SUM(1,1) | покаже 2 |
| табуляція, CR, LF | невидимі | те саме, але ти цього не бачиш |
Показати «2» замість «=1+1» — нешкідливо. Небезпечно те, що поруч із арифметикою в тій самій клітинці працюють функції, які виходять назовні. Три сходинки, від нудної до серйозної:
=1+1 перетворюється на 2. Відгук зник,
підрахунок зіпсовано, і ніхто не помітив.
=HYPERLINK("http://...","Оновіть доступ") малює в клітинці
звичайне синє посилання. Клацнув — і ти вже на чужому сайті.
Є функції, які самі звертаються на чужий сервер. У таку адресу можна підставити вміст сусідніх клітинок — тобто чужі відгуки.
HYPERLINK нічого не питають. По-третє, OWASP окремо зазначає:
універсальної безпечної схеми немає, поведінка залежить від програми
й версії. Тому єдина надійна дія — не пустити символ у файл.Впиши в поле «коментар» щось, що починається з =, +,
- або @. Обидві панелі беруть поля в лапки
за стандартом RFC 4180.
Тобто ліворуч не найгірший обробник світу. Це звичайний акуратний обробник, який просто не чув про CSV-injection. Різниця між панелями — рівно один апостроф. Таблиця праворуч рахує по-справжньому: арифметику обчислює, посилання розпізнає.
Готові приклади з переліку OWASP:
| name | comment |
|---|---|
| name | comment |
|---|---|
csv(){
local v=$1
v=${v//$'\r'/ }; v=${v//$'\n'/ }; v=${v//$'\t'/ }
v=${v:0:500}
case "$v" in [=+@-]*) v="'$v" ;; esac # 1. гасимо формулу
v=${v//\"/\"\"} # 2. подвоюємо лапки
printf '"%s"' "$v" # 3. беремо все в лапки
}
'=1+1. Таблиця покаже
=1+1, без апострофа.
Є й інший шлях: зберігати значення як є, а екранувати вже під час експорту.
Він гнучкіший. Але тоді небезпечний файл лежить на диску, і досить один
раз відкрити не той.
Ми обираємо безпеку файлу, а не його красу. І памʼятаємо: на уроці 12
скрипт звіту має цей апостроф зрізати.Учасник опитування написав у полі «коментар» рядок =1+1.
Учитель відкрив responses.csv в Excel і побачив у клітинці
«2». Що це означає — і який варіант того самого був би значно гіршим?
Учень вирішив захиститися інакше: у формі поставив
pattern="[^=+@-].*", щоб браузер не дав почати коментар
з небезпечного символу. Обробник лишив без змін. Наскільки він захищений?
Майже завжди не зі зла й навіть не свідомо.
Уяви: людина натиснула «Надіслати», сторінка думає півсекунди, нічого не змінилося. Що вона зробить? Натисне ще раз. Це не рідкісний випадок, а звичайна поведінка — і твій файл отримає два однакові рядки.
| Звідки береться дубль | Що бачить людина | Що лікує |
|---|---|---|
| Подвійний клік по кнопці | нічого; вона просто нетерпляча | вимкнути кнопку після першого кліку |
| F5 після відповіді | «надіслати форму повторно?» | відповідь 303: оновлюється вже GET |
| Кнопка «назад» і ще раз «Надіслати» | здається, що не спрацювало | відбиток вмісту на сервері |
| Поганий мобільний інтернет, повтор запиту | крутилка й нічого | відбиток вмісту на сервері |
| Людина справді передумала й пише інше | усе гаразд | нічого — це не дубль |
Останній рядок — ключ до правильного правила. Дубль визначають не за людиною, а за вмістом. Якщо Оксана надіслала два різні коментарі — це дві думки, обидві цінні. Якщо той самий текст із тією самою оцінкою прилетів двічі за десять секунд — це один клік, порахований двічі.
Спершу обробник склеює чотири відповіді в один рядок:
Оксана|5|експорт|дорого. Далі йому потрібна коротка позначка,
за якою такий самий рядок упізнається вдруге.
Позначку роблять так. Беруть увесь текст і рахують із нього одне довге число. Змінилася хоч одна літера — число виходить зовсім інше. Це схоже на відбиток пальця: сам палець із відбитка не відновиш, зате два відбитки легко порівняти.
Спосіб рахувати таке число зветься sha256, а рахує його на сервері
команда sha256sum. Повний результат — 64 символи. Обробник
бере від нього перші 16 і саме їх зберігає.
Чому вистачає 16? Кожен символ — одна з 16 позначок: цифри від
0 до 9 і літери від a до
f. Шістнадцять таких символів дають 16 у шістнадцятому степені
різних відбитків. У цьому числі двадцять цифр. Твої 15 відгуків на такому
просторі не зіткнуться.
Відбитки останніх десяти хвилин лежать у службовому файлі
~/data/.seen. Старші рядки викидаються при кожному запуску,
тож файл не росте.
Поки обробник був недописаний, дублі туди потрапили. Знайти їх можна однією командою: беремо все, крім першої колонки з часом, і шукаємо повтори.
Команда cut -d, ріже по комі й не розуміє лапок,
тому на коментарі з комою вона помиляється. Для швидкого погляду цього
достатньо; для звіту на уроці 12 файл читатимуть справжнім парсером CSV.
У файлі 20 рядків, але різних імен лише 12. Учень збирається видалити все, де імʼя повторюється, щоб лишилося «по одному відгуку на людину». Що він втратить?
Лендинг має право знати, скільки людей відповіли. Він не має права знати, хто й що написав.
Ти зібрав 12 відгуків. На лендингу хочеться показати живе, а не вигадане: «12 відгуків, середня оцінка 4,3». Це агрегат — число, пораховане з багатьох рядків, за яким не відновити жодного окремого.
Різницю добре видно на прикладі. Ліворуч — те, що не можна публікувати ніколи, навіть «на хвилинку, щоб перевірити». Праворуч — те, заради чого все й робилося.
Читати CSV із лапками вручну не варто. У мові Python модуль csv
уже вміє все, що описує RFC 4180. Скрипт бере приватний файл і віддає публічний
JSON. Жодного тексту відгуків він не переносить.
#!/usr/bin/env python3 # приватне -> публічне: сюди заходять відгуки, звідси виходять два числа import csv, json, datetime, pathlib home = pathlib.Path.home() src = home / 'data' / 'responses.csv' # приватно dst = home / 'www' / 'data' / 'feedback-stats.json' # публічно rows = list(csv.DictReader(src.open(encoding='utf-8', newline=''))) marks = [int(r['rating']) for r in rows if r['rating'].isdigit()] out = { 'count': len(rows), 'avg': round(sum(marks) / len(marks), 2) if marks else None, 'updated': datetime.date.today().isoformat() } dst.write_text(json.dumps(out, ensure_ascii=False), encoding='utf-8') print(out)
На лендингу це три рядки: fetch('/data/feedback-stats.json'),
підстановка count і avg у текст. На уроці 12 ту саму
ідею розгорнемо у повний звіт, який оновлює себе за розкладом cron.
tasks.json і roadmap.html.Учень хоче зробити лендинг переконливішим і виводить на нього три останні коментарі з іменами — «це ж просто відгуки, вони не таємні». Що не так?
Виконуй по черзі. Галочки зберігаються — сторінку можна закрити.
feedback.html і form-handler доводиш у редакторі
в себе на компʼютері.
По FTP: feedback.html → ~/www/,
form-handler → ~/upload/, а звідти по SSH
переносиш у ~/bin/ і ставиш біт виконання.
responses.csv не заливаєш ніколи — його створює сам обробник..sh, тому заливай скрипт як
form-handler.sh, а вже на сервері перейменуй у
form-handler. Розширення в Linux нічого не вирішує — програму
обирає перший рядок файлу, #!/bin/bash.Тестовий рядок після перевірки прибери:
grep -v '"Тест"' ~/data/responses.csv > /tmp/r && mv /tmp/r ~/data/responses.csv.
У Windows PowerShell 5.1 пиши curl.exe, а не curl.
check11 7, перша частинаСистема зробить справжній POST на твою адресу, подивиться, що зʼявилося у файлі, і прибере за собою. Спроби не обмежені.
Перевірка check11 7 одна на два уроки. Сьогодні вона дивиться
на форму, обробник і приватний файл — це перша частина. На уроці 12 та сама
команда прожене другу частину: звіт report.html і розклад
запуску. Імʼя команди там таке саме, тому не лякайся, побачивши його вдруге.
Демонстраційний режим: результат згенеровано для показу.
На сервері ця кнопка запускає check11 7 від імені учня
і читає ~/.progress/11-7.json.
__autocheck__
Перевірка надсилає відгук, у якому в полі comment є слово
__autocheck__, і потім видаляє саме цей рядок. Тому не бійся,
що чекер зіпсує статистику. Але й не пиши це слово сам: свій відгук
із таким текстом буде прибрано разом із маркерним.private блокує оцінку: якщо відповіді людей віддаються по HTTP,
решта зробленого не має значення. no_media діє наскрізно весь
модуль. На уроці 14 check11 8 прожене обидва повторно.ls -l ~/bin ~/data — права 700
на скрипті й на теці;curl і рядок, який після нього зʼявився
у файлі;=, у файлі — з апострофом.Це те, що бачить учитель, коли виставляє оцінку.
| Складник | Вага | Результат |
|---|
=, +, -
або @, у таблиці перестає бути текстом.cut -d, -f2- ~/data/responses.csv | sort | uniq -d
має мовчати.=,
і показати рядок у файлі: апостроф має бути на місці.feedback-stats.json у теці ~/www/data/.
Сьогодні ти рахуєш це руками. На уроці 12 ті самі два числа
рахуватиме скрипт report.sh за розкладом, без тебе.report.sh: він рахує середню оцінку, розподіл оцінок
і три функції, яких просять найчастіше. А cron —
це розклад на сервері — запускатиме скрипт щогодини без тебе.
Наприкінці буде режим «Зламай конвеєр»: учитель псує одну ланку,
а ти шукаєш яку.Кожне число, яке ти можеш перевірити, має посилання. Перевіряти дозволено й корисно.
=, +, -,
@, табуляція, CR, LF; наслідки й способи екранування —
owasp.org · CSV Injection=1+1, +1+1, -1+1,
@SUM(1,1), =HYPERLINK(…) і зауваження, що
універсальної схеми санітизації немає —
OWASP WSTG · Testing for CSV InjectionREQUEST_METHOD, CONTENT_LENGTH,
тіло запиту у стандартному вводі, заголовки відповіді у виводі —
RFC 3875. rfc-editor.org · RFC 3875required, min,
max і те, що це підказка браузера —
MDN · Constraint validationНадбавка 15 секунд за відкрите питання в конструкторі,
приклади рядків у файлі, імена, дати й числа «12 відгуків, середня 4,3» —
ілюстрації автора сторінки, а не результати вимірювань. Повний список джерел
із поясненнями — у файлі urok-11-джерела.md поруч із цією
сторінкою.