Алекс Тарков

Бывает, что ты узнаёшь что-то, и тебя поражает, как так вышло, что ты не доходил до этого раньше. В западном интернете заметки об этом часто начинаются с TIL — Today I Learned. Мне не хотелось тащить сюда англицизм, потому я решил, что буду называть такие записи крупицами знаний.

А поскольку они совсем маленькие, для них нет отдельных страниц, они все перечислены тут. Если нужно дать ссылку на конкретную крупицу — она в разделителе.


Клавиатуры и другие устройства нынче можно конфигурировать через браузер, как это делает Keychron, например:

launcher.keychron.com

Это возможно благодаря WebHID API.


В стандартом логгере Андроида помимо привычных функций типа Log.i (info), Log.v (verbose) и подобных есть замечательная Log.wtf. What a Terrible Failure, если верить им:

developer.android.com/reference/android/util/Log.html#wtf


Тайпскрипт не следует семверу:

github.com/microsoft/TypeScript#14116


Есть простой способ найти документы в коллекции MongoDB, у которых массив имеет длину больше N — с помощью обращения к индексу массива.

Например, чтобы найти все документы, у которых поле-массив содержит больше 3 элементов, можно сделать так:

db.collection.find({ 'array.3': { $exists: true } });

Более «правильный» способ требует использования $expr, $gt и $size, но он получается слишком многословным.


Если вы всё ещё используете docker-compose, потому что раньше это была отдельная утилита для Докера, то можно больше не.

Сегодня и docker compose отлично работает.


Много лет назад все сервисы использовали MD5 для хэширования паролей, адресов почт и прочих данных. Большинство из них тогда не использовали соль, потому что все считали, что MD5 довольно сложно взломать.

Сегодня у нас есть множество утёкших MD5-хэшей и соответствующих исходных строк, которые собраны в базы данных. Поэтому существуют сервисы, где можно просто вставить MD5-хэш в одно поле и получить исходную строку за считанные секунды.

Я разбирал страницы своих старых блогов, восстанавливая некоторые из них, и заметил, что раньше мой блог на WordPress использовал Gravatar для аватаров пользователей.

Gravatar — это сервис, где вы один раз регистрировались, указывали изображение профиля, и затем каждый раз, когда использовали ту же почту в комментариях на других блогах, WordPress показывал всем выбранную вами картинку. Всё просто.

(Gravatar существует и сегодня, но я не знаю, используется ли он где-либо сейчас, поэтому говорю в прошедшем времени.)

Более того, вам даже не нужно было регистрироваться в Gravatar, чтобы получать одно и то же изображение на всех сайтах WordPress. Они генерировали изображение для любой почты, которую вы указывали. Так что если вы везде использовали один и тот же адрес, у вас было одинаковое изображение.

URL вашего аватара выглядел так:

http://1.gravatar.com/avatar/f9c6da5d3c13b99dff24b478540acbd0?s=39&d=identicon&r=G

Хэш в ссылке, — это просто MD5 от почты, для которой Gravatar возвращает изображение. В те времена это, вероятно, казалось «безопасным».

Но поскольку базы данных с несолёными MD5-хэшами утекали множество раз, я легко расшифровал их и узнал почты пользователей. Хотя в архивных страницах моего блога они не отображались.

Раньше WordPress не показывал адрес почты автора комментария. Только имя и аватар — ради приватности и всего такого.

Но сегодня мы можем легко обойти этот слой приватности из-за количества утечек и отсутствия соли в хэшах.

Таким образом, помимо того, что сегодня можно получить почту пользователя любого блога на WordPress, я ещё и убедился, что любой алгоритм хэширования может стать «слабым», если использовать его неправильно и не уделять достаточно внимания безопасности БД.


В Go используют предопределённый таймстамп для описания формата даты:

pkg.go.dev/time#pkg-constants

Другими словами, вместо YYYY используется конкретный год — 2006; вместо MM — 01 и так далее.

Каждая часть формата сопоставляется с конкретным числом, что позволяет парсеру легко понять, что именно мы имеем в виду. Нужен короткий год? Используйте 06. Его нельзя перепутать с месяцем, потому что месяц всегда кодируется числом 1 (или January и его формы).

Например, вот как я форматирую дату для атрибута datetime в HTML-теге <time> в моём сетапе Hugo-блога, который использует Go для шаблонов:

<time datetime="{{ time.Format "2006-01-02T15:04:05Z0700" .Date.Local }}">

2006-01-02T15:04:05Z0700 — это формат, описанный в этой странной, но гениальной системе Go.


Узнал сегодня, что существует такой проект как «Can I Email». Например:

caniemail.com/features/css-at-media-prefers-color-scheme


Оказывается, в мире MongoDB существует такая вещь, как атака типа «инъекция селекторов» (Selector Injection Attack).

Вкратце: допустим, у вас есть приложение на Express/Koa и вы используете Mongoose. Вам нужно проверить, валидны ли учётные данные пользователя, и вы делаете это через следующий код:

await User.findOne({
  email: req.body.email,
  password: req.body.password,
});

(Конечно, пароль должен быть захеширован, но это просто пример.)

Код выглядит безопасным, но это не так. Если клиент отправит такое тело в запросе:

{
  "email": "[email protected]",
  "password": { "$ne": null }
}

MongoDB будет искать пользователя с указанным email и любым паролем, отличным от null. В случае аутентификации такой «пользователь» будет успешно авторизован.

Таким образом, даже если мы не работаем в мире SQL, нам всё равно необходимо санитизировать пользовательский ввод.

Есть несколько способов это сделать. Например, можно вручную фильтровать все ключи, содержащие $ и . в передаваемых объектах. См. пакет express-mongo-sanitize.

Или, если используется Mongoose, можно включить опцию sanitizeFilter, которая оборачивает ключи фильтра в $eq и делает такую инъекцию невозможной. Подробнее:

thecodebarbarian.com/whats-new-in-mongoose-6-sanitizefilter.html


Я знал, что существуют локалезависимые альтернативы для toLowerCase и toUpperCase, но никогда не задумывался, зачем.

Причина в том, как работает Unicode. Операции изменения регистра зависят от локали, потому что некоторые символы переиспользуются в разных языках, несмотря на то, что они не равны между собой.

Самый наглядный пример — буква I в турецком языке. Существует две версии I: с точкой и без точки. Если изменить регистр этой буквы без указания локали, получится неправильный результат:

'I'.toLowerCase()
'i'
'I'.toLocaleLowerCase('tr')
'ı'

Было бы иначе, если бы в турецком языке были собственные отдельные символы для I (с точкой и без). Но поскольку символы переиспользуются в разных языках, необходимо учитывать, что именно вы преобразуете, прежде чем выполнять операцию.

Я наткнулся на этот пример в замечательной статье Никиты Прокопова о Unicode:

tonsky.me/blog/unicode/


Bash поддерживает больше глобов, чем описано в man glob.7.

См.: gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Pattern-Matching

Например, можно использовать отрицание. Пригождается, когда надо применить команду ко всем файлам, кроме одного.

В моём случае я хотел переместить всё в поддиректорию, исключая саму эту поддиректорию. Сделал так:

mkdir old
mv !(old) old

Конечно, можно просто выполнить mv * old. mv выдаст ошибку о рекурсии, но файлы всё равно будут перемещены. Просто неуютно.


В GitHub Flavored Markdown есть специальный синтаксис для выделения важных кусков текста:

docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts

> [!WARNING]
> Critical content demanding immediate user attention due to potential risks.

В TypeScript

title: string | undefined;

title?: string;

Это вроде очевидно, но я никогда об этом не задумывался. В первом случае нужно обязательно передать свойство title, тогда как во втором его можно полностью опустить.

Кстати, в TypeScript v4.4 добавили флаг --exactOptionalPropertyTypes, который заставляет TS проверять такие случаи.

Image by rawpixel.com on Freepik: https://www.freepik.com/free-vector/illustration-summer-beach-object_2824865.htm