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