Failing, but...
This commit is contained in:
parent
194c74130b
commit
2137470d79
7 changed files with 209 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,6 +5,7 @@
|
|||
.packages
|
||||
.project
|
||||
.pub/
|
||||
.scripts-bin/
|
||||
build/
|
||||
**/packages/
|
||||
|
||||
|
|
1
.travis.yml
Normal file
1
.travis.yml
Normal file
|
@ -0,0 +1 @@
|
|||
language: dart
|
12
README.md
12
README.md
|
@ -1,2 +1,14 @@
|
|||
# cors
|
||||
|
||||

|
||||

|
||||
|
||||
Angel CORS middleware.
|
||||
Port of [the original Express CORS middleware](https://github.com/expressjs/cors).
|
||||
|
||||
```dart
|
||||
main() {
|
||||
var app = new Angel();
|
||||
app.before.add(cors());
|
||||
}
|
||||
```
|
77
lib/angel_cors.dart
Normal file
77
lib/angel_cors.dart
Normal file
|
@ -0,0 +1,77 @@
|
|||
/// Angel CORS middleware.
|
||||
library angel_cors;
|
||||
|
||||
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) {
|
||||
if (allowedOrigin is List) {
|
||||
return allowedOrigin.any((x) => _isOriginAllowed(origin, x));
|
||||
} else if (allowedOrigin is String) {
|
||||
return origin == allowedOrigin;
|
||||
} else if (allowedOrigin is RegExp) {
|
||||
return allowedOrigin.hasMatch(origin);
|
||||
} else if (allowedOrigin is CorsFilter) {
|
||||
return allowedOrigin(origin);
|
||||
} else {
|
||||
return allowedOrigin != false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies the given [CorsOptions].
|
||||
RequestMiddleware cors([CorsOptions options]) {
|
||||
final opts = options ?? new CorsOptions();
|
||||
|
||||
return (RequestContext req, ResponseContext res) async {
|
||||
// Access-Control-Allow-Credentials
|
||||
if (opts.credentials == true) {
|
||||
res.header('Access-Control-Allow-Credentials', 'true');
|
||||
}
|
||||
|
||||
// Access-Control-Allow-Headers
|
||||
if (opts.allowedHeaders.isNotEmpty) {
|
||||
res.header('Access-Control-Allow-Headers', opts.allowedHeaders.join(','));
|
||||
}
|
||||
|
||||
// Access-Control-Expose-Headers
|
||||
if (opts.exposedHeaders.isNotEmpty) {
|
||||
res.header(
|
||||
'Access-Control-Expose-Headers', opts.exposedHeaders.join(','));
|
||||
}
|
||||
|
||||
// Access-Control-Allow-Methods
|
||||
if (opts.methods.isNotEmpty) {
|
||||
res.header('Access-Control-Allow-Methods', opts.methods.join(','));
|
||||
}
|
||||
|
||||
// Access-Control-Max-Age
|
||||
if (opts.maxAge != null) {
|
||||
res.header('Access-Control-Max-Age', opts.maxAge.toString());
|
||||
}
|
||||
|
||||
// Access-Control-Allow-Origin
|
||||
if (opts.origin == false || opts.origin == '*') {
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
} else if (opts.origin is String) {
|
||||
res
|
||||
..header('Access-Control-Allow-Origin', opts.origin)
|
||||
..header('Vary', 'Origin');
|
||||
} else {
|
||||
bool isAllowed =
|
||||
_isOriginAllowed(req.headers.value('Origin'), opts.origin);
|
||||
|
||||
res.header('Access-Control-Allow-Origin',
|
||||
isAllowed ? req.headers.value('Origin') : false.toString());
|
||||
|
||||
if (isAllowed) {
|
||||
res.header('Vary', 'Origin');
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
69
lib/src/cors_options.dart
Normal file
69
lib/src/cors_options.dart
Normal file
|
@ -0,0 +1,69 @@
|
|||
/// 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.
|
||||
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`
|
||||
int maxAge = null;
|
||||
|
||||
/// 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".
|
||||
/// - `Function` - set `origin` to a function implementing some custom logic. The function takes the request origin as the first parameter and a callback (which expects the signature `err [object], allow [bool]`) as the second.
|
||||
///
|
||||
/// Default: `'*'`
|
||||
var origin;
|
||||
|
||||
/// Pass the CORS preflight response to the next handler.
|
||||
///
|
||||
/// Default: `false`
|
||||
bool preflightContinue;
|
||||
|
||||
CorsOptions(
|
||||
{List<String> allowedHeaders: const [],
|
||||
this.credentials,
|
||||
this.maxAge,
|
||||
List<String> methods: const [
|
||||
'GET',
|
||||
'HEAD',
|
||||
'PUT',
|
||||
'PATCH',
|
||||
'POST',
|
||||
'DELETE˝'
|
||||
],
|
||||
this.origin: '*',
|
||||
this.preflightContinue: false,
|
||||
List<String> exposedHeaders: const []}) {
|
||||
if (allowedHeaders != null) this.allowedHeaders.addAll(allowedHeaders);
|
||||
|
||||
if (methods != null) this.methods.addAll(methods);
|
||||
|
||||
if (exposedHeaders != null) this.exposedHeaders.addAll(exposedHeaders);
|
||||
}
|
||||
}
|
10
pubspec.yaml
Normal file
10
pubspec.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
author: "Tobe O <thosakwe@gmail.com>"
|
||||
description: "Angel CORS middleware."
|
||||
homepage: "https://github.com/angel-dart/cors.git"
|
||||
name: "angel_cors"
|
||||
version: "1.0.0-dev"
|
||||
dependencies:
|
||||
angel_framework: "^1.0.0-dev.28"
|
||||
dev_dependencies:
|
||||
http: "^0.11.3+9"
|
||||
test: "^0.12.17"
|
39
test/basic_test.dart
Normal file
39
test/basic_test.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
import 'dart:io';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_cors/angel_cors.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
main() {
|
||||
Angel app;
|
||||
http.Client client;
|
||||
HttpServer server;
|
||||
String url;
|
||||
|
||||
setUp(() async {
|
||||
app = new Angel()
|
||||
..before.add(cors())
|
||||
..post('/', (req, res) async {
|
||||
return res
|
||||
..write('hello world')
|
||||
..end();
|
||||
});
|
||||
|
||||
server = await app.startServer();
|
||||
url = 'http://${server.address.address}:${server.port}';
|
||||
client = new http.Client();
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await server.close(force: true);
|
||||
app = null;
|
||||
client = null;
|
||||
url = null;
|
||||
});
|
||||
|
||||
test('POST works', () async {
|
||||
final response = await client.post(url);
|
||||
expect(response.statusCode, equals(200));
|
||||
expect(response.headers['access-control-allow-origin'], equals('*'));
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue