mirror of
https://github.com/wekan/wekan.git
synced 2025-12-18 00:10:13 +01:00
Fixed Non-ASCII attachment filename will crash when downloading.
Thanks to xet7 ! Fixes #2759
This commit is contained in:
parent
843ff8eaaa
commit
c2da477735
277 changed files with 30568 additions and 52 deletions
18
packages/wekan-cfs-http-methods/.editorconfig
Normal file
18
packages/wekan-cfs-http-methods/.editorconfig
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# .editorconfig
|
||||
# Meteor adapted EditorConfig, http://EditorConfig.org
|
||||
# By RaiX 2013
|
||||
|
||||
root = true
|
||||
|
||||
[*.js]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
max_line_length = 80
|
||||
indent_brace_style = 1TBS
|
||||
spaces_around_operators = true
|
||||
quote_type = auto
|
||||
# curly_bracket_next_line = true
|
||||
2
packages/wekan-cfs-http-methods/.gitignore
vendored
Normal file
2
packages/wekan-cfs-http-methods/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.build*
|
||||
/.versions
|
||||
140
packages/wekan-cfs-http-methods/.jshintrc
Normal file
140
packages/wekan-cfs-http-methods/.jshintrc
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
{
|
||||
// JSHint Meteor Configuration File
|
||||
// Match the Meteor Style Guide
|
||||
//
|
||||
// By @raix with contributions from @aldeed and @awatson1978
|
||||
// Source https://github.com/raix/Meteor-jshintrc
|
||||
//
|
||||
// See http://jshint.com/docs/ for more details
|
||||
|
||||
"maxerr" : 50, // {int} Maximum error before stopping
|
||||
|
||||
// Enforcing
|
||||
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
|
||||
"camelcase" : false, // false: Identifiers do not need to be in camelCase. We would like to enforce this except for when interacting with our api objects which use snakeCase properties.
|
||||
"curly" : false, // false: Do not require {} for every new block or scope
|
||||
"eqeqeq" : true, // true: Require triple equals (===) for comparison
|
||||
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
|
||||
"immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
|
||||
"indent" : 2, // {int} Number of spaces to use for indentation
|
||||
"latedef" : false, // true: Require variables/functions to be defined before being used
|
||||
"newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()`
|
||||
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
|
||||
"noempty" : true, // true: Prohibit use of empty blocks
|
||||
"nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment)
|
||||
"plusplus" : false, // true: Prohibit use of `++` & `--`
|
||||
"quotmark" : false, // Quotation mark consistency:
|
||||
// false : do nothing (default)
|
||||
// true : ensure whatever is used is consistent
|
||||
// "single" : require single quotes
|
||||
// "double" : require double quotes
|
||||
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
|
||||
"unused" : true, // true: Require all defined variables be used
|
||||
"strict" : false, // false: Do not require all functions to be run in ES5 Strict Mode
|
||||
"trailing" : true, // true: Prohibit trailing whitespaces
|
||||
"maxparams" : false, // {int} Max number of formal params allowed per function
|
||||
"maxdepth" : false, // {int} Max depth of nested blocks (within functions)
|
||||
"maxstatements" : false, // {int} Max number statements per function
|
||||
"maxcomplexity" : false, // {int} Max cyclomatic complexity per function
|
||||
"maxlen" : 250, // {int} Max number of characters per line
|
||||
|
||||
// Relaxing
|
||||
"asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
|
||||
"boss" : false, // true: Tolerate assignments where comparisons would be expected
|
||||
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
|
||||
"eqnull" : false, // true: Tolerate use of `== null`
|
||||
"es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
|
||||
"esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
|
||||
"moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
|
||||
// (ex: `for each`, multiple try/catch, function expression…)
|
||||
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
|
||||
"expr" : true, // true: Tolerate `ExpressionStatement` as Programs
|
||||
"funcscope" : false, // true: Tolerate defining variables inside control statements"
|
||||
"globalstrict" : true, // true: Allow global "use strict" (also enables 'strict')
|
||||
"iterator" : false, // true: Tolerate using the `__iterator__` property
|
||||
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
|
||||
"laxbreak" : false, // true: Tolerate possibly unsafe line breakings
|
||||
"laxcomma" : false, // true: Tolerate comma-first style coding
|
||||
"loopfunc" : false, // true: Tolerate functions being defined in loops
|
||||
"multistr" : false, // true: Tolerate multi-line strings
|
||||
"proto" : false, // true: Tolerate using the `__proto__` property
|
||||
"scripturl" : false, // true: Tolerate script-targeted URLs
|
||||
"smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment
|
||||
"shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
|
||||
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
|
||||
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
|
||||
"validthis" : false, // true: Tolerate using this in a non-constructor function
|
||||
|
||||
// Environments
|
||||
"browser" : true, // Web Browser (window, document, etc)
|
||||
"couch" : false, // CouchDB
|
||||
"devel" : true, // Development/debugging (alert, confirm, etc)
|
||||
"dojo" : false, // Dojo Toolkit
|
||||
"jquery" : false, // jQuery
|
||||
"mootools" : false, // MooTools
|
||||
"node" : false, // Node.js
|
||||
"nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
|
||||
"prototypejs" : false, // Prototype and Scriptaculous
|
||||
"rhino" : false, // Rhino
|
||||
"worker" : false, // Web Workers
|
||||
"wsh" : false, // Windows Scripting Host
|
||||
"yui" : false, // Yahoo User Interface
|
||||
//"meteor" : false, // Meteor.js
|
||||
|
||||
// Legacy
|
||||
"nomen" : false, // true: Prohibit dangling `_` in variables
|
||||
"onevar" : false, // true: Allow only one `var` statement per function
|
||||
"passfail" : false, // true: Stop on first error
|
||||
"white" : false, // true: Check against strict whitespace and indentation rules
|
||||
|
||||
// Custom globals, from http://docs.meteor.com, in the order they appear there
|
||||
"globals" : {
|
||||
"Meteor": false,
|
||||
"DDP": false,
|
||||
"Mongo": false,
|
||||
"Session": false,
|
||||
"Accounts": false,
|
||||
"Template": false,
|
||||
"Blaze": false,
|
||||
"Spacebars": false,
|
||||
"Match": false,
|
||||
"check": false,
|
||||
"Tracker": false,
|
||||
"ReactiveVar": false,
|
||||
"ReactiveDict": false,
|
||||
"EJSON": false,
|
||||
"HTTP": false,
|
||||
"Email": false,
|
||||
"Assets": false,
|
||||
"Package": false,
|
||||
"App": false, //mobile-config.js
|
||||
"cordova": false,
|
||||
"Cordova": false,
|
||||
"Buffer": false,
|
||||
|
||||
// Meteor internals
|
||||
"DDPServer": false,
|
||||
"global": false,
|
||||
"Log": false,
|
||||
"MongoInternals": false,
|
||||
"process": false,
|
||||
"Retry": false,
|
||||
"WebApp": false,
|
||||
"WebAppInternals": false,
|
||||
|
||||
// globals useful when creating Meteor packages
|
||||
"Npm": false,
|
||||
"Tinytest": false,
|
||||
|
||||
// common Meteor packages
|
||||
"HTTP": true,
|
||||
"Random": false,
|
||||
"_": false, // Underscore.js
|
||||
"$": false, // jQuery
|
||||
|
||||
// This package
|
||||
"_methodHTTP": true,
|
||||
"Fiber": true,
|
||||
"runServerMethod": true
|
||||
}
|
||||
}
|
||||
5
packages/wekan-cfs-http-methods/.travis.yml
Normal file
5
packages/wekan-cfs-http-methods/.travis.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
before_install:
|
||||
- "curl -L http://git.io/s0Zu-w | /bin/sh"
|
||||
152
packages/wekan-cfs-http-methods/CHANGELOG.md
Normal file
152
packages/wekan-cfs-http-methods/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
# Changelog
|
||||
|
||||
## vCurrent
|
||||
|
||||
## [v0.0.30]
|
||||
#### 9/9/15 by Eric Dobbertin
|
||||
- Ensure we do not try to end responses twice
|
||||
|
||||
## [v0.0.29]
|
||||
#### 29/4/15 by Eric Dobbertin
|
||||
- Respond properly to 206 Range requests (thanks [cherbst](https://github.com/cherbst))
|
||||
|
||||
## [v0.0.28]
|
||||
#### 8/4/15 by Eric Dobbertin
|
||||
- Fix issue where response would not be sent when using streams
|
||||
|
||||
## [v0.0.26] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.26)
|
||||
#### 17/12/14 by Morten Henriksen
|
||||
- mbr update, remove versions.json
|
||||
|
||||
## [v0.0.25] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.25)
|
||||
#### 17/12/14 by Morten Henriksen
|
||||
- mbr update versions and fix warnings
|
||||
|
||||
- *Merged pull-request:* "- Added link to documentation about usage of HTTP client-side" [#22](https://github.com/zcfs/Meteor-http-methods/issues/22) ([jkaan](https://github.com/jkaan))
|
||||
|
||||
- *Merged pull-request:* "Typos." [#23](https://github.com/zcfs/Meteor-http-methods/issues/23) ([bradvogel](https://github.com/bradvogel))
|
||||
|
||||
- - Added link to documentation about usage of HTTP client-side
|
||||
|
||||
- *Merged pull-request:* "typo in example: this.params instead of this.param" [#21](https://github.com/zcfs/Meteor-http-methods/issues/21) ([lukasvan3l](https://github.com/lukasvan3l))
|
||||
|
||||
- typo in example: this.params instead of this.param
|
||||
|
||||
- *Merged pull-request:* ""code" is not always there" [#20](https://github.com/zcfs/Meteor-http-methods/issues/20) ([lukasvan3l](https://github.com/lukasvan3l))
|
||||
|
||||
- code is not always there
|
||||
|
||||
- 0.9.1 support
|
||||
|
||||
Patches by GitHub users [@jkaan](https://github.com/jkaan), [@bradvogel](https://github.com/bradvogel), [@lukasvan3l](https://github.com/lukasvan3l).
|
||||
|
||||
## [v0.0.23] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.23)
|
||||
#### 25/08/14 by Morten Henriksen
|
||||
- *Fixed bug:* "Cannot transfer a file stream over 5Mb to the server." [#17](https://github.com/zcfs/Meteor-http-methods/issues/17)
|
||||
|
||||
- *Merged pull-request:* "Add this.requestHeaders to readme" [#12](https://github.com/zcfs/Meteor-http-methods/issues/12) ([matthewsimo](https://github.com/matthewsimo))
|
||||
|
||||
- Add this.requestHeaders to readme
|
||||
|
||||
Patches by GitHub user [@matthewsimo](https://github.com/matthewsimo).
|
||||
|
||||
## [v0.0.22] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.22)
|
||||
#### 25/03/14 by Morten Henriksen
|
||||
- *Merged pull-request:* "Typo fixes" [#11](https://github.com/zcfs/Meteor-http-methods/issues/11) ([dandv](https://github.com/dandv))
|
||||
|
||||
- *Merged pull-request:* "Cosmetic fixes" [#10](https://github.com/zcfs/Meteor-http-methods/issues/10) ([dandv](https://github.com/dandv))
|
||||
|
||||
- Typo fixes
|
||||
|
||||
- Cosmetic fixes
|
||||
|
||||
Patches by GitHub user [@dandv](https://github.com/dandv).
|
||||
|
||||
## [v0.0.21] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.21)
|
||||
#### 23/03/14 by Morten Henriksen
|
||||
## [v0.0.20] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.20)
|
||||
#### 22/03/14 by Morten Henriksen
|
||||
- add check
|
||||
|
||||
- use collectionFS travis version force update
|
||||
|
||||
## [v0.0.19] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.19)
|
||||
#### 22/03/14 by Morten Henriksen
|
||||
- Added ability for throwing http errors
|
||||
|
||||
## [v0.0.18] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.18)
|
||||
#### 18/03/14 by Morten Henriksen
|
||||
- support HEAD requests
|
||||
|
||||
- Add better streaming api and fix timeout / chunk size bug
|
||||
|
||||
- refactor + added read stream
|
||||
|
||||
- Add streaming WIP
|
||||
|
||||
- Name tests by package
|
||||
|
||||
- unbreak http-publish tests when run at in one test
|
||||
|
||||
## [v0.0.17] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.17)
|
||||
#### 22/02/14 by Morten Henriksen
|
||||
- *Fixed bug:* "Binary use currently breaks json usage of data" [#6](https://github.com/zcfs/Meteor-http-methods/issues/6)
|
||||
|
||||
## [v0.0.16] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.16)
|
||||
#### 22/02/14 by Morten Henriksen
|
||||
- Fix test - params from a query string is string now
|
||||
|
||||
- make sure falsy turns into string
|
||||
|
||||
## [v0.0.15] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.15)
|
||||
#### 21/02/14 by Eric Dobbertin
|
||||
## [v0.0.14] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.14)
|
||||
#### 20/02/14 by Eric Dobbertin
|
||||
- support binary data from the request
|
||||
|
||||
## [v0.0.13] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.13)
|
||||
#### 17/02/14 by Morten Henriksen
|
||||
- Merge branch 'master' of https://github.com/zcfs/Meteor-http-methods
|
||||
|
||||
- Bump to version 0.0.12
|
||||
|
||||
## [v0.0.12] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.12)
|
||||
#### 17/02/14 by Eric Dobbertin
|
||||
- Add requestHeaders to method context
|
||||
|
||||
- Better handling of Meteor.Error's
|
||||
|
||||
- url decode values in params
|
||||
|
||||
- Allow user to set headers in custom response
|
||||
|
||||
## [v0.0.11] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.11)
|
||||
#### 08/12/13 by Morten Henriksen
|
||||
## [v0.0.10] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.10)
|
||||
#### 14/11/13 by Morten Henriksen
|
||||
## [v0.0.9] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.9)
|
||||
#### 30/09/13 by Morten Henriksen
|
||||
- Add MIT License
|
||||
|
||||
## [v0.0.8] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.8)
|
||||
#### 28/09/13 by Morten Henriksen
|
||||
- Comment feature - format selector as extension in url
|
||||
|
||||
## [v0.0.7] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.7)
|
||||
#### 18/09/13 by Morten Henriksen
|
||||
## [v0.0.6, tag: v0.0.5] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.6, tag: v0.0.5)
|
||||
#### 18/09/13 by Morten Henriksen
|
||||
- Added more features
|
||||
|
||||
## [v0.0.4] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.4)
|
||||
#### 13/09/13 by Morten Henriksen
|
||||
## [v0.0.3] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.3)
|
||||
#### 12/09/13 by Morten Henriksen
|
||||
## [v0.0.2] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.2)
|
||||
#### 12/09/13 by Morten Henriksen
|
||||
- *Fixed bug:* "allow empty last value in url" [#1](https://github.com/zcfs/Meteor-http-methods/issues/1)
|
||||
|
||||
## [v0.0.1] (https://github.com/zcfs/Meteor-http-methods/tree/v0.0.1)
|
||||
#### 12/09/13 by Morten Henriksen
|
||||
- Init commit
|
||||
|
||||
20
packages/wekan-cfs-http-methods/LICENSE.md
Normal file
20
packages/wekan-cfs-http-methods/LICENSE.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 [@raix](https://github.com/raix), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com
|
||||
|
||||
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.
|
||||
215
packages/wekan-cfs-http-methods/README.md
Normal file
215
packages/wekan-cfs-http-methods/README.md
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
wekan-cfs-http-methods [](https://travis-ci.org/CollectionFS/Meteor-http-methods)
|
||||
============
|
||||
|
||||
~~Looking for maintainers - please reach out!~~
|
||||
This package is to be archived due to inability to find contributors, thanks to everyone who helped make it possible.
|
||||
|
||||
**If you're looking for an alternative, we highly recommend [Meteor-Files](https://github.com/VeliovGroup/Meteor-Files) by [VeliovGroup](https://github.com/VeliovGroup)**
|
||||
|
||||
---
|
||||
|
||||
Add server-side methods to the `HTTP` object your app. It's a server-side package only *- no client simulations added.*
|
||||
|
||||
## Usage
|
||||
|
||||
The `HTTP` object gets a `methods` method:
|
||||
|
||||
```js
|
||||
HTTP.methods({
|
||||
'list': function() {
|
||||
return '<b>Default content type is text/html</b>';
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Methods scope
|
||||
The methods scope contains different kinds of inputs. We can also get user details if logged in.
|
||||
|
||||
|
||||
* `this.userId` - the user whose id and token was used to run this method, if set/found
|
||||
* `this.method` - `GET`, `POST`, `PUT`, `DELETE`
|
||||
* `this.query` - query params `?token=1&id=2` -> `{ token: 1, id: 2 }`
|
||||
* `this.params` - set params `/foo/:name/test/:id` -> `{ name: '', id: '' }`
|
||||
* `this.userAgent` - get the user agent string set in the request header
|
||||
* `this.requestHeaders` - request headers object
|
||||
* `this.setUserId(id)` - option for setting the `this.userId`
|
||||
* `this.isSimulation` - always false on the server
|
||||
* `this.unblock` - not implemented
|
||||
* `this.setContentType('text/html')` - set the content type in header, defaults to `text/html`
|
||||
* `this.addHeader('Content-Disposition', 'attachment; filename="name.ext"')`
|
||||
* `this.setStatusCode(200)` - set the status code in the response header
|
||||
* `createReadStream` - if a request, then get the read stream
|
||||
* `createWriteStream` - if you want to stream data to the client
|
||||
* `Error` - when streaming we have to be able to send error and close connection
|
||||
* `this.request` The original request object
|
||||
|
||||
## Passing data via header
|
||||
|
||||
From the client:
|
||||
```js
|
||||
HTTP.post('list', {
|
||||
data: { foo: 'bar' }
|
||||
}, function(err, result) {
|
||||
console.log('Content: ' + result.content + ' === "Hello"');
|
||||
});
|
||||
```
|
||||
|
||||
HTTP Server method:
|
||||
```js
|
||||
HTTP.methods({
|
||||
'list': function(data) {
|
||||
if (data.foo === 'bar') {
|
||||
/* data we pass via the header is parsed by EJSON.parse
|
||||
If not able, then it returns the raw data instead */
|
||||
}
|
||||
return 'Hello';
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Parameters
|
||||
The method name or URL can be used to pass parameters to the method. The parameters are available on the server under `this.params`:
|
||||
|
||||
Client
|
||||
```js
|
||||
HTTP.post('/items/12/emails/5', function(err, result) {
|
||||
console.log('Got back: ' + result.content);
|
||||
});
|
||||
```
|
||||
|
||||
Server
|
||||
```js
|
||||
HTTP.methods({
|
||||
'/items/:itemId/emails/:emailId': function() {
|
||||
// this.params.itemId === '12'
|
||||
// this.params.emailId === '5'
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Extended usage
|
||||
The `HTTP.methods` normally takes a function, but it can be set to an object for fine-grained handling.
|
||||
|
||||
Example:
|
||||
```js
|
||||
HTTP.methods({
|
||||
'/hello': {
|
||||
get: function(data) {},
|
||||
// post: function(data) {},
|
||||
// put: function(data) {},
|
||||
// delete: function(data) {},
|
||||
// options: function() {
|
||||
// // Example of a simple options function
|
||||
// this.setStatusCode(200);
|
||||
// this.addHeader('Accept', 'POST,PUT');
|
||||
// // The options for this restpoint
|
||||
// var options = {
|
||||
// POST: {
|
||||
// description: 'Create an issue',
|
||||
// parameters: {
|
||||
// title: {
|
||||
// type: 'string',
|
||||
// description: 'Issue title'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
// // Print the options in pretty json
|
||||
// return JSON.stringify(options, null, '\t');
|
||||
// },
|
||||
// stream: true // flag whether to allow stream handling in the request
|
||||
}
|
||||
});
|
||||
```
|
||||
*In this example the mounted http rest point will only support the `get` method*
|
||||
|
||||
Example:
|
||||
```js
|
||||
HTTP.methods({
|
||||
'/hello': {
|
||||
method: function(data) {},
|
||||
}
|
||||
});
|
||||
```
|
||||
*In this example all methods `get`, `put`, `post`, `delete` will use the same function - This would be equal to setting the function directly on the http mount point*
|
||||
|
||||
## Authentication
|
||||
|
||||
The client needs the `access_token` to login in HTTP methods. *One could create a HTTP login/logout method for allowing pure external access*
|
||||
|
||||
Client
|
||||
```js
|
||||
HTTP.post('/hello', {
|
||||
params: {
|
||||
token: Accounts && Accounts._storedLoginToken()
|
||||
}
|
||||
}, function(err, result) {
|
||||
console.log('Got back: ' + result.content);
|
||||
});
|
||||
```
|
||||
|
||||
Server
|
||||
```js
|
||||
'hello': function(data) {
|
||||
if (this.userId) {
|
||||
var user = Meteor.users.findOne({ _id: this.userId });
|
||||
return 'Hello ' + (user && user.username || user && user.emails[0].address || 'user');
|
||||
} else {
|
||||
this.setStatusCode(401); // Unauthorized
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using custom authentication
|
||||
|
||||
It's possible to make your own function to set the userId - not using the built-in token pattern.
|
||||
```js
|
||||
// My auth will return the userId
|
||||
var myAuth = function() {
|
||||
// Read the token from '/hello?token=5'
|
||||
var userToken = self.query.token;
|
||||
// Check the userToken before adding it to the db query
|
||||
// Set the this.userId
|
||||
if (userToken) {
|
||||
var user = Meteor.users.findOne({ 'services.resume.loginTokens.token': userToken });
|
||||
|
||||
// Set the userId in the scope
|
||||
return user && user._id;
|
||||
}
|
||||
};
|
||||
|
||||
HTTP.methods({
|
||||
'/hello': {
|
||||
auth: myAuth,
|
||||
method: function(data) {
|
||||
// this.userId is set by myAuth
|
||||
if (this.userId) { /**/ } else { /**/ }
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
*The above resembles the builtin auth handler*
|
||||
|
||||
## Security
|
||||
When buffering data instead of streaming we set the buffer limit to 5mb - This can be changed on the fly:
|
||||
```js
|
||||
// Set the max data length
|
||||
// 5mb = 5 * 1024 * 1024 = 5242880;
|
||||
HTTP.methodsMaxDataLength = 5242880;
|
||||
```
|
||||
|
||||
## Login and logout (TODO)
|
||||
|
||||
These operations are not currently supported for off Meteor use - there are some security considerations.
|
||||
|
||||
`basic-auth` is broadly supported, but:
|
||||
* password should not be sent in clear text - hash with base64?
|
||||
* should be used on https connections
|
||||
* Its difficult / impossible to logout a user?
|
||||
|
||||
`token` the current `access_token` seems to be a better solution. Better control and options to logout users. But calling the initial `login` method still requires:
|
||||
* hashing of password
|
||||
* use https
|
||||
|
||||
## HTTP Client-side usage
|
||||
If you want to use the HTTP client-side functionality and find yourself having a hard time viewing all available options; these can be found on https://docs.meteor.com/#/full/http.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
HTTP = Package.http && Package.http.HTTP || {};
|
||||
|
||||
// Client-side simulation is not yet implemented
|
||||
HTTP.methods = function() {
|
||||
throw new Error('HTTP.methods not implemented on client-side');
|
||||
};
|
||||
644
packages/wekan-cfs-http-methods/http.methods.server.api.js
Normal file
644
packages/wekan-cfs-http-methods/http.methods.server.api.js
Normal file
|
|
@ -0,0 +1,644 @@
|
|||
/*
|
||||
|
||||
GET /note
|
||||
GET /note/:id
|
||||
POST /note
|
||||
PUT /note/:id
|
||||
DELETE /note/:id
|
||||
|
||||
*/
|
||||
HTTP = Package.http && Package.http.HTTP || {};
|
||||
|
||||
// Primary local test scope
|
||||
_methodHTTP = {};
|
||||
|
||||
|
||||
_methodHTTP.methodHandlers = {};
|
||||
_methodHTTP.methodTree = {};
|
||||
|
||||
// This could be changed eg. could allow larger data chunks than 1.000.000
|
||||
// 5mb = 5 * 1024 * 1024 = 5242880;
|
||||
HTTP.methodsMaxDataLength = 5242880; //1e6;
|
||||
|
||||
_methodHTTP.nameFollowsConventions = function(name) {
|
||||
// Check that name is string, not a falsy or empty
|
||||
return name && name === '' + name && name !== '';
|
||||
};
|
||||
|
||||
|
||||
_methodHTTP.getNameList = function(name) {
|
||||
// Remove leading and trailing slashes and make command array
|
||||
name = name && name.replace(/^\//g, '') || ''; // /^\/|\/$/g
|
||||
// TODO: Get the format from the url - eg.: "/list/45.json" format should be
|
||||
// set in this function by splitting the last list item by . and have format
|
||||
// as the last item. How should we toggle:
|
||||
// "/list/45/item.name.json" and "/list/45/item.name"?
|
||||
// We would either have to check all known formats or allways determin the "."
|
||||
// as an extension. Resolving in "json" and "name" as handed format - the user
|
||||
// Could simply just add the format as a parametre? or be explicit about
|
||||
// naming
|
||||
return name && name.split('/') || [];
|
||||
};
|
||||
|
||||
// Merge two arrays one containing keys and one values
|
||||
_methodHTTP.createObject = function(keys, values) {
|
||||
var result = {};
|
||||
if (keys && values) {
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
result[keys[i]] = values[i] && decodeURIComponent(values[i]) || '';
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
_methodHTTP.addToMethodTree = function(methodName) {
|
||||
var list = _methodHTTP.getNameList(methodName);
|
||||
var name = '/';
|
||||
// Contains the list of params names
|
||||
var params = [];
|
||||
var currentMethodTree = _methodHTTP.methodTree;
|
||||
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
|
||||
// get the key name
|
||||
var key = list[i];
|
||||
// Check if it expects a value
|
||||
if (key[0] === ':') {
|
||||
// This is a value
|
||||
params.push(key.slice(1));
|
||||
key = ':value';
|
||||
}
|
||||
name += key + '/';
|
||||
|
||||
// Set the key into the method tree
|
||||
if (typeof currentMethodTree[key] === 'undefined') {
|
||||
currentMethodTree[key] = {};
|
||||
}
|
||||
|
||||
// Dig deeper
|
||||
currentMethodTree = currentMethodTree[key];
|
||||
|
||||
}
|
||||
|
||||
if (_.isEmpty(currentMethodTree[':ref'])) {
|
||||
currentMethodTree[':ref'] = {
|
||||
name: name,
|
||||
params: params
|
||||
};
|
||||
}
|
||||
|
||||
return currentMethodTree[':ref'];
|
||||
};
|
||||
|
||||
// This method should be optimized for speed since its called on allmost every
|
||||
// http call to the server so we return null as soon as we know its not a method
|
||||
_methodHTTP.getMethod = function(name) {
|
||||
// Check if the
|
||||
if (!_methodHTTP.nameFollowsConventions(name)) {
|
||||
return null;
|
||||
}
|
||||
var list = _methodHTTP.getNameList(name);
|
||||
// Check if we got a correct list
|
||||
if (!list || !list.length) {
|
||||
return null;
|
||||
}
|
||||
// Set current refernce in the _methodHTTP.methodTree
|
||||
var currentMethodTree = _methodHTTP.methodTree;
|
||||
// Buffer for values to hand on later
|
||||
var values = [];
|
||||
// Iterate over the method name and check if its found in the method tree
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
// get the key name
|
||||
var key = list[i];
|
||||
// We expect to find the key or :value if not we break
|
||||
if (typeof currentMethodTree[key] !== 'undefined' ||
|
||||
typeof currentMethodTree[':value'] !== 'undefined') {
|
||||
// We got a result now check if its a value
|
||||
if (typeof currentMethodTree[key] === 'undefined') {
|
||||
// Push the value
|
||||
values.push(key);
|
||||
// Set the key to :value to dig deeper
|
||||
key = ':value';
|
||||
}
|
||||
|
||||
} else {
|
||||
// Break - method call not found
|
||||
return null;
|
||||
}
|
||||
|
||||
// Dig deeper
|
||||
currentMethodTree = currentMethodTree[key];
|
||||
}
|
||||
|
||||
// Extract reference pointer
|
||||
var reference = currentMethodTree && currentMethodTree[':ref'];
|
||||
if (typeof reference !== 'undefined') {
|
||||
return {
|
||||
name: reference.name,
|
||||
params: _methodHTTP.createObject(reference.params, values),
|
||||
handle: _methodHTTP.methodHandlers[reference.name]
|
||||
};
|
||||
} else {
|
||||
// Did not get any reference to the method
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// This method retrieves the userId from the token and makes sure that the token
|
||||
// is valid and not expired
|
||||
_methodHTTP.getUserId = function() {
|
||||
var self = this;
|
||||
|
||||
// // Get ip, x-forwarded-for can be comma seperated ips where the first is the
|
||||
// // client ip
|
||||
// var ip = self.req.headers['x-forwarded-for'] &&
|
||||
// // Return the first item in ip list
|
||||
// self.req.headers['x-forwarded-for'].split(',')[0] ||
|
||||
// // or return the remoteAddress
|
||||
// self.req.connection.remoteAddress;
|
||||
|
||||
// Check authentication
|
||||
var userToken = self.query.token;
|
||||
|
||||
// Check if we are handed strings
|
||||
try {
|
||||
userToken && check(userToken, String);
|
||||
} catch(err) {
|
||||
throw new Meteor.Error(404, 'Error user token and id not of type strings, Error: ' + (err.stack || err.message));
|
||||
}
|
||||
|
||||
// Set the this.userId
|
||||
if (userToken) {
|
||||
// Look up user to check if user exists and is loggedin via token
|
||||
var user = Meteor.users.findOne({
|
||||
$or: [
|
||||
{'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(userToken)},
|
||||
{'services.resume.loginTokens.token': userToken}
|
||||
]
|
||||
});
|
||||
// TODO: check 'services.resume.loginTokens.when' to have the token expire
|
||||
|
||||
// Set the userId in the scope
|
||||
return user && user._id;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// Expose the default auth for calling from custom authentication handlers.
|
||||
HTTP.defaultAuth = _methodHTTP.getUserId;
|
||||
|
||||
/*
|
||||
|
||||
Add default support for options
|
||||
|
||||
*/
|
||||
_methodHTTP.defaultOptionsHandler = function(methodObject) {
|
||||
// List of supported methods
|
||||
var allowMethods = [];
|
||||
// The final result object
|
||||
var result = {};
|
||||
|
||||
// Iterate over the methods
|
||||
// XXX: We should have a way to extend this - We should have some schema model
|
||||
// for our methods...
|
||||
_.each(methodObject, function(f, methodName) {
|
||||
// Skip the stream and auth functions - they are not public / accessible
|
||||
if (methodName !== 'stream' && methodName !== 'auth') {
|
||||
|
||||
// Create an empty description
|
||||
result[methodName] = { description: '', parameters: {} };
|
||||
// Add method name to headers
|
||||
allowMethods.push(methodName);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Lets play nice
|
||||
this.setStatusCode(200);
|
||||
|
||||
// We have to set some allow headers here
|
||||
this.addHeader('Allow', allowMethods.join(','));
|
||||
|
||||
// Return json result - Pretty print
|
||||
return JSON.stringify(result, null, '\t');
|
||||
};
|
||||
|
||||
// Public interface for adding server-side http methods - if setting a method to
|
||||
// 'false' it would actually remove the method (can be used to unpublish a method)
|
||||
HTTP.methods = function(newMethods) {
|
||||
_.each(newMethods, function(func, name) {
|
||||
if (_methodHTTP.nameFollowsConventions(name)) {
|
||||
// Check if we got a function
|
||||
//if (typeof func === 'function') {
|
||||
var method = _methodHTTP.addToMethodTree(name);
|
||||
// The func is good
|
||||
if (typeof _methodHTTP.methodHandlers[method.name] !== 'undefined') {
|
||||
if (func === false) {
|
||||
// If the method is set to false then unpublish
|
||||
delete _methodHTTP.methodHandlers[method.name];
|
||||
// Delete the reference in the _methodHTTP.methodTree
|
||||
delete method.name;
|
||||
delete method.params;
|
||||
} else {
|
||||
// We should not allow overwriting - following Meteor.methods
|
||||
throw new Error('HTTP method "' + name + '" is already registered');
|
||||
}
|
||||
} else {
|
||||
// We could have a function or a object
|
||||
// The object could have:
|
||||
// '/test/': {
|
||||
// auth: function() ... returning the userId using over default
|
||||
//
|
||||
// method: function() ...
|
||||
// or
|
||||
// post: function() ...
|
||||
// put:
|
||||
// get:
|
||||
// delete:
|
||||
// head:
|
||||
// }
|
||||
|
||||
/*
|
||||
We conform to the object format:
|
||||
{
|
||||
auth:
|
||||
post:
|
||||
put:
|
||||
get:
|
||||
delete:
|
||||
head:
|
||||
}
|
||||
This way we have a uniform reference
|
||||
*/
|
||||
|
||||
var uniObj = {};
|
||||
if (typeof func === 'function') {
|
||||
uniObj = {
|
||||
'auth': _methodHTTP.getUserId,
|
||||
'stream': false,
|
||||
'POST': func,
|
||||
'PUT': func,
|
||||
'GET': func,
|
||||
'DELETE': func,
|
||||
'HEAD': func,
|
||||
'OPTIONS': _methodHTTP.defaultOptionsHandler
|
||||
};
|
||||
} else {
|
||||
uniObj = {
|
||||
'stream': func.stream || false,
|
||||
'auth': func.auth || _methodHTTP.getUserId,
|
||||
'POST': func.post || func.method,
|
||||
'PUT': func.put || func.method,
|
||||
'GET': func.get || func.method,
|
||||
'DELETE': func.delete || func.method,
|
||||
'HEAD': func.head || func.get || func.method,
|
||||
'OPTIONS': func.options || _methodHTTP.defaultOptionsHandler
|
||||
};
|
||||
}
|
||||
|
||||
// Registre the method
|
||||
_methodHTTP.methodHandlers[method.name] = uniObj; // func;
|
||||
|
||||
}
|
||||
// } else {
|
||||
// // We do require a function as a function to execute later
|
||||
// throw new Error('HTTP.methods failed: ' + name + ' is not a function');
|
||||
// }
|
||||
} else {
|
||||
// We have to follow the naming spec defined in nameFollowsConventions
|
||||
throw new Error('HTTP.method "' + name + '" invalid naming of method');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var sendError = function(res, code, message) {
|
||||
if (code) {
|
||||
res.writeHead(code);
|
||||
} else {
|
||||
res.writeHead(500);
|
||||
}
|
||||
res.end(message);
|
||||
};
|
||||
|
||||
// This handler collects the header data into either an object (if json) or the
|
||||
// raw data. The data is passed to the callback
|
||||
var requestHandler = function(req, res, callback) {
|
||||
if (typeof callback !== 'function') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Container for buffers and a sum of the length
|
||||
var bufferData = [], dataLen = 0;
|
||||
|
||||
// Extract the body
|
||||
req.on('data', function(data) {
|
||||
bufferData.push(data);
|
||||
dataLen += data.length;
|
||||
|
||||
// We have to check the data length in order to spare the server
|
||||
if (dataLen > HTTP.methodsMaxDataLength) {
|
||||
dataLen = 0;
|
||||
bufferData = [];
|
||||
// Flood attack or faulty client
|
||||
sendError(res, 413, 'Flood attack or faulty client');
|
||||
req.connection.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
// When message is ready to be passed on
|
||||
req.on('end', function() {
|
||||
if (res.finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow the result to be undefined if so
|
||||
var result;
|
||||
|
||||
// If data found the work it - either buffer or json
|
||||
if (dataLen > 0) {
|
||||
result = new Buffer(dataLen);
|
||||
// Merge the chunks into one buffer
|
||||
for (var i = 0, ln = bufferData.length, pos = 0; i < ln; i++) {
|
||||
bufferData[i].copy(result, pos);
|
||||
pos += bufferData[i].length;
|
||||
delete bufferData[i];
|
||||
}
|
||||
// Check if we could be dealing with json
|
||||
if (result[0] == 0x7b && result[1] === 0x22) {
|
||||
try {
|
||||
// Convert the body into json and extract the data object
|
||||
result = EJSON.parse(result.toString());
|
||||
} catch(err) {
|
||||
// Could not parse so we return the raw data
|
||||
}
|
||||
}
|
||||
} // Else result will be undefined
|
||||
|
||||
try {
|
||||
callback(result);
|
||||
} catch(err) {
|
||||
sendError(res, 500, 'Error in requestHandler callback, Error: ' + (err.stack || err.message) );
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// This is the simplest handler - it simply passes req stream as data to the
|
||||
// method
|
||||
var streamHandler = function(req, res, callback) {
|
||||
try {
|
||||
callback();
|
||||
} catch(err) {
|
||||
sendError(res, 500, 'Error in requestHandler callback, Error: ' + (err.stack || err.message) );
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Allow file uploads in cordova cfs
|
||||
*/
|
||||
var setCordovaHeaders = function(request, response) {
|
||||
var origin = request.headers.origin;
|
||||
// Match http://localhost:<port> for Cordova clients in Meteor 1.3
|
||||
// and http://meteor.local for earlier versions
|
||||
if (origin && (origin === 'http://meteor.local' || /^http:\/\/localhost/.test(origin))) {
|
||||
// We need to echo the origin provided in the request
|
||||
response.setHeader("Access-Control-Allow-Origin", origin);
|
||||
|
||||
response.setHeader("Access-Control-Allow-Methods", "PUT");
|
||||
response.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
||||
}
|
||||
};
|
||||
|
||||
// Handle the actual connection
|
||||
WebApp.connectHandlers.use(function(req, res, next) {
|
||||
|
||||
// Check to se if this is a http method call
|
||||
var method = _methodHTTP.getMethod(req._parsedUrl.pathname);
|
||||
|
||||
// If method is null then it wasn't and we pass the request along
|
||||
if (method === null) {
|
||||
return next();
|
||||
}
|
||||
|
||||
var dataHandle = (method.handle && method.handle.stream)?streamHandler:requestHandler;
|
||||
|
||||
dataHandle(req, res, function(data) {
|
||||
// If methodsHandler not found or somehow the methodshandler is not a
|
||||
// function then return a 404
|
||||
if (typeof method.handle === 'undefined') {
|
||||
sendError(res, 404, 'Error HTTP method handler "' + method.name + '" is not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Set CORS headers for Meteor Cordova clients
|
||||
setCordovaHeaders(req, res);
|
||||
|
||||
// Set fiber scope
|
||||
var fiberScope = {
|
||||
// Pointers to Request / Response
|
||||
req: req,
|
||||
res: res,
|
||||
// Request / Response helpers
|
||||
statusCode: 200,
|
||||
method: req.method,
|
||||
// Headers for response
|
||||
headers: {
|
||||
'Content-Type': 'text/html' // Set default type
|
||||
},
|
||||
// Arguments
|
||||
data: data,
|
||||
query: req.query,
|
||||
params: method.params,
|
||||
// Method reference
|
||||
reference: method.name,
|
||||
methodObject: method.handle,
|
||||
_streamsWaiting: 0
|
||||
};
|
||||
|
||||
// Helper functions this scope
|
||||
Fiber = Npm.require('fibers');
|
||||
runServerMethod = Fiber(function(self) {
|
||||
var result, resultBuffer;
|
||||
|
||||
// We fetch methods data from methodsHandler, the handler uses the this.addItem()
|
||||
// function to populate the methods, this way we have better check control and
|
||||
// better error handling + messages
|
||||
|
||||
// The scope for the user methodObject callbacks
|
||||
var thisScope = {
|
||||
// The user whos id and token was used to run this method, if set/found
|
||||
userId: null,
|
||||
// The id of the data
|
||||
_id: null,
|
||||
// Set the query params ?token=1&id=2 -> { token: 1, id: 2 }
|
||||
query: self.query,
|
||||
// Set params /foo/:name/test/:id -> { name: '', id: '' }
|
||||
params: self.params,
|
||||
// Method GET, PUT, POST, DELETE, HEAD
|
||||
method: self.method,
|
||||
// User agent
|
||||
userAgent: req.headers['user-agent'],
|
||||
// All request headers
|
||||
requestHeaders: req.headers,
|
||||
// Add the request object it self
|
||||
request: req,
|
||||
// Set the userId
|
||||
setUserId: function(id) {
|
||||
this.userId = id;
|
||||
},
|
||||
// We dont simulate / run this on the client at the moment
|
||||
isSimulation: false,
|
||||
// Run the next method in a new fiber - This is default at the moment
|
||||
unblock: function() {},
|
||||
// Set the content type in header, defaults to text/html?
|
||||
setContentType: function(type) {
|
||||
self.headers['Content-Type'] = type;
|
||||
},
|
||||
setStatusCode: function(code) {
|
||||
self.statusCode = code;
|
||||
},
|
||||
addHeader: function(key, value) {
|
||||
self.headers[key] = value;
|
||||
},
|
||||
createReadStream: function() {
|
||||
self._streamsWaiting++;
|
||||
return req;
|
||||
},
|
||||
createWriteStream: function() {
|
||||
self._streamsWaiting++;
|
||||
return res;
|
||||
},
|
||||
Error: function(err) {
|
||||
|
||||
if (err instanceof Meteor.Error) {
|
||||
// Return controlled error
|
||||
sendError(res, err.error, err.message);
|
||||
} else if (err instanceof Error) {
|
||||
// Return error trace - this is not intented
|
||||
sendError(res, 503, 'Error in method "' + self.reference + '", Error: ' + (err.stack || err.message) );
|
||||
} else {
|
||||
sendError(res, 503, 'Error in method "' + self.reference + '"' );
|
||||
}
|
||||
|
||||
},
|
||||
// getData: function() {
|
||||
// // XXX: TODO if we could run the request handler stuff eg.
|
||||
// // in here in a fiber sync it could be cool - and the user did
|
||||
// // not have to specify the stream=true flag?
|
||||
// }
|
||||
};
|
||||
|
||||
// This function sends the final response. Depending on the
|
||||
// timing of the streaming, we might have to wait for all
|
||||
// streaming to end, or we might have to wait for this function
|
||||
// to finish after streaming ends. The checks in this function
|
||||
// and the fact that we call it twice ensure that we will always
|
||||
// send the response if we haven't sent an error response, but
|
||||
// we will not send it too early.
|
||||
function sendResponseIfDone() {
|
||||
res.statusCode = self.statusCode;
|
||||
// If no streams are waiting
|
||||
if (self._streamsWaiting === 0 &&
|
||||
(self.statusCode === 200 || self.statusCode === 206) &&
|
||||
self.done &&
|
||||
!self._responseSent &&
|
||||
!res.finished) {
|
||||
self._responseSent = true;
|
||||
res.end(resultBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
var methodCall = self.methodObject[self.method];
|
||||
|
||||
// If the method call is set for the POST/PUT/GET or DELETE then run the
|
||||
// respective methodCall if its a function
|
||||
if (typeof methodCall === 'function') {
|
||||
|
||||
// Get the userId - This is either set as a method specific handler and
|
||||
// will allways default back to the builtin getUserId handler
|
||||
try {
|
||||
// Try to set the userId
|
||||
thisScope.userId = self.methodObject.auth.apply(self);
|
||||
} catch(err) {
|
||||
sendError(res, err.error, (err.message || err.stack));
|
||||
return;
|
||||
}
|
||||
|
||||
// This must be attached before there's any chance of `createReadStream`
|
||||
// or `createWriteStream` being called, which means before we do
|
||||
// `methodCall.apply` below.
|
||||
req.on('end', function() {
|
||||
self._streamsWaiting--;
|
||||
sendResponseIfDone();
|
||||
});
|
||||
|
||||
// Get the result of the methodCall
|
||||
try {
|
||||
if (self.method === 'OPTIONS') {
|
||||
result = methodCall.apply(thisScope, [self.methodObject]) || '';
|
||||
} else {
|
||||
result = methodCall.apply(thisScope, [self.data]) || '';
|
||||
}
|
||||
} catch(err) {
|
||||
if (err instanceof Meteor.Error) {
|
||||
// Return controlled error
|
||||
sendError(res, err.error, err.message);
|
||||
} else {
|
||||
// Return error trace - this is not intented
|
||||
sendError(res, 503, 'Error in method "' + self.reference + '", Error: ' + (err.stack || err.message) );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Set headers
|
||||
_.each(self.headers, function(value, key) {
|
||||
// If value is defined then set the header, this allows for unsetting
|
||||
// the default content-type
|
||||
if (typeof value !== 'undefined')
|
||||
res.setHeader(key, value);
|
||||
});
|
||||
|
||||
// If OK / 200 then Return the result
|
||||
if (self.statusCode === 200 || self.statusCode === 206) {
|
||||
|
||||
if (self.method !== "HEAD") {
|
||||
// Return result
|
||||
if (typeof result === 'string') {
|
||||
resultBuffer = new Buffer(result);
|
||||
} else {
|
||||
resultBuffer = new Buffer(JSON.stringify(result));
|
||||
}
|
||||
|
||||
// Check if user wants to overwrite content length for some reason?
|
||||
if (typeof self.headers['Content-Length'] === 'undefined') {
|
||||
self.headers['Content-Length'] = resultBuffer.length;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
self.done = true;
|
||||
sendResponseIfDone();
|
||||
|
||||
} else {
|
||||
// Allow user to alter the status code and send a message
|
||||
sendError(res, self.statusCode, result);
|
||||
}
|
||||
|
||||
} else {
|
||||
sendError(res, 404, 'Service not found');
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
// Run http methods handler
|
||||
try {
|
||||
runServerMethod.run(fiberScope);
|
||||
} catch(err) {
|
||||
sendError(res, 500, 'Error running the server http method handler, Error: ' + (err.stack || err.message));
|
||||
}
|
||||
|
||||
}); // EO Request handler
|
||||
|
||||
|
||||
});
|
||||
117
packages/wekan-cfs-http-methods/http.methods.tests.js
Normal file
117
packages/wekan-cfs-http-methods/http.methods.tests.js
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
function equals(a, b) {
|
||||
return EJSON.stringify(a) === EJSON.stringify(b);
|
||||
}
|
||||
|
||||
Tinytest.add('http-methods - test environment', function(test) {
|
||||
test.isTrue(typeof _methodHTTP !== 'undefined', 'test environment not initialized _methodHTTP');
|
||||
test.isTrue(typeof HTTP !== 'undefined', 'test environment not initialized HTTP');
|
||||
test.isTrue(typeof HTTP.methods !== 'undefined', 'test environment not initialized HTTP.methods');
|
||||
|
||||
});
|
||||
|
||||
Tinytest.add('http-methods - nameFollowsConventions', function(test) {
|
||||
test.isFalse(_methodHTTP.nameFollowsConventions(), 'Tested methods naming convention 1');
|
||||
test.isFalse(_methodHTTP.nameFollowsConventions(''), 'Tested methods naming convention 2');
|
||||
test.isFalse(_methodHTTP.nameFollowsConventions({}), 'Tested methods naming convention 3');
|
||||
test.isFalse(_methodHTTP.nameFollowsConventions([1]), 'Tested methods naming convention 4');
|
||||
test.isFalse(_methodHTTP.nameFollowsConventions(-1), 'Tested methods naming convention 5');
|
||||
test.isFalse(_methodHTTP.nameFollowsConventions(1), 'Tested methods naming convention 6');
|
||||
test.isFalse(_methodHTTP.nameFollowsConventions(0.1), 'Tested methods naming convention 7');
|
||||
test.isFalse(_methodHTTP.nameFollowsConventions(-0.1), 'Tested methods naming convention 8');
|
||||
|
||||
test.isTrue(_methodHTTP.nameFollowsConventions('/test/test'), 'Tested methods naming convention leading slash');
|
||||
test.isTrue(_methodHTTP.nameFollowsConventions('test/test'), 'Tested methods naming convention');
|
||||
});
|
||||
|
||||
Tinytest.add('http-methods - getNameList', function(test) {
|
||||
test.equal(EJSON.stringify(_methodHTTP.getNameList()), '[]', 'Name list failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.getNameList('')), '[]', 'Name list failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.getNameList('/')), '[]', 'Name list failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.getNameList('//')), '["",""]', 'Name list failed');
|
||||
|
||||
test.equal(EJSON.stringify(_methodHTTP.getNameList('/1/')), '["1",""]', 'Name list failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.getNameList('/1/2')), '["1","2"]', 'Name list failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.getNameList('/1/:name/2')), '["1",":name","2"]', 'Name list failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.getNameList('/1//2')), '["1","","2"]', 'Name list failed');
|
||||
});
|
||||
|
||||
|
||||
Tinytest.add('http-methods - createObject', function(test) {
|
||||
test.equal(EJSON.stringify(_methodHTTP.createObject()), '{}', 'createObject failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.createObject(2, 4)), '{}', 'createObject failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.createObject(['foo'], [])), '{"foo":""}', 'createObject failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.createObject(['foo'], ['bar'])), '{"foo":"bar"}', 'createObject failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.createObject(['foo'], [3])), '{"foo":"3"}', 'createObject failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.createObject(['foo'], ['bar', 3])), '{"foo":"bar"}', 'createObject failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.createObject(['foo', 'foo'], ['bar', 3])), '{"foo":"3"}', 'createObject failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.createObject([''], ['bar', 3])), '{"":"bar"}', 'createObject failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.createObject(['', ''], ['bar', 3])), '{"":"3"}', 'createObject failed');
|
||||
});
|
||||
|
||||
Tinytest.add('http-methods - addToMethodTree', function(test) {
|
||||
var original = _methodHTTP.methodTree;
|
||||
_methodHTTP.methodTree = {};
|
||||
_methodHTTP.addToMethodTree('login');
|
||||
test.equal(EJSON.stringify(_methodHTTP.methodTree), '{"login":{":ref":{"name":"/login/","params":[]}}}', 'addToMethodTree failed');
|
||||
|
||||
_methodHTTP.methodTree = {};
|
||||
_methodHTTP.addToMethodTree('/foo/bar');
|
||||
test.equal(EJSON.stringify(_methodHTTP.methodTree), '{"foo":{"bar":{":ref":{"name":"/foo/bar/","params":[]}}}}', 'addToMethodTree failed');
|
||||
|
||||
_methodHTTP.methodTree = {};
|
||||
_methodHTTP.addToMethodTree('/foo/:name/bar');
|
||||
test.equal(EJSON.stringify(_methodHTTP.methodTree), '{"foo":{":value":{"bar":{":ref":{"name":"/foo/:value/bar/","params":["name"]}}}}}', 'addToMethodTree failed');
|
||||
|
||||
_methodHTTP.addToMethodTree('/foo/:name/bar');
|
||||
test.equal(EJSON.stringify(_methodHTTP.methodTree), '{"foo":{":value":{"bar":{":ref":{"name":"/foo/:value/bar/","params":["name"]}}}}}', 'addToMethodTree failed');
|
||||
|
||||
_methodHTTP.addToMethodTree('/foo/name/bar');
|
||||
test.equal(EJSON.stringify(_methodHTTP.methodTree), '{"foo":{":value":{"bar":{":ref":{"name":"/foo/:value/bar/","params":["name"]}}},"name":{"bar":{":ref":{"name":"/foo/name/bar/","params":[]}}}}}', 'addToMethodTree failed');
|
||||
|
||||
_methodHTTP.methodTree = original;
|
||||
});
|
||||
|
||||
Tinytest.add('http-methods - getMethod', function(test) {
|
||||
// Basic tests
|
||||
test.equal(EJSON.stringify(_methodHTTP.getMethod('')), 'null', 'getMethod failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.getMethod('//')), 'null', 'getMethod failed');
|
||||
|
||||
_methodHTTP.addToMethodTree('login');
|
||||
test.equal(EJSON.stringify(_methodHTTP.getMethod('login')), '{"name":"/login/","params":{}}', 'getMethod failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.getMethod('/login')), '{"name":"/login/","params":{}}', 'getMethod failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.getMethod('login/')), 'null', 'getMethod failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.getMethod('/login/')), 'null', 'getMethod failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.getMethod('login/test')), 'null', 'getMethod failed');
|
||||
|
||||
_methodHTTP.addToMethodTree('/login/');
|
||||
test.equal(EJSON.stringify(_methodHTTP.getMethod('login')), '{"name":"/login/","params":{}}', 'getMethod failed');
|
||||
|
||||
//
|
||||
|
||||
_methodHTTP.addToMethodTree('/login/foo');
|
||||
test.equal(EJSON.stringify(_methodHTTP.getMethod('login/foo')), '{"name":"/login/foo/","params":{}}', 'getMethod failed');
|
||||
|
||||
_methodHTTP.addToMethodTree('/login/:name/foo');
|
||||
test.equal(EJSON.stringify(_methodHTTP.getMethod('login/bar/foo')), '{"name":"/login/:value/foo/","params":{"name":"bar"}}', 'getMethod failed');
|
||||
test.equal(EJSON.stringify(_methodHTTP.getMethod('login/foo')), '{"name":"/login/foo/","params":{}}', 'getMethod failed');
|
||||
|
||||
});
|
||||
|
||||
//Test API:
|
||||
//test.isFalse(v, msg)
|
||||
//test.isTrue(v, msg)
|
||||
//test.equal(actual, expected, message, not)
|
||||
//test.length(obj, len)
|
||||
//test.include(s, v)
|
||||
//test.isNaN(v, msg)
|
||||
//test.isUndefined(v, msg)
|
||||
//test.isNotNull
|
||||
//test.isNull
|
||||
//test.throws(func)
|
||||
//test.instanceOf(obj, klass)
|
||||
//test.notEqual(actual, expected, message)
|
||||
//test.runId()
|
||||
//test.exception(exception)
|
||||
//test.expect_fail()
|
||||
//test.ok(doc)
|
||||
//test.fail(doc)
|
||||
31
packages/wekan-cfs-http-methods/package.js
Normal file
31
packages/wekan-cfs-http-methods/package.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
Package.describe({
|
||||
git: 'https://github.com/zcfs/Meteor-http-methods.git',
|
||||
name: 'wekan-cfs-http-methods',
|
||||
version: '0.0.32',
|
||||
summary: 'Adds HTTP.methods RESTful'
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.0');
|
||||
|
||||
api.use(['webapp', 'underscore', 'ejson'], 'server');
|
||||
|
||||
api.use('http', { weak: true });
|
||||
|
||||
api.export && api.export('HTTP');
|
||||
|
||||
api.export && api.export('_methodHTTP', { testOnly: true });
|
||||
|
||||
api.addFiles('http.methods.client.api.js', 'client');
|
||||
api.addFiles('http.methods.server.api.js', 'server');
|
||||
|
||||
});
|
||||
|
||||
Package.onTest(function (api) {
|
||||
api.use('wekan-cfs-http-methods', ['server']);
|
||||
api.use('test-helpers', 'server');
|
||||
api.use(['tinytest', 'underscore', 'ejson', 'ordered-dict',
|
||||
'random', 'deps']);
|
||||
|
||||
api.addFiles('http.methods.tests.js', 'server');
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue