Skip to content

Commit

Permalink
Merge pull request #2 from koajs/spec-compliant
Browse files Browse the repository at this point in the history
fix: make more spec-compliant
  • Loading branch information
fengmk2 committed Feb 11, 2015
2 parents b84fdb5 + 7aa03fd commit bc1c828
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 87 deletions.
84 changes: 52 additions & 32 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var copy = require('copy-to');
* CORS middleware
*
* @param {Object} [options]
* - {String|Function(ctx)} origin `Access-Control-Allow-Origin`, default is '*'
* - {String|Function(ctx)} origin `Access-Control-Allow-Origin`, default is request Origin header
* - {String|Array} allowMethods `Access-Control-Allow-Methods`, default is 'GET,HEAD,PUT,POST,DELETE'
* - {String|Array} exposeHeaders `Access-Control-Expose-Headers`
* - {String|Array} allowHeaders `Access-Control-Allow-Headers`
Expand All @@ -27,9 +27,6 @@ var copy = require('copy-to');
*/
module.exports = function (options) {
var defaults = {
origin: function (ctx) {
return ctx.get('origin') || '*';
},
allowMethods: 'GET,HEAD,PUT,POST,DELETE'
};

Expand All @@ -55,47 +52,70 @@ module.exports = function (options) {
options.credentials = !!options.credentials;

return function* cors(next) {
var origin = options.origin;
if (origin === false) {
// If the Origin header is not present terminate this set of steps. The request is outside the scope of this specification.
var requestOrigin = this.get('Origin');
if (!requestOrigin) {
return yield* next;
}

var origin;
if (typeof options.origin === 'function') {
origin = options.origin(this);
if (!origin) {
return yield* next;
}
} else {
origin = options.origin || requestOrigin;
}

if (origin === false) {
return yield* next;
}

this.set('Access-Control-Allow-Origin', origin);
if (this.method !== 'OPTIONS') {
// Simple Cross-Origin Request, Actual Request, and Redirects

if (options.exposeHeaders) {
this.set('Access-Control-Expose-Headers', options.exposeHeaders);
}

if (options.maxAge) {
this.set('Access-Control-Max-Age', options.maxAge);
}
this.set('Access-Control-Allow-Origin', origin);

if (options.credentials === true) {
this.set('Access-Control-Allow-Credentials', 'true');
}
if (options.credentials === true) {
this.set('Access-Control-Allow-Credentials', 'true');
}

this.set('Access-Control-Allow-Methods', options.allowMethods);
if (options.exposeHeaders) {
this.set('Access-Control-Expose-Headers', options.exposeHeaders);
}

var allowHeaders = options.allowHeaders;
if (!allowHeaders) {
allowHeaders = this.get('access-control-request-headers');
}
if (allowHeaders) {
this.set('Access-Control-Allow-Headers', allowHeaders);
}
yield* next;
} else {
// Preflight Request

// If there is no Access-Control-Request-Method header or if parsing failed,
// do not set any additional headers and terminate this set of steps.
// The request is outside the scope of this specification.
if (!this.get('Access-Control-Request-Method')) {
// this not preflight request, ignore it
return yield* next;
}

this.set('Access-Control-Allow-Origin', origin);

if (options.credentials === true) {
this.set('Access-Control-Allow-Credentials', 'true');
}

if (options.maxAge) {
this.set('Access-Control-Max-Age', options.maxAge);
}

if (options.allowMethods) {
this.set('Access-Control-Allow-Methods', options.allowMethods);
}

var allowHeaders = options.allowHeaders;
if (!allowHeaders) {
allowHeaders = this.get('Access-Control-Request-Headers');
}
if (allowHeaders) {
this.set('Access-Control-Allow-Headers', allowHeaders);
}

if (this.method === 'OPTIONS') {
this.status = 204;
} else {
yield* next;
}
};
};
156 changes: 101 additions & 55 deletions test/cors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ describe('cors.test.js', function () {
this.body = {foo: 'bar'};
});

it('should set `Access-Control-Allow-Origin` to * and `Access-Control-Allow-Methods`', function (done) {
it('should not set `Access-Control-Allow-Origin` when request Origin header missing', function (done) {
request(app.listen())
.get('/')
.expect('Access-Control-Allow-Origin', '*')
.expect('Access-Control-Allow-Methods', 'GET,HEAD,PUT,POST,DELETE')
.expect({foo: 'bar'})
.expect(200, done);
.expect(200, function (err, res) {
assert(!err);
assert(!res.headers['access-control-allow-origin']);
done();
});
});

it('should set `Access-Control-Allow-Origin` to request origin header', function (done) {
Expand All @@ -45,58 +47,39 @@ describe('cors.test.js', function () {
.expect(200, done);
});

it('should 204 on OPTIONS request', function (done) {
it('should 204 on Preflight Request', function (done) {
request(app.listen())
.options('/')
.expect('Access-Control-Allow-Origin', '*')
.set('Origin', 'http://koajs.com')
.set('Access-Control-Request-Method', 'PUT')
.expect('Access-Control-Allow-Origin', 'http://koajs.com')
.expect('Access-Control-Allow-Methods', 'GET,HEAD,PUT,POST,DELETE')
.expect(204, done);
});
});

describe('options.origin=*', function () {
var app = koa();
app.use(cors({
origin: '*'
}));
app.use(function* () {
this.body = {foo: 'bar'};
});

it('should always set `Access-Control-Allow-Origin` to *', function (done) {
it('should not Preflight Request if request missing Access-Control-Request-Method', function (done) {
request(app.listen())
.get('/')
.options('/')
.set('Origin', 'http://koajs.com')
.expect('Access-Control-Allow-Origin', '*')
.expect({foo: 'bar'})
.expect(200, done);
});
});

describe('options.origin=false', function () {
describe('options.origin=*', function () {
var app = koa();
app.use(cors({
origin: false
origin: '*'
}));
app.use(function* () {
this.body = {foo: 'bar'};
});

it('should disable cors', function (done) {
it('should always set `Access-Control-Allow-Origin` to *', function (done) {
request(app.listen())
.get('/')
.set('Origin', 'http://koajs.com')
.expect('Access-Control-Allow-Origin', '*')
.expect({foo: 'bar'})
.expect(200, function (err, res) {
assert(!err);
assert(!res.headers['access-control-allow-origin']);
done();
});
});

it('should not handle OPTIONS request', function (done) {
request(app.listen())
.options('/')
.expect(200, done);
});
});
Expand Down Expand Up @@ -126,6 +109,15 @@ describe('cors.test.js', function () {
done();
});
});

it('should set access-control-allow-origin to *', function (done) {
request(app.listen())
.get('/')
.set('Origin', 'http://koajs.com')
.expect({foo: 'bar'})
.expect('Access-Control-Allow-Origin', '*')
.expect(200, done);
});
});

describe('options.exposeHeaders', function () {
Expand All @@ -140,6 +132,7 @@ describe('cors.test.js', function () {

request(app.listen())
.get('/')
.set('Origin', 'http://koajs.com')
.expect('Access-Control-Expose-Headers', 'content-length')
.expect({foo: 'bar'})
.expect(200, done);
Expand All @@ -156,6 +149,7 @@ describe('cors.test.js', function () {

request(app.listen())
.get('/')
.set('Origin', 'http://koajs.com')
.expect('Access-Control-Expose-Headers', 'content-length,x-header')
.expect({foo: 'bar'})
.expect(200, done);
Expand All @@ -173,10 +167,11 @@ describe('cors.test.js', function () {
});

request(app.listen())
.get('/')
.options('/')
.set('Origin', 'http://koajs.com')
.set('Access-Control-Request-Method', 'PUT')
.expect('Access-Control-Max-Age', '3600')
.expect({foo: 'bar'})
.expect(200, done);
.expect(204, done);
});

it('should set maxAge with string', function (done) {
Expand All @@ -189,29 +184,60 @@ describe('cors.test.js', function () {
});

request(app.listen())
.get('/')
.options('/')
.set('Origin', 'http://koajs.com')
.set('Access-Control-Request-Method', 'PUT')
.expect('Access-Control-Max-Age', '3600')
.expect({foo: 'bar'})
.expect(200, done);
.expect(204, done);
});
});

describe('options.credentials', function () {
it('should enable Access-Control-Allow-Credentials', function (done) {
it('should not set maxAge on simple request', function (done) {
var app = koa();
app.use(cors({
credentials: true
maxAge: '3600'
}));
app.use(function* () {
this.body = {foo: 'bar'};
});

request(app.listen())
.get('/')
.set('Origin', 'http://koajs.com')
.expect({foo: 'bar'})
.expect(200, function (err, res) {
assert(!err);
assert(!res.headers['access-control-max-age']);
done();
});
});
});

describe('options.credentials', function () {
var app = koa();
app.use(cors({
credentials: true
}));
app.use(function* () {
this.body = {foo: 'bar'};
});

it('should enable Access-Control-Allow-Credentials on Simple request', function (done) {
request(app.listen())
.get('/')
.set('Origin', 'http://koajs.com')
.expect('Access-Control-Allow-Credentials', 'true')
.expect({foo: 'bar'})
.expect(200, done);
});

it('should enable Access-Control-Allow-Credentials on Preflight request', function (done) {
request(app.listen())
.options('/')
.set('Origin', 'http://koajs.com')
.set('Access-Control-Request-Method', 'DELETE')
.expect('Access-Control-Allow-Credentials', 'true')
.expect(204, done);
});
});

describe('options.allowHeaders', function () {
Expand All @@ -225,10 +251,11 @@ describe('cors.test.js', function () {
});

request(app.listen())
.get('/')
.options('/')
.set('Origin', 'http://koajs.com')
.set('Access-Control-Request-Method', 'PUT')
.expect('Access-Control-Allow-Headers', 'X-PINGOTHER')
.expect({foo: 'bar'})
.expect(200, done);
.expect(204, done);
});

it('should work with allowHeaders is array', function (done) {
Expand All @@ -241,10 +268,11 @@ describe('cors.test.js', function () {
});

request(app.listen())
.get('/')
.options('/')
.set('Origin', 'http://koajs.com')
.set('Access-Control-Request-Method', 'PUT')
.expect('Access-Control-Allow-Headers', 'X-PINGOTHER')
.expect({foo: 'bar'})
.expect(200, done);
.expect(204, done);
});

it('should set Access-Control-Allow-Headers to request access-control-request-headers header', function (done) {
Expand All @@ -255,11 +283,12 @@ describe('cors.test.js', function () {
});

request(app.listen())
.get('/')
.options('/')
.set('Origin', 'http://koajs.com')
.set('Access-Control-Request-Method', 'PUT')
.set('access-control-request-headers', 'X-PINGOTHER')
.expect('Access-Control-Allow-Headers', 'X-PINGOTHER')
.expect({foo: 'bar'})
.expect(200, done);
.expect(204, done);
});
});

Expand All @@ -274,10 +303,27 @@ describe('cors.test.js', function () {
});

request(app.listen())
.get('/')
.options('/')
.set('Origin', 'http://koajs.com')
.set('Access-Control-Request-Method', 'PUT')
.expect('Access-Control-Allow-Methods', 'GET,POST')
.expect({foo: 'bar'})
.expect(200, done);
.expect(204, done);
});

it('should skip allowMethods', function (done) {
var app = koa();
app.use(cors({
allowMethods: null
}));
app.use(function* () {
this.body = {foo: 'bar'};
});

request(app.listen())
.options('/')
.set('Origin', 'http://koajs.com')
.set('Access-Control-Request-Method', 'PUT')
.expect(204, done);
});
});
});

0 comments on commit bc1c828

Please sign in to comment.