mirror of
https://github.com/wekan/wekan.git
synced 2025-12-19 17:00: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
466
packages/wekan-cfs-http-publish/http.publish.server.api.js
Normal file
466
packages/wekan-cfs-http-publish/http.publish.server.api.js
Normal file
|
|
@ -0,0 +1,466 @@
|
|||
/*
|
||||
|
||||
GET /note
|
||||
GET /note/:id
|
||||
POST /note
|
||||
PUT /note/:id
|
||||
DELETE /note/:id
|
||||
|
||||
*/
|
||||
|
||||
// Could be cool if we could serve some api doc or even an api script
|
||||
// user could do <script href="/note/api?token=1&user=2"></script> and be served
|
||||
// a client-side javascript api?
|
||||
// Eg.
|
||||
// HTTP.api.note.create();
|
||||
// HTTP.api.login(username, password);
|
||||
// HTTP.api.logout
|
||||
|
||||
|
||||
_publishHTTP = {};
|
||||
|
||||
// Cache the names of all http methods we've published
|
||||
_publishHTTP.currentlyPublished = [];
|
||||
|
||||
var defaultAPIPrefix = '/api/';
|
||||
|
||||
/**
|
||||
* @method _publishHTTP.getPublishScope
|
||||
* @private
|
||||
* @param {Object} scope
|
||||
* @returns {httpPublishGetPublishScope.publishScope}
|
||||
*
|
||||
* Creates a nice scope for the publish method
|
||||
*/
|
||||
_publishHTTP.getPublishScope = function httpPublishGetPublishScope(scope) {
|
||||
var publishScope = {};
|
||||
publishScope.userId = scope.userId;
|
||||
publishScope.params = scope.params;
|
||||
publishScope.query = scope.query;
|
||||
// TODO: Additional scoping
|
||||
// publishScope.added
|
||||
// publishScope.ready
|
||||
return publishScope;
|
||||
};
|
||||
|
||||
_publishHTTP.formatHandlers = {};
|
||||
|
||||
/**
|
||||
* @method _publishHTTP.formatHandlers.json
|
||||
* @private
|
||||
* @param {Object} result - The result object
|
||||
* @returns {String} JSON
|
||||
*
|
||||
* Formats the output into JSON and sets the appropriate content type on `this`
|
||||
*/
|
||||
_publishHTTP.formatHandlers.json = function httpPublishJSONFormatHandler(result) {
|
||||
// Set the method scope content type to json
|
||||
this.setContentType('application/json');
|
||||
// Return EJSON string
|
||||
return EJSON.stringify(result);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method _publishHTTP.formatResult
|
||||
* @private
|
||||
* @param {Object} result - The result object
|
||||
* @param {Object} scope
|
||||
* @param {String} [defaultFormat='json'] - Default format to use if format is not in query string.
|
||||
* @returns {Any} The formatted result
|
||||
*
|
||||
* Formats the result into the format selected by querystring eg. "&format=json"
|
||||
*/
|
||||
_publishHTTP.formatResult = function httpPublishFormatResult(result, scope, defaultFormat) {
|
||||
|
||||
// Get the format in lower case and default to json
|
||||
var format = scope && scope.query && scope.query.format || defaultFormat || 'json';
|
||||
|
||||
// Set the format handler found
|
||||
var formatHandlerFound = !!(typeof _publishHTTP.formatHandlers[format] === 'function');
|
||||
|
||||
// Set the format handler and fallback to default json if handler not found
|
||||
var formatHandler = _publishHTTP.formatHandlers[(formatHandlerFound) ? format : 'json'];
|
||||
|
||||
// Check if format handler is a function
|
||||
if (typeof formatHandler !== 'function') {
|
||||
// We break things the user could have overwritten the default json handler
|
||||
throw new Error('The default json format handler not found');
|
||||
}
|
||||
|
||||
if (!formatHandlerFound) {
|
||||
scope.setStatusCode(500);
|
||||
return '{"error":"Format handler for: `' + format + '` not found"}';
|
||||
}
|
||||
|
||||
// Execute the format handler
|
||||
try {
|
||||
return formatHandler.apply(scope, [result]);
|
||||
} catch(err) {
|
||||
scope.setStatusCode(500);
|
||||
return '{"error":"Format handler for: `' + format + '` Error: ' + err.message + '"}';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method _publishHTTP.error
|
||||
* @private
|
||||
* @param {String} statusCode - The status code
|
||||
* @param {String} message - The message
|
||||
* @param {Object} scope
|
||||
* @returns {Any} The formatted result
|
||||
*
|
||||
* Responds with error message in the expected format
|
||||
*/
|
||||
_publishHTTP.error = function httpPublishError(statusCode, message, scope) {
|
||||
var result = _publishHTTP.formatResult(message, scope);
|
||||
scope.setStatusCode(statusCode);
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* @method _publishHTTP.getMethodHandler
|
||||
* @private
|
||||
* @param {Meteor.Collection} collection - The Meteor.Collection instance
|
||||
* @param {String} methodName - The method name
|
||||
* @returns {Function} The server method
|
||||
*
|
||||
* Returns the DDP connection handler, already setup and secured
|
||||
*/
|
||||
_publishHTTP.getMethodHandler = function httpPublishGetMethodHandler(collection, methodName) {
|
||||
if (collection instanceof Meteor.Collection) {
|
||||
if (collection._connection && collection._connection.method_handlers) {
|
||||
return collection._connection.method_handlers[collection._prefix + methodName];
|
||||
} else {
|
||||
throw new Error('HTTP publish does not work with current version of Meteor');
|
||||
}
|
||||
} else {
|
||||
throw new Error('_publishHTTP.getMethodHandler expected a collection');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method _publishHTTP.unpublishList
|
||||
* @private
|
||||
* @param {Array} names - List of method names to unpublish
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Unpublishes all HTTP methods that have names matching the given list.
|
||||
*/
|
||||
_publishHTTP.unpublishList = function httpPublishUnpublishList(names) {
|
||||
if (!names.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Carry object for methods
|
||||
var methods = {};
|
||||
|
||||
// Unpublish the rest points by setting them to false
|
||||
for (var i = 0, ln = names.length; i < ln; i++) {
|
||||
methods[names[i]] = false;
|
||||
}
|
||||
|
||||
HTTP.methods(methods);
|
||||
|
||||
// Remove the names from our list of currently published methods
|
||||
_publishHTTP.currentlyPublished = _.difference(_publishHTTP.currentlyPublished, names);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method _publishHTTP.unpublish
|
||||
* @private
|
||||
* @param {String|Meteor.Collection} [name] - The method name or collection
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Unpublishes all HTTP methods that were published with the given name or
|
||||
* for the given collection. Call with no arguments to unpublish all.
|
||||
*/
|
||||
_publishHTTP.unpublish = function httpPublishUnpublish(/* name or collection, options */) {
|
||||
|
||||
// Determine what method name we're unpublishing
|
||||
var name = (arguments[0] instanceof Meteor.Collection) ?
|
||||
defaultAPIPrefix + arguments[0]._name : arguments[0];
|
||||
|
||||
// Unpublish name and name/id
|
||||
if (name && name.length) {
|
||||
_publishHTTP.unpublishList([name, name + '/:id']);
|
||||
}
|
||||
|
||||
// If no args, unpublish all
|
||||
else {
|
||||
_publishHTTP.unpublishList(_publishHTTP.currentlyPublished);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @method HTTP.publishFormats
|
||||
* @public
|
||||
* @param {Object} newHandlers
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Add publish formats. Example:
|
||||
```js
|
||||
HTTP.publishFormats({
|
||||
|
||||
json: function(inputObject) {
|
||||
// Set the method scope content type to json
|
||||
this.setContentType('application/json');
|
||||
// Return EJSON string
|
||||
return EJSON.stringify(inputObject);
|
||||
}
|
||||
|
||||
});
|
||||
```
|
||||
*/
|
||||
HTTP.publishFormats = function httpPublishFormats(newHandlers) {
|
||||
_.extend(_publishHTTP.formatHandlers, newHandlers);
|
||||
};
|
||||
|
||||
/**
|
||||
* @method HTTP.publish
|
||||
* @public
|
||||
* @param {Object} options
|
||||
* @param {String} [name] - Restpoint name (url prefix). Optional if `collection` is passed. Will mount on `/api/collectionName` by default.
|
||||
* @param {Meteor.Collection} [collection] - Meteor.Collection instance. Required for all restpoints except collectionGet
|
||||
* @param {String} [options.defaultFormat='json'] - Format to use for responses when `format` is not found in the query string.
|
||||
* @param {String} [options.collectionGet=true] - Add GET restpoint for collection? Requires a publish function.
|
||||
* @param {String} [options.collectionPost=true] - Add POST restpoint for adding documents to the collection?
|
||||
* @param {String} [options.documentGet=true] - Add GET restpoint for documents in collection? Requires a publish function.
|
||||
* @param {String} [options.documentPut=true] - Add PUT restpoint for updating a document in the collection?
|
||||
* @param {String} [options.documentDelete=true] - Add DELETE restpoint for deleting a document in the collection?
|
||||
* @param {Function} [publishFunc] - A publish function. Required to mount GET restpoints.
|
||||
* @returns {undefined}
|
||||
* @todo this should use options argument instead of optional args
|
||||
*
|
||||
* Publishes one or more restpoints, mounted on "name" ("/api/collectionName/"
|
||||
* by default). The GET restpoints are subscribed to the document set (cursor)
|
||||
* returned by the publish function you supply. The other restpoints forward
|
||||
* requests to Meteor's built-in DDP methods (insert, update, remove), meaning
|
||||
* that full allow/deny security is automatic.
|
||||
*
|
||||
* __Usage:__
|
||||
*
|
||||
* Publish only:
|
||||
*
|
||||
* HTTP.publish({name: 'mypublish'}, publishFunc);
|
||||
*
|
||||
* Publish and mount crud rest point for collection /api/myCollection:
|
||||
*
|
||||
* HTTP.publish({collection: myCollection}, publishFunc);
|
||||
*
|
||||
* Mount CUD rest point for collection and documents without GET:
|
||||
*
|
||||
* HTTP.publish({collection: myCollection});
|
||||
*
|
||||
*/
|
||||
HTTP.publish = function httpPublish(options, publishFunc) {
|
||||
options = _.extend({
|
||||
name: null,
|
||||
auth: null,
|
||||
collection: null,
|
||||
defaultFormat: null,
|
||||
collectionGet: true,
|
||||
collectionPost: true,
|
||||
documentGet: true,
|
||||
documentPut: true,
|
||||
documentDelete: true
|
||||
}, options || {});
|
||||
|
||||
var collection = options.collection;
|
||||
|
||||
// Use provided name or build one
|
||||
var name = (typeof options.name === "string") ? options.name : defaultAPIPrefix + collection._name;
|
||||
|
||||
// Make sure we have a name
|
||||
if (typeof name !== "string") {
|
||||
throw new Error('HTTP.publish expected a collection or name option');
|
||||
}
|
||||
|
||||
var defaultFormat = options.defaultFormat;
|
||||
|
||||
// Rig the methods for the CRUD interface
|
||||
var methods = {};
|
||||
|
||||
// console.log('HTTP restpoint: ' + name);
|
||||
|
||||
// list and create
|
||||
methods[name] = {};
|
||||
|
||||
if (options.collectionGet && publishFunc) {
|
||||
// Return the published documents
|
||||
methods[name].get = function(data) {
|
||||
// Format the scope for the publish method
|
||||
var publishScope = _publishHTTP.getPublishScope(this);
|
||||
// Get the publish cursor
|
||||
var cursor = publishFunc.apply(publishScope, [data]);
|
||||
|
||||
// Check if its a cursor
|
||||
if (cursor && cursor.fetch) {
|
||||
// Fetch the data fron cursor
|
||||
var result = cursor.fetch();
|
||||
// Return the data
|
||||
return _publishHTTP.formatResult(result, this, defaultFormat);
|
||||
} else {
|
||||
// We didnt get any
|
||||
return _publishHTTP.error(200, [], this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (collection) {
|
||||
// If we have a collection then add insert method
|
||||
if (options.collectionPost) {
|
||||
methods[name].post = function(data) {
|
||||
var insertMethodHandler = _publishHTTP.getMethodHandler(collection, 'insert');
|
||||
// Make sure that _id isset else create a Meteor id
|
||||
data._id = data._id || Random.id();
|
||||
// Create the document
|
||||
try {
|
||||
// We should be passed a document in data
|
||||
insertMethodHandler.apply(this, [data]);
|
||||
// Return the data
|
||||
return _publishHTTP.formatResult({ _id: data._id }, this, defaultFormat);
|
||||
} catch(err) {
|
||||
// This would be a Meteor.error?
|
||||
return _publishHTTP.error(err.error, { error: err.message }, this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// We also add the findOne, update and remove methods
|
||||
methods[name + '/:id'] = {};
|
||||
|
||||
if (options.documentGet && publishFunc) {
|
||||
// We have to have a publish method inorder to publish id? The user could
|
||||
// just write a publish all if needed - better to make this explicit
|
||||
methods[name + '/:id'].get = function(data) {
|
||||
// Get the mongoId
|
||||
var mongoId = this.params.id;
|
||||
|
||||
// We would allways expect a string but it could be empty
|
||||
if (mongoId !== '') {
|
||||
|
||||
// Format the scope for the publish method
|
||||
var publishScope = _publishHTTP.getPublishScope(this);
|
||||
|
||||
// Get the publish cursor
|
||||
var cursor = publishFunc.apply(publishScope, [data]);
|
||||
|
||||
// Result will contain the document if found
|
||||
var result;
|
||||
|
||||
// Check to see if document is in published cursor
|
||||
if (cursor) {
|
||||
cursor.forEach(function(doc) {
|
||||
if (!result) {
|
||||
if (doc._id === mongoId) {
|
||||
result = doc;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If the document is found the return
|
||||
if (result) {
|
||||
return _publishHTTP.formatResult(result, this, defaultFormat);
|
||||
} else {
|
||||
// We do a check to see if the doc id exists
|
||||
var exists = collection.findOne({ _id: mongoId });
|
||||
// If it exists its not published to the user
|
||||
if (exists) {
|
||||
// Unauthorized
|
||||
return _publishHTTP.error(401, { error: 'Unauthorized' }, this);
|
||||
} else {
|
||||
// Not found
|
||||
return _publishHTTP.error(404, { error: 'Document with id ' + mongoId + ' not found' }, this);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
return _publishHTTP.error(400, { error: 'Method expected a document id' }, this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (options.documentPut) {
|
||||
methods[name + '/:id'].put = function(data) {
|
||||
// Get the mongoId
|
||||
var mongoId = this.params.id;
|
||||
|
||||
// We would allways expect a string but it could be empty
|
||||
if (mongoId !== '') {
|
||||
|
||||
var updateMethodHandler = _publishHTTP.getMethodHandler(collection, 'update');
|
||||
// Create the document
|
||||
try {
|
||||
// We should be passed a document in data
|
||||
updateMethodHandler.apply(this, [{ _id: mongoId }, data]);
|
||||
// Return the data
|
||||
return _publishHTTP.formatResult({ _id: mongoId }, this, defaultFormat);
|
||||
} catch(err) {
|
||||
// This would be a Meteor.error?
|
||||
return _publishHTTP.error(err.error, { error: err.message }, this);
|
||||
}
|
||||
|
||||
} else {
|
||||
return _publishHTTP.error(400, { error: 'Method expected a document id' }, this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (options.documentDelete) {
|
||||
methods[name + '/:id'].delete = function(data) {
|
||||
// Get the mongoId
|
||||
var mongoId = this.params.id;
|
||||
|
||||
// We would allways expect a string but it could be empty
|
||||
if (mongoId !== '') {
|
||||
|
||||
var removeMethodHandler = _publishHTTP.getMethodHandler(collection, 'remove');
|
||||
// Create the document
|
||||
try {
|
||||
// We should be passed a document in data
|
||||
removeMethodHandler.apply(this, [{ _id: mongoId }]);
|
||||
// Return the data
|
||||
return _publishHTTP.formatResult({ _id: mongoId }, this, defaultFormat);
|
||||
} catch(err) {
|
||||
// This would be a Meteor.error?
|
||||
return _publishHTTP.error(err.error, { error: err.message }, this);
|
||||
}
|
||||
|
||||
} else {
|
||||
return _publishHTTP.error(400, { error: 'Method expected a document id' }, this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Authenticate with our own auth method: https://github.com/zcfs/Meteor-http-methods#authentication
|
||||
if (options.auth) {
|
||||
if (methods[name]) {
|
||||
methods[name].auth = options.auth;
|
||||
}
|
||||
if (methods[name + '/:id']) {
|
||||
methods[name + '/:id'].auth = options.auth;
|
||||
}
|
||||
}
|
||||
|
||||
// Publish the methods
|
||||
HTTP.methods(methods);
|
||||
|
||||
// Mark these method names as currently published
|
||||
_publishHTTP.currentlyPublished = _.union(_publishHTTP.currentlyPublished, _.keys(methods));
|
||||
|
||||
}; // EO Publish
|
||||
|
||||
/**
|
||||
* @method HTTP.unpublish
|
||||
* @public
|
||||
* @param {String|Meteor.Collection} [name] - The method name or collection
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Unpublishes all HTTP methods that were published with the given name or
|
||||
* for the given collection. Call with no arguments to unpublish all.
|
||||
*/
|
||||
HTTP.unpublish = _publishHTTP.unpublish;
|
||||
Loading…
Add table
Add a link
Reference in a new issue