UNPKG

7.93 kBPlain TextView Raw
1/**
2 * -------------------------------------------------------------------------------------------
3 * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
4 * See License in the project root for license information.
5 * -------------------------------------------------------------------------------------------
6 */
7
8/**
9 * @module RedirectHandler
10 */
11
12import { Context } from "../IContext";
13import { RequestMethod } from "../RequestMethod";
14import { Middleware } from "./IMiddleware";
15import { MiddlewareControl } from "./MiddlewareControl";
16import { cloneRequestWithNewUrl } from "./MiddlewareUtil";
17import { RedirectHandlerOptions } from "./options/RedirectHandlerOptions";
18import { FeatureUsageFlag, TelemetryHandlerOptions } from "./options/TelemetryHandlerOptions";
19
20/**
21 * @class
22 * Class
23 * @implements Middleware
24 * Class representing RedirectHandler
25 */
26export class RedirectHandler implements Middleware {
27 /**
28 * @private
29 * @static
30 * A member holding the array of redirect status codes
31 */
32 private static REDIRECT_STATUS_CODES: number[] = [
33 301, // Moved Permanently
34 302, // Found
35 303, // See Other
36 307, // Temporary Permanently
37 308, // Moved Permanently
38 ];
39
40 /**
41 * @private
42 * @static
43 * A member holding SeeOther status code
44 */
45 private static STATUS_CODE_SEE_OTHER = 303;
46
47 /**
48 * @private
49 * @static
50 * A member holding the name of the location header
51 */
52 private static LOCATION_HEADER = "Location";
53
54 /**
55 * @private
56 * @static
57 * A member representing the authorization header name
58 */
59 private static AUTHORIZATION_HEADER = "Authorization";
60
61 /**
62 * @private
63 * @static
64 * A member holding the manual redirect value
65 */
66 private static MANUAL_REDIRECT: RequestRedirect = "manual";
67
68 /**
69 * @private
70 * A member holding options to customize the handler behavior
71 */
72 private options: RedirectHandlerOptions;
73
74 /**
75 * @private
76 * A member to hold next middleware in the middleware chain
77 */
78 private nextMiddleware: Middleware;
79
80 /**
81 * @public
82 * @constructor
83 * To create an instance of RedirectHandler
84 * @param {RedirectHandlerOptions} [options = new RedirectHandlerOptions()] - The redirect handler options instance
85 * @returns An instance of RedirectHandler
86 */
87
88 public constructor(options: RedirectHandlerOptions = new RedirectHandlerOptions()) {
89 this.options = options;
90 }
91
92 /**
93 * @private
94 * To check whether the response has the redirect status code or not
95 * @param {Response} response - The response object
96 * @returns A boolean representing whether the response contains the redirect status code or not
97 */
98 private isRedirect(response: Response): boolean {
99 return RedirectHandler.REDIRECT_STATUS_CODES.indexOf(response.status) !== -1;
100 }
101
102 /**
103 * @private
104 * To check whether the response has location header or not
105 * @param {Response} response - The response object
106 * @returns A boolean representing the whether the response has location header or not
107 */
108 private hasLocationHeader(response: Response): boolean {
109 return response.headers.has(RedirectHandler.LOCATION_HEADER);
110 }
111
112 /**
113 * @private
114 * To get the redirect url from location header in response object
115 * @param {Response} response - The response object
116 * @returns A redirect url from location header
117 */
118 private getLocationHeader(response: Response): string {
119 return response.headers.get(RedirectHandler.LOCATION_HEADER);
120 }
121
122 /**
123 * @private
124 * To check whether the given url is a relative url or not
125 * @param {string} url - The url string value
126 * @returns A boolean representing whether the given url is a relative url or not
127 */
128 private isRelativeURL(url: string): boolean {
129 return url.indexOf("://") === -1;
130 }
131
132 /**
133 * @private
134 * To check whether the authorization header in the request should be dropped for consequent redirected requests
135 * @param {string} requestUrl - The request url value
136 * @param {string} redirectUrl - The redirect url value
137 * @returns A boolean representing whether the authorization header in the request should be dropped for consequent redirected requests
138 */
139 private shouldDropAuthorizationHeader(requestUrl: string, redirectUrl: string): boolean {
140 const schemeHostRegex = /^[A-Za-z].+?:\/\/.+?(?=\/|$)/;
141 const requestMatches: string[] = schemeHostRegex.exec(requestUrl);
142 let requestAuthority: string;
143 let redirectAuthority: string;
144 if (requestMatches !== null) {
145 requestAuthority = requestMatches[0];
146 }
147 const redirectMatches: string[] = schemeHostRegex.exec(redirectUrl);
148 if (redirectMatches !== null) {
149 redirectAuthority = redirectMatches[0];
150 }
151 return typeof requestAuthority !== "undefined" && typeof redirectAuthority !== "undefined" && requestAuthority !== redirectAuthority;
152 }
153
154 /**
155 * @private
156 * @async
157 * To update a request url with the redirect url
158 * @param {string} redirectUrl - The redirect url value
159 * @param {Context} context - The context object value
160 * @returns Nothing
161 */
162 private async updateRequestUrl(redirectUrl: string, context: Context): Promise<void> {
163 context.request = typeof context.request === "string" ? redirectUrl : await cloneRequestWithNewUrl(redirectUrl, context.request as Request);
164 }
165
166 /**
167 * @private
168 * To get the options for execution of the middleware
169 * @param {Context} context - The context object
170 * @returns A options for middleware execution
171 */
172 private getOptions(context: Context): RedirectHandlerOptions {
173 let options: RedirectHandlerOptions;
174 if (context.middlewareControl instanceof MiddlewareControl) {
175 options = context.middlewareControl.getMiddlewareOptions(RedirectHandlerOptions) as RedirectHandlerOptions;
176 }
177 if (typeof options === "undefined") {
178 options = Object.assign(new RedirectHandlerOptions(), this.options);
179 }
180 return options;
181 }
182
183 /**
184 * @private
185 * @async
186 * To execute the next middleware and to handle in case of redirect response returned by the server
187 * @param {Context} context - The context object
188 * @param {number} redirectCount - The redirect count value
189 * @param {RedirectHandlerOptions} options - The redirect handler options instance
190 * @returns A promise that resolves to nothing
191 */
192 private async executeWithRedirect(context: Context, redirectCount: number, options: RedirectHandlerOptions): Promise<void> {
193 await this.nextMiddleware.execute(context);
194 const response = context.response;
195 if (redirectCount < options.maxRedirects && this.isRedirect(response) && this.hasLocationHeader(response) && options.shouldRedirect(response)) {
196 ++redirectCount;
197 if (response.status === RedirectHandler.STATUS_CODE_SEE_OTHER) {
198 context.options.method = RequestMethod.GET;
199 delete context.options.body;
200 } else {
201 const redirectUrl: string = this.getLocationHeader(response);
202 if (!this.isRelativeURL(redirectUrl) && this.shouldDropAuthorizationHeader(response.url, redirectUrl)) {
203 delete context.options.headers[RedirectHandler.AUTHORIZATION_HEADER];
204 }
205 await this.updateRequestUrl(redirectUrl, context);
206 }
207 await this.executeWithRedirect(context, redirectCount, options);
208 } else {
209 return;
210 }
211 }
212
213 /**
214 * @public
215 * @async
216 * To execute the current middleware
217 * @param {Context} context - The context object of the request
218 * @returns A Promise that resolves to nothing
219 */
220 public async execute(context: Context): Promise<void> {
221 const redirectCount = 0;
222 const options = this.getOptions(context);
223 context.options.redirect = RedirectHandler.MANUAL_REDIRECT;
224 TelemetryHandlerOptions.updateFeatureUsageFlag(context, FeatureUsageFlag.REDIRECT_HANDLER_ENABLED);
225 return await this.executeWithRedirect(context, redirectCount, options);
226 }
227
228 /**
229 * @public
230 * To set the next middleware in the chain
231 * @param {Middleware} next - The middleware instance
232 * @returns Nothing
233 */
234 public setNext(next: Middleware): void {
235 this.nextMiddleware = next;
236 }
237}