Почему возникает ошибка 404 только под Firefox?
Есть расширение YaMuTools, которое добавляет функций для Яндекс.Музыки. Под Chrome работает как и задумано. Недавно портировал под Firefox и возникла проблема.
Один из get-запросов под Firefox возвращает ошибку 404. Все остальные со статусом 200. Под Chrome тот же запрос и тот же код работает без ошибки. В чем может быть проблема?
Чтобы воспроизвести ошибку, установите расширение, зайдите на страницу плейлиста Яндекс.Музыки (нужно быть залогиненным) и нажмите кнопки: YaMuTools > Фильтр треков > Вычитание треков. Появится всплывающее окно с бесконечной загрузкой. В консоле будет ошибка 404. Полный код есть здесь.
Запрос совершается через XMLHttpRequest. Пробовал fetch и очистку кэш/кук - не помогло.
Firefox, 404
XHRGET
404
Not Found
HTTP/1.1
GET /handlers/library.jsx?owner=music-blog&filter=playlists HTTP/1.1
Host: music.yandex.ru
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:92.0) Gecko/20100101 Firefox/92.0
Accept: */*
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
X-Retpath-Y: https://music.yandex.ru/users/music-blog/playlists/2678
Content-Type: application/x-www-form-urlencoded
X-Current-UID: 224867931
Connection: keep-alive
Cookie: ...
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Тот же запрос в Chrome
GET /handlers/library.jsx?owner=music-blog&filter=playlists HTTP/1.1
Host: music.yandex.ru
Connection: keep-alive
sec-ch-ua: "Yandex";v="21", " Not;A Brand";v="99", "Chromium";v="93"
DNT: 1
X-Retpath-Y: https://music.yandex.ru/users/music-blog/playlists/2678
X-Current-UID: 224867931
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 YaBrowser/21.9.0.1044 Yowser/2.5 Safari/537.36
Content-Type: application/x-www-form-urlencoded
sec-ch-ua-platform: "Windows"
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://music.yandex.ru/users/music-blog/playlists/2678
Accept-Encoding: gzip, deflate, br
Accept-Language: ru,en;q=0.9,la;q=0.8
Cookie: ...
Другой запрос под Firefox
XHRGET
200
OK
HTTP/1.1
GET /handlers/playlist.jsx?owner=music-blog&kinds=2678&light=false HTTP/1.1
Host: music.yandex.ru
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:92.0) Gecko/20100101 Firefox/92.0
Accept: */*
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
X-Retpath-Y: https://music.yandex.ru/users/music-blog/playlists/2678
Content-Type: application/x-www-form-urlencoded
X-Current-UID: 224867931
Connection: keep-alive
Cookie: ...
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Этот код отправляет запрос во всех случаях.
function requestGET(url, callback) {
requestOfType({
type: 'GET',
url: url,
formData: null,
callback: callback,
});
}
let countRequest = 0;
function requestOfType(data) {
if (countRequest > 100) {
let timerId = setTimeout(() => {
clearTimeout(timerId);
requestOfType(data);
}, 1000);
return;
}
let request = new XMLHttpRequest();
countRequest++;
request.onreadystatechange = function () {
if (request.readyState == 4) {
countRequest--;
if (request.status >= 400) {
data.callback('error');
} else {
let responseJSON = JSON.parse(request.responseText);
data.callback(responseJSON);
}
}
};
request.open(data.type, data.url);
request.send(data.formData);
}
Ответы (1 шт):
В данном случае, сетевой запрос из расширения Firefox не содержит заголовок Referer. Некоторые реализации бэкэнда проверяют значение этого заголовка (для ограничения сторонних запросов с тем же ориджином)... то есть, как дополнение к CORS, немного помогающее бороться с запросами из инъекций кода в веб-страницы.
Добавлять этот заголовок самостоятельно, перед отправкой запроса - бесполезно: в соответствии со спецификацией XMLHttpRequest, реферер автоматически удаляется при отправке запроса (это мера обеспечения безопасности).
Почему в расширениях Firefox сам не добавляет Referer и Origin
В современных браузерах, скрипты страницы и скрипты расширений выполняются в изолированных сэндбоксах, для защиты от XSS-подобных атак. Но реализация такого разделения в данный момент не стандартизирована (как и браузерные расширения в целом), поэтому разные браузеры делают это по-разному.
Firefox, в частности, предоставляет контент-скриптам расширений отдельные от контекста страницы глобальные объекты, включая объект XMLHttpRequest (который в расширении получает повышенные привилегии, и поэтому должен быть изолирован от скриптов страницы). Это имеет позитивный побочный эффект отправки запросов без "утечки" кроссовых кук и реферера с ориджином - что мы и наблюдаем здесь.
Что же с этим делать
Когда в Firefox-расширении хочется отправить запрос "от имени" странички (с автодобавлением заголовка Referer и возможностью использования withCredentials в случае кроссдоменности) - надо всего лишь получить объект XMLHttpRequest из контекста страницы.
Сейчас это делается так (MDN):
const xhr = new content.XMLHttpRequest();
И современный фичедетект вариант (для кроссбраузерных расширений, ES11+):
const xhr = new (window.content?.XMLHttpRequest ?? window.XMLHttpRequest)();
В старом обсуждении на Mozilla Discourse (форум сообщества) можно найти еще и такой фичедетект вариант, который я тут слегка поправил:
function getXMLHttpRequest() {
try {
return XPCNativeWrapper(new window.wrappedJSObject.XMLHttpRequest());
}
catch () {
return new XMLHttpRequest();
}
}
// ....
const xhr = getXMLHttpRequest();
Классическое решение использует обертку объекта XMLHttpRequest страницы, полученного через wrappedJSObject (функция XPCNativeWrapper добавляет безопасности: любые обращения к свойствам обернутого объекта будут игнорировать переопределения, возвращая исходные значения. На данный момент, документацию этой функции почему-то выпилили из MDN).
Учитывая то что работа с wrappedJSObject официально не рекомендована как решение - первый способ из этого ответа (с window.content) воспринимается более надежным, чем классический. Использование нейтив-обертки опционально, по желанию... судя по базе знаний, она вообще по большей части предназначена для XUL-документов, и у меня есть сомнения насчет "не является ли грязным хаком ее использование в контент-скрипте расширения".