Add 'packages/cors/' from commit 'b5c0635952fe9d863f6190495ca99b3f5a8ce378'
git-subtree-dir: packages/cors git-subtree-mainline:3a14263a6f
git-subtree-split:b5c0635952
This commit is contained in:
commit
12aa791a8e
11 changed files with 522 additions and 0 deletions
74
packages/cors/.gitignore
vendored
Normal file
74
packages/cors/.gitignore
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
.buildlog
|
||||
.packages
|
||||
.project
|
||||
.pub/
|
||||
.scripts-bin/
|
||||
build/
|
||||
**/packages/
|
||||
|
||||
# Files created by dart2js
|
||||
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
||||
# rules if you intend to use dart2js directly
|
||||
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
||||
# differentiate from explicit Javascript files)
|
||||
*.dart.js
|
||||
*.part.js
|
||||
*.js.deps
|
||||
*.js.map
|
||||
*.info.json
|
||||
|
||||
# Directory created by dartdoc
|
||||
doc/api/
|
||||
|
||||
# Don't commit pubspec lock file
|
||||
# (Library packages only! Remove pattern if developing an application package)
|
||||
pubspec.lock
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
.dart_tool
|
1
packages/cors/.travis.yml
Normal file
1
packages/cors/.travis.yml
Normal file
|
@ -0,0 +1 @@
|
|||
language: dart
|
2
packages/cors/CHANGELOG.md
Normal file
2
packages/cors/CHANGELOG.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
# 2.0.0
|
||||
* Updates for Dart 2 and Angel 2.
|
21
packages/cors/LICENSE
Normal file
21
packages/cors/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016 The Angel Framework
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
8
packages/cors/README.md
Normal file
8
packages/cors/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# cors
|
||||
[![Pub](https://img.shields.io/pub/v/angel_cors.svg)](https://pub.dartlang.org/packages/angel_cors)
|
||||
[![build status](https://travis-ci.org/angel-dart/cors.svg)](https://travis-ci.org/angel-dart/cors)
|
||||
|
||||
Angel CORS middleware.
|
||||
Port of [the original Express CORS middleware](https://github.com/expressjs/cors).
|
||||
|
||||
For complete example usage, see the [example file](example/example.dart).
|
4
packages/cors/analysis_options.yaml
Normal file
4
packages/cors/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
51
packages/cors/example/example.dart
Normal file
51
packages/cors/example/example.dart
Normal file
|
@ -0,0 +1,51 @@
|
|||
import 'dart:async';
|
||||
import 'package:angel_cors/angel_cors.dart';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
|
||||
Future configureServer(Angel app) async {
|
||||
// The default options will allow CORS for any request.
|
||||
// Combined with `fallback`, you can enable CORS application-wide.
|
||||
app.fallback(cors());
|
||||
|
||||
// You can also enable CORS for a single route.
|
||||
app.get(
|
||||
'/my_api',
|
||||
chain([
|
||||
cors(),
|
||||
(req, res) {
|
||||
// Request handling logic here...
|
||||
}
|
||||
]),
|
||||
);
|
||||
|
||||
// Likewise, you can apply CORS to a group.
|
||||
app.chain([cors()]).group('/api', (router) {
|
||||
router.get('/version', (req, res) => 'v0');
|
||||
});
|
||||
|
||||
// Of course, you can configure CORS.
|
||||
// The following is just a subset of the available options;
|
||||
app.fallback(cors(
|
||||
CorsOptions(
|
||||
origin: 'https://pub.dartlang.org', successStatus: 200, // default 204
|
||||
allowedHeaders: ['POST'],
|
||||
preflightContinue: false, // default false
|
||||
),
|
||||
));
|
||||
|
||||
// You can specify the origin in different ways:
|
||||
CorsOptions(origin: 'https://pub.dartlang.org');
|
||||
CorsOptions(origin: ['https://example.org', 'http://foo.bar']);
|
||||
CorsOptions(origin: RegExp(r'^foo\.[^$]+'));
|
||||
CorsOptions(origin: (String s) => s.length == 4);
|
||||
|
||||
// Lastly, you can dynamically configure CORS:
|
||||
app.fallback(dynamicCors((req, res) {
|
||||
return CorsOptions(
|
||||
origin: [
|
||||
req.headers.value('origin') ?? 'https://pub.dartlang.org',
|
||||
RegExp(r'\.com$'),
|
||||
],
|
||||
);
|
||||
}));
|
||||
}
|
100
packages/cors/lib/angel_cors.dart
Normal file
100
packages/cors/lib/angel_cors.dart
Normal file
|
@ -0,0 +1,100 @@
|
|||
/// Angel CORS middleware.
|
||||
library angel_cors;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'src/cors_options.dart';
|
||||
export 'src/cors_options.dart';
|
||||
|
||||
/// Determines if a request origin is CORS-able.
|
||||
typedef bool _CorsFilter(String origin);
|
||||
|
||||
bool _isOriginAllowed(String origin, [allowedOrigin]) {
|
||||
allowedOrigin ??= [];
|
||||
if (allowedOrigin is Iterable) {
|
||||
return allowedOrigin.any((x) => _isOriginAllowed(origin, x));
|
||||
} else if (allowedOrigin is String) {
|
||||
return origin == allowedOrigin;
|
||||
} else if (allowedOrigin is RegExp) {
|
||||
return origin != null && allowedOrigin.hasMatch(origin);
|
||||
} else if (origin != null && allowedOrigin is _CorsFilter) {
|
||||
return allowedOrigin(origin);
|
||||
} else {
|
||||
return allowedOrigin != false;
|
||||
}
|
||||
}
|
||||
|
||||
/// On-the-fly configures the [cors] handler. Use this when the context of the surrounding request
|
||||
/// is necessary to decide how to handle an incoming request.
|
||||
Future<bool> Function(RequestContext, ResponseContext) dynamicCors(
|
||||
FutureOr<CorsOptions> Function(RequestContext, ResponseContext) f) {
|
||||
return (req, res) async {
|
||||
var opts = await f(req, res);
|
||||
var handler = cors(opts);
|
||||
return await handler(req, res);
|
||||
};
|
||||
}
|
||||
|
||||
/// Applies the given [CorsOptions].
|
||||
Future<bool> Function(RequestContext, ResponseContext) cors(
|
||||
[CorsOptions options]) {
|
||||
options ??= CorsOptions();
|
||||
|
||||
return (req, res) async {
|
||||
// access-control-allow-credentials
|
||||
if (options.credentials == true) {
|
||||
res.headers['access-control-allow-credentials'] = 'true';
|
||||
}
|
||||
|
||||
// access-control-allow-headers
|
||||
if (req.method == 'OPTIONS' && options.allowedHeaders.isNotEmpty) {
|
||||
res.headers['access-control-allow-headers'] =
|
||||
options.allowedHeaders.join(',');
|
||||
} else if (req.headers['access-control-request-headers'] != null) {
|
||||
res.headers['access-control-allow-headers'] =
|
||||
req.headers.value('access-control-request-headers');
|
||||
}
|
||||
|
||||
// access-control-expose-headers
|
||||
if (options.exposedHeaders.isNotEmpty) {
|
||||
res.headers['access-control-expose-headers'] =
|
||||
options.exposedHeaders.join(',');
|
||||
}
|
||||
|
||||
// access-control-allow-methods
|
||||
if (req.method == 'OPTIONS' && options.methods.isNotEmpty) {
|
||||
res.headers['access-control-allow-methods'] = options.methods.join(',');
|
||||
}
|
||||
|
||||
// access-control-max-age
|
||||
if (req.method == 'OPTIONS' && options.maxAge != null) {
|
||||
res.headers['access-control-max-age'] = options.maxAge.toString();
|
||||
}
|
||||
|
||||
// access-control-allow-origin
|
||||
if (options.origin == false || options.origin == '*') {
|
||||
res.headers['access-control-allow-origin'] = '*';
|
||||
} else if (options.origin is String) {
|
||||
res
|
||||
..headers['access-control-allow-origin'] = options.origin as String
|
||||
..headers['vary'] = 'origin';
|
||||
} else {
|
||||
bool isAllowed =
|
||||
_isOriginAllowed(req.headers.value('origin'), options.origin);
|
||||
|
||||
res.headers['access-control-allow-origin'] =
|
||||
isAllowed ? req.headers.value('origin') : false.toString();
|
||||
|
||||
if (isAllowed) {
|
||||
res.headers['vary'] = 'origin';
|
||||
}
|
||||
}
|
||||
|
||||
if (req.method != 'OPTIONS') return true;
|
||||
res.statusCode = options.successStatus ?? 204;
|
||||
res.contentLength = 0;
|
||||
await res.close();
|
||||
return options.preflightContinue;
|
||||
};
|
||||
}
|
73
packages/cors/lib/src/cors_options.dart
Normal file
73
packages/cors/lib/src/cors_options.dart
Normal file
|
@ -0,0 +1,73 @@
|
|||
/// CORS configuration options.
|
||||
///
|
||||
/// The default configuration is the equivalent of:
|
||||
///
|
||||
///```json
|
||||
///{
|
||||
/// "origin": "*",
|
||||
/// "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
|
||||
/// "preflightContinue": false
|
||||
///}
|
||||
/// ```
|
||||
class CorsOptions {
|
||||
/// Configures the **Access-Control-Allow-Headers** CORS header. Expects a comma-delimited string (ex: 'Content-Type,Authorization') or an array (ex: `['Content-Type', 'Authorization']`). If not specified, defaults to reflecting the headers specified in the request's **Access-Control-Request-Headers** header.
|
||||
final List<String> allowedHeaders = [];
|
||||
|
||||
/// Configures the **Access-Control-Allow-Credentials** CORS header. Set to `true` to pass the header, otherwise it is omitted.
|
||||
final bool credentials;
|
||||
|
||||
/// Configures the **Access-Control-Expose-Headers** CORS header. Expects a comma-delimited string (ex: 'Content-Range,X-Content-Range') or an array (ex: `['Content-Range', 'X-Content-Range']`). If not specified, no custom headers are exposed.
|
||||
final List<String> exposedHeaders = [];
|
||||
|
||||
/// Configures the **Access-Control-Max-Age** CORS header. Set to an integer to pass the header, otherwise it is omitted.
|
||||
///
|
||||
/// Default: `null`
|
||||
final int maxAge;
|
||||
|
||||
/// The status code to be sent on successful `OPTIONS` requests, if [preflightContinue] is `false`.
|
||||
final int successStatus;
|
||||
|
||||
/// Configures the **Access-Control-Allow-Methods** CORS header. Expects a comma-delimited string (ex: 'GET,PUT,POST') or an array (ex: `['GET', 'PUT', 'POST']`).
|
||||
///
|
||||
/// Default: `['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE˝']`
|
||||
final List<String> methods = [];
|
||||
|
||||
/// Configures the **Access-Control-Allow-Origin** CORS header.
|
||||
/// Possible values:
|
||||
/// - `Boolean` - set `origin` to `true` to reflect the [request origin](http://tools.ietf.org/html/draft-abarth-origin-09), as defined by `req.header('Origin')`, or set it to `false` to disable CORS.
|
||||
/// - `String` - set `origin` to a specific origin. For example if you set it to `"http://example.com"` only requests from "http://example.com" will be allowed.
|
||||
/// - `RegExp` - set `origin` to a regular expression pattern which will be used to test the request origin. If it's a match, the request origin will be reflected. For example the pattern `/example\.com$/` will reflect any request that is coming from an origin ending with "example.com".
|
||||
/// - `Array` - set `origin` to an array of valid origins. Each origin can be a `String` or a `RegExp`. For example `["http://example1.com", /\.example2\.com$/]` will accept any request from "http://example1.com" or from a subdomain of "example2.com".
|
||||
/// - `bool Function(String)` - set `origin` to a function implementing some custom logic. The function takes the request origin as the first parameter and returns a [bool].
|
||||
///
|
||||
/// Default: `'*'`
|
||||
final origin;
|
||||
|
||||
/// If `false`, then the [cors] handler will terminate the response after performing its logic.
|
||||
///
|
||||
/// Default: `false`
|
||||
final bool preflightContinue;
|
||||
|
||||
CorsOptions(
|
||||
{Iterable<String> allowedHeaders = const [],
|
||||
this.credentials,
|
||||
this.maxAge,
|
||||
Iterable<String> methods = const [
|
||||
'GET',
|
||||
'HEAD',
|
||||
'PUT',
|
||||
'PATCH',
|
||||
'POST',
|
||||
'DELETE'
|
||||
],
|
||||
this.origin = '*',
|
||||
this.successStatus = 204,
|
||||
this.preflightContinue = false,
|
||||
Iterable<String> exposedHeaders = const []}) {
|
||||
if (allowedHeaders != null) this.allowedHeaders.addAll(allowedHeaders);
|
||||
|
||||
if (methods != null) this.methods.addAll(methods);
|
||||
|
||||
if (exposedHeaders != null) this.exposedHeaders.addAll(exposedHeaders);
|
||||
}
|
||||
}
|
14
packages/cors/pubspec.yaml
Normal file
14
packages/cors/pubspec.yaml
Normal file
|
@ -0,0 +1,14 @@
|
|||
author: Tobe O <thosakwe@gmail.com>
|
||||
description: Angel CORS middleware. Port of expressjs/cors to the Angel framework.
|
||||
environment:
|
||||
sdk: ">=2.0.0 <3.0.0"
|
||||
homepage: https://github.com/angel-dart/cors.git
|
||||
name: angel_cors
|
||||
version: 2.0.0
|
||||
dependencies:
|
||||
angel_framework: ^2.0.0-alpha
|
||||
dev_dependencies:
|
||||
angel_test: ^2.0.0
|
||||
http: ^0.12.0
|
||||
pedantic: ^1.0.0
|
||||
test: ^1.0.0
|
174
packages/cors/test/cors_test.dart
Normal file
174
packages/cors/test/cors_test.dart
Normal file
|
@ -0,0 +1,174 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:angel_cors/angel_cors.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
main() {
|
||||
Angel app;
|
||||
AngelHttp server;
|
||||
http.Client client;
|
||||
|
||||
setUp(() async {
|
||||
app = Angel()
|
||||
..options('/credentials', cors(CorsOptions(credentials: true)))
|
||||
..options('/credentials_d',
|
||||
dynamicCors((req, res) => CorsOptions(credentials: true)))
|
||||
..options(
|
||||
'/headers', cors(CorsOptions(exposedHeaders: ['x-foo', 'x-bar'])))
|
||||
..options('/max_age', cors(CorsOptions(maxAge: 250)))
|
||||
..options('/methods', cors(CorsOptions(methods: ['GET', 'POST'])))
|
||||
..get(
|
||||
'/originl',
|
||||
chain([
|
||||
cors(CorsOptions(
|
||||
origin: ['foo.bar', 'baz.quux'],
|
||||
)),
|
||||
(req, res) => req.headers['origin']
|
||||
]))
|
||||
..get(
|
||||
'/origins',
|
||||
chain([
|
||||
cors(CorsOptions(
|
||||
origin: 'foo.bar',
|
||||
)),
|
||||
(req, res) => req.headers['origin']
|
||||
]))
|
||||
..get(
|
||||
'/originr',
|
||||
chain([
|
||||
cors(CorsOptions(
|
||||
origin: RegExp(r'^foo\.[^x]+$'),
|
||||
)),
|
||||
(req, res) => req.headers['origin']
|
||||
]))
|
||||
..get(
|
||||
'/originp',
|
||||
chain([
|
||||
cors(CorsOptions(
|
||||
origin: (String s) => s.endsWith('.bar'),
|
||||
)),
|
||||
(req, res) => req.headers['origin']
|
||||
]))
|
||||
..options('/status', cors(CorsOptions(successStatus: 418)))
|
||||
..fallback(cors(CorsOptions()))
|
||||
..post('/', (req, res) async {
|
||||
res.write('hello world');
|
||||
})
|
||||
..fallback((req, res) => throw AngelHttpException.notFound());
|
||||
|
||||
server = AngelHttp(app);
|
||||
await server.startServer('127.0.0.1', 0);
|
||||
client = http.Client();
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await server.close();
|
||||
app = null;
|
||||
client = null;
|
||||
});
|
||||
|
||||
test('status 204 by default', () async {
|
||||
var rq = http.Request('OPTIONS', server.uri.replace(path: '/max_age'));
|
||||
var response = await client.send(rq).then(http.Response.fromStream);
|
||||
expect(response.statusCode, 204);
|
||||
});
|
||||
|
||||
test('content length 0 by default', () async {
|
||||
var rq = http.Request('OPTIONS', server.uri.replace(path: '/max_age'));
|
||||
var response = await client.send(rq).then(http.Response.fromStream);
|
||||
expect(response.contentLength, 0);
|
||||
});
|
||||
|
||||
test('custom successStatus', () async {
|
||||
var rq = http.Request('OPTIONS', server.uri.replace(path: '/status'));
|
||||
var response = await client.send(rq).then(http.Response.fromStream);
|
||||
expect(response.statusCode, 418);
|
||||
});
|
||||
|
||||
test('max age', () async {
|
||||
var rq = http.Request('OPTIONS', server.uri.replace(path: '/max_age'));
|
||||
var response = await client.send(rq).then(http.Response.fromStream);
|
||||
expect(response.headers['access-control-max-age'], '250');
|
||||
});
|
||||
|
||||
test('methods', () async {
|
||||
var rq = http.Request('OPTIONS', server.uri.replace(path: '/methods'));
|
||||
var response = await client.send(rq).then(http.Response.fromStream);
|
||||
expect(response.headers['access-control-allow-methods'], 'GET,POST');
|
||||
});
|
||||
|
||||
test('dynamicCors.credentials', () async {
|
||||
var rq =
|
||||
http.Request('OPTIONS', server.uri.replace(path: '/credentials_d'));
|
||||
var response = await client.send(rq).then(http.Response.fromStream);
|
||||
expect(response.headers['access-control-allow-credentials'], 'true');
|
||||
});
|
||||
|
||||
test('credentials', () async {
|
||||
var rq = http.Request('OPTIONS', server.uri.replace(path: '/credentials'));
|
||||
var response = await client.send(rq).then(http.Response.fromStream);
|
||||
expect(response.headers['access-control-allow-credentials'], 'true');
|
||||
});
|
||||
|
||||
test('exposed headers', () async {
|
||||
var rq = http.Request('OPTIONS', server.uri.replace(path: '/headers'));
|
||||
var response = await client.send(rq).then(http.Response.fromStream);
|
||||
expect(response.headers['access-control-expose-headers'], 'x-foo,x-bar');
|
||||
});
|
||||
|
||||
test('invalid origin', () async {
|
||||
var response = await client.get(server.uri.replace(path: '/originl'),
|
||||
headers: {'origin': 'foreign'});
|
||||
expect(response.headers['access-control-allow-origin'], 'false');
|
||||
});
|
||||
|
||||
test('list origin', () async {
|
||||
var response = await client.get(server.uri.replace(path: '/originl'),
|
||||
headers: {'origin': 'foo.bar'});
|
||||
expect(response.headers['access-control-allow-origin'], 'foo.bar');
|
||||
expect(response.headers['vary'], 'origin');
|
||||
response = await client.get(server.uri.replace(path: '/originl'),
|
||||
headers: {'origin': 'baz.quux'});
|
||||
expect(response.headers['access-control-allow-origin'], 'baz.quux');
|
||||
expect(response.headers['vary'], 'origin');
|
||||
});
|
||||
|
||||
test('string origin', () async {
|
||||
var response = await client.get(server.uri.replace(path: '/origins'),
|
||||
headers: {'origin': 'foo.bar'});
|
||||
expect(response.headers['access-control-allow-origin'], 'foo.bar');
|
||||
expect(response.headers['vary'], 'origin');
|
||||
});
|
||||
|
||||
test('regex origin', () async {
|
||||
var response = await client.get(server.uri.replace(path: '/originr'),
|
||||
headers: {'origin': 'foo.bar'});
|
||||
expect(response.headers['access-control-allow-origin'], 'foo.bar');
|
||||
expect(response.headers['vary'], 'origin');
|
||||
});
|
||||
|
||||
test('predicate origin', () async {
|
||||
var response = await client.get(server.uri.replace(path: '/originp'),
|
||||
headers: {'origin': 'foo.bar'});
|
||||
expect(response.headers['access-control-allow-origin'], 'foo.bar');
|
||||
expect(response.headers['vary'], 'origin');
|
||||
});
|
||||
|
||||
test('POST works', () async {
|
||||
final response = await client.post(server.uri);
|
||||
expect(response.statusCode, equals(200));
|
||||
print('Response: ${response.body}');
|
||||
print('Headers: ${response.headers}');
|
||||
expect(response.headers['access-control-allow-origin'], equals('*'));
|
||||
});
|
||||
|
||||
test('mirror headers', () async {
|
||||
final response = await client
|
||||
.post(server.uri, headers: {'access-control-request-headers': 'foo'});
|
||||
expect(response.statusCode, equals(200));
|
||||
print('Response: ${response.body}');
|
||||
print('Headers: ${response.headers}');
|
||||
expect(response.headers['access-control-allow-headers'], equals('foo'));
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue