@@ -160,89 +160,81 @@ Future<shelf.Response> handler(shelf.Request request) async {
160160 }
161161 final imageUrlBytes = utf8.encode (imageUrl);
162162
163- if (_constantTimeEquals (hmacSign (secret, imageUrlBytes), signature)) {
164- final Uri parsedImageUrl;
165- try {
166- parsedImageUrl = Uri .parse (imageUrl);
167- } on FormatException catch (e) {
168- return shelf.Response .badRequest (
169- body: 'Malformed proxied url $e ' ,
170- headers: securityHeaders,
171- );
172- }
173- if (! (parsedImageUrl.isScheme ('http' ) ||
174- parsedImageUrl.isScheme ('https' ))) {
175- return shelf.Response .badRequest (
176- body: 'Can only proxy http and https urls' ,
177- headers: securityHeaders,
178- );
179- }
180- if (! parsedImageUrl.isAbsolute) {
181- return shelf.Response .badRequest (
182- body: 'Can only proxy absolute urls' ,
183- headers: securityHeaders,
184- );
185- }
163+ if (! _constantTimeEquals (hmacSign (secret, imageUrlBytes), signature)) {
164+ return shelf.Response .unauthorized ('Bad hmac' , headers: securityHeaders);
165+ }
166+ final Uri parsedImageUrl;
167+ try {
168+ parsedImageUrl = Uri .parse (imageUrl);
169+ } on FormatException catch (e) {
170+ return shelf.Response .badRequest (
171+ body: 'Malformed proxied url $e ' ,
172+ headers: securityHeaders,
173+ );
174+ }
175+ if (! (parsedImageUrl.isScheme ('http' ) ||
176+ parsedImageUrl.isScheme ('https' ))) {
177+ return shelf.Response .badRequest (
178+ body: 'Can only proxy http and https urls' ,
179+ headers: securityHeaders,
180+ );
181+ }
182+ if (! parsedImageUrl.isAbsolute) {
183+ return shelf.Response .badRequest (
184+ body: 'Can only proxy absolute urls' ,
185+ headers: securityHeaders,
186+ );
187+ }
186188
187- int statusCode;
188- List <int > bytes;
189- String ? contentType;
190- String ? contentEncoding;
189+ Future <
190+ ({
191+ int statusCode,
192+ List <int > body,
193+ String ? contentType,
194+ String ? contentEncoding,
195+ })
196+ >
197+ makeRequest (Uri url, {int redirectCount = 0 }) async {
198+ stderr.writeln ('Requesting $url ' );
199+ if (redirectCount > 10 ) {
200+ throw RedirectException ('Too many redirects.' );
201+ }
202+ final request = await client.getUrl (url);
203+ final timeout = Timer (timeoutDelay, () {
204+ request.abort (RequestTimeoutException ('No response' ));
205+ });
206+ HttpClientResponse ? response;
191207 try {
192- (statusCode, bytes, contentType, contentEncoding) = await retry (
193- maxDelay: timeoutDelay,
194- maxAttempts: isTesting ? 2 : 8 ,
195- () async {
196- final request = await client.getUrl (parsedImageUrl);
197- Timer ? timeoutTimer;
198- void scheduleRequestTimeout () {
199- timeoutTimer? .cancel ();
200- timeoutTimer = Timer (timeoutDelay, () {
201- request.abort (RequestTimeoutException ('No response' ));
202- });
203- }
204-
205- request.headers.add (
206- 'user-agent' ,
207- 'Image proxy for pub.dev. See https://github.com/dart-lang/pub-dev/pkg/image-proxy. If you have any issues, contact [email protected] .' ,
208- );
209- request.followRedirects = false ;
210- scheduleRequestTimeout ();
211- var response = await request.close ();
212- var redirectCount = 0 ;
213- while (response.isRedirect) {
214- await response.drain ();
215- redirectCount++ ;
216- if (redirectCount > 10 ) {
217- throw RedirectException ('Too many redirects.' );
218- }
219- final location = response.headers.value (
220- HttpHeaders .locationHeader,
221- );
222- if (location == null ) {
223- throw RedirectException ('No location header in redirect.' );
224- }
225- final uri = parsedImageUrl.resolve (location);
226- final request = await client.getUrl (uri);
227-
228- request.headers.add ('user-agent' , 'pub-proxy' );
229- // Set the body or headers as desired.
230- request.followRedirects = false ;
231- scheduleRequestTimeout ();
232- response = await request.close ();
233- }
234- switch (response.statusCode) {
235- case final int statusCode && >= 500 && < 600 :
236- throw ServerSideException (statusCode: statusCode);
237- case final int statusCode && >= 300 && < 400 :
238- throw ServerSideException (statusCode: statusCode);
239- }
240- final contentLength = response.contentLength;
241- if (contentLength != - 1 && contentLength > maxImageSize) {
242- throw TooLargeException ();
243- }
244- return (
245- response.statusCode,
208+ request.headers.add (
209+ 'user-agent' ,
210+ 'Image proxy for pub.dev. See https://github.com/dart-lang/pub-dev/pkg/image-proxy. If you have any issues, contact [email protected] .' ,
211+ );
212+ request.followRedirects = false ;
213+ response = await request.close ();
214+ if (response.isRedirect) {
215+ await response.listen ((_) => null ).cancel ();
216+ final location = response.headers.value (HttpHeaders .locationHeader);
217+ if (location == null ) {
218+ throw RedirectException ('No location header in redirect.' );
219+ }
220+ return makeRequest (
221+ parsedImageUrl.resolve (location),
222+ redirectCount: redirectCount + 1 ,
223+ );
224+ }
225+ switch (response.statusCode) {
226+ case final int statusCode && >= 500 && < 600 :
227+ throw ServerSideException (statusCode: statusCode);
228+ case final int statusCode && >= 300 && < 400 :
229+ throw ServerSideException (statusCode: statusCode);
230+ }
231+ final contentLength = response.contentLength;
232+ if (contentLength != - 1 && contentLength > maxImageSize) {
233+ throw TooLargeException ();
234+ }
235+ return (
236+ statusCode: response.statusCode,
237+ body:
246238 await readAllBytes (
247239 response,
248240 contentLength == - 1 ? maxImageSize : contentLength,
@@ -252,49 +244,60 @@ Future<shelf.Response> handler(shelf.Request request) async {
252244 throw RequestTimeoutException ('No response' );
253245 },
254246 ),
255- response.headers.value ('content-type' ),
256- response.headers.value ('content-encoding' ),
257- );
258- },
259- retryIf: (e) =>
260- e is SocketException ||
261- e is http.ClientException ||
262- e is ServerSideException ,
263- );
264- } on TooLargeException {
265- return shelf.Response .badRequest (
266- body: 'Image too large' ,
267- headers: securityHeaders,
268- );
269- } on RedirectException catch (e) {
270- return shelf.Response .badRequest (
271- body: e.message,
272- headers: securityHeaders,
273- );
274- } on RequestTimeoutException catch (e) {
275- return shelf.Response .badRequest (
276- body: e.message,
277- headers: securityHeaders,
278- );
279- } on ServerSideException catch (e) {
280- return shelf.Response .badRequest (
281- body: 'Failed to retrieve image. Status code ${e .statusCode }' ,
282- headers: securityHeaders,
247+ contentType: response.headers.value ('content-type' ),
248+ contentEncoding: response.headers.value ('content-encoding' ),
283249 );
250+ } finally {
251+ timeout.cancel ();
252+ try {
253+ // Attempt closing resources
254+ request.abort ();
255+ await response? .listen ((_) => null ).cancel ();
256+ } catch (_) {}
284257 }
258+ }
259+
260+ try {
261+ final (: statusCode, : body, : contentType, : contentEncoding) = await retry (
262+ maxDelay: timeoutDelay,
263+ maxAttempts: isTesting ? 2 : 8 ,
264+ () => makeRequest (parsedImageUrl),
265+ retryIf: (e) =>
266+ e is SocketException ||
267+ e is http.ClientException ||
268+ e is ServerSideException ,
269+ );
285270
286271 return shelf.Response (
287272 statusCode,
288- body: bytes ,
273+ body: body ,
289274 headers: {
290275 'Cache-control' : 'max-age=180, public' ,
291276 'content-type' : ? contentType,
292277 'content-encoding' : ? contentEncoding,
293278 ...securityHeaders,
294279 },
295280 );
296- } else {
297- return shelf.Response .unauthorized ('Bad hmac' , headers: securityHeaders);
281+ } on TooLargeException {
282+ return shelf.Response .badRequest (
283+ body: 'Image too large' ,
284+ headers: securityHeaders,
285+ );
286+ } on RedirectException catch (e) {
287+ return shelf.Response .badRequest (
288+ body: e.message,
289+ headers: securityHeaders,
290+ );
291+ } on RequestTimeoutException catch (e) {
292+ return shelf.Response .badRequest (
293+ body: e.message,
294+ headers: securityHeaders,
295+ );
296+ } on ServerSideException catch (e) {
297+ return shelf.Response .badRequest (
298+ body: 'Failed to retrieve image. Status code ${e .statusCode }' ,
299+ headers: securityHeaders,
300+ );
298301 }
299302 } catch (e, st) {
300303 stderr.writeln ('Uncaught error: $e $st ' );
0 commit comments