/
githubmirror
/
angular
Обзор
Документация
Войти
/
githubmirror
/
angular
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/common/http/src/xsrf.ts
141 строка
4 KB
Jaime Burgos
fix(http): enable xsrf for root-provided HttpClient
29 июл 2026, 18:40
Не верифицирован
29 июл 2026, 18:40
de240a5
Код
Авторство
О чём код?
/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ import { EnvironmentInjector, inject, Injectable, InjectionToken, runInInjectionContext, Service, } from '@angular/core'; import {Observable} from 'rxjs'; import {DOCUMENT, ɵparseCookieValue as parseCookieValue, PlatformLocation} from '../../index'; import type {HttpHandler} from './backend'; import type {HttpHandlerFn, HttpInterceptor} from './interceptor'; import {HttpRequest} from './request'; import {HttpEvent} from './response'; export const XSRF_ENABLED = new InjectionToken<boolean>( typeof ngDevMode !== 'undefined' && ngDevMode ? 'XSRF_ENABLED' : '', { factory: () => true, }, ); export const XSRF_DEFAULT_COOKIE_NAME = 'XSRF-TOKEN'; export const XSRF_COOKIE_NAME = new InjectionToken<string>( typeof ngDevMode !== 'undefined' && ngDevMode ? 'XSRF_COOKIE_NAME' : '', { // Providing a factory implies that the token is provided in root by default factory: () => XSRF_DEFAULT_COOKIE_NAME, }, ); export const XSRF_DEFAULT_HEADER_NAME = 'X-XSRF-TOKEN'; export const XSRF_HEADER_NAME = new InjectionToken<string>( typeof ngDevMode !== 'undefined' && ngDevMode ? 'XSRF_HEADER_NAME' : '', { factory: () => XSRF_DEFAULT_HEADER_NAME, }, ); /** * `HttpXsrfTokenExtractor` which retrieves the token from a cookie. */ @Service() export class HttpXsrfCookieExtractor implements HttpXsrfTokenExtractor { private readonly cookieName = inject(XSRF_COOKIE_NAME); private readonly doc = inject(DOCUMENT); private lastCookieString: string = ''; private lastToken: string | null = null; /** * @internal for testing */ parseCount: number = 0; getToken(): string | null { if (typeof ngServerMode !== 'undefined' && ngServerMode) { return null; } const cookieString = this.doc.cookie || ''; if (cookieString !== this.lastCookieString) { this.parseCount++; this.lastToken = parseCookieValue(cookieString, this.cookieName); this.lastCookieString = cookieString; } return this.lastToken; } } /** * Retrieves the current XSRF token to use with the next outgoing request. * * @publicApi */ @Injectable({providedIn: 'root', useExisting: HttpXsrfCookieExtractor}) export abstract class HttpXsrfTokenExtractor { /** * Get the XSRF token to use with an outgoing request. * * Will be called for every request, so the token may change between requests. */ abstract getToken(): string | null; } export function xsrfInterceptorFn( req: HttpRequest<unknown>, next: HttpHandlerFn, ): Observable<HttpEvent<unknown>> { // Skip both non-mutating requests // Non-mutating requests generally don't require a token. if (!inject(XSRF_ENABLED) || req.method === 'GET' || req.method === 'HEAD') { return next(req); } try { const locationHref = inject(PlatformLocation).href; const {origin: locationOrigin} = new URL(locationHref); // We can use `new URL` to normalize a relative URL like '//something.com' to // 'https://something.com' in order to make consistent same-origin comparisons. const {origin: requestOrigin} = new URL(req.url, locationOrigin); if (locationOrigin !== requestOrigin) { return next(req); } } catch { // Handle invalid URLs gracefully. return next(req); } const token = inject(HttpXsrfTokenExtractor).getToken(); const headerName = inject(XSRF_HEADER_NAME); // Be careful not to overwrite an existing header of the same name. if (token != null && !req.headers.has(headerName)) { req = req.clone({headers: req.headers.set(headerName, token)}); } return next(req); } /** * `HttpInterceptor` which adds an XSRF token to eligible outgoing requests. */ @Injectable() export class HttpXsrfInterceptor implements HttpInterceptor { private readonly injector = inject(EnvironmentInjector); intercept(initialRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return runInInjectionContext(this.injector, () => xsrfInterceptorFn(initialRequest, (downstreamRequest) => next.handle(downstreamRequest)), ); } }