You are on page 1of 30

(function () {

/* Imports */
var Meteor = Package.meteor.Meteor;
var global = Package.meteor.global;
var meteorEnv = Package.meteor.meteorEnv;
var FS = Package['cfs:base-package'].FS;
var check = Package.check.check;
var Match = Package.check.Match;
var EJSON = Package.ejson.EJSON;
var HTTP = Package['cfs:http-methods'].HTTP;

/* Package-scope variables */
var rootUrlPathPrefix, baseUrl, getHeaders, getHeadersByCollection,
_existingMountPoints, mountUrls;

(function(){

///////////////////////////////////////////////////////////////////////
// //
// packages/cfs_access-point/packages/cfs_access-point.js //
// //
///////////////////////////////////////////////////////////////////////
//
(function () {

///////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////
//
//
// packages/cfs:access-point/access-point-common.js
//
//
//
///////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////

//
rootUrlPathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || "";
// 1
// Adjust the rootUrlPathPrefix if necessary
// 2
if (rootUrlPathPrefix.length > 0) {
// 3
if (rootUrlPathPrefix.slice(0, 1) !== '/') {
// 4
rootUrlPathPrefix = '/' + rootUrlPathPrefix;
// 5
}
// 6
if (rootUrlPathPrefix.slice(-1) === '/') {
// 7
rootUrlPathPrefix = rootUrlPathPrefix.slice(0, -1);
// 8
}
// 9
}
// 10
// 11
// prepend ROOT_URL when isCordova
// 12
if (Meteor.isCordova) {
// 13
rootUrlPathPrefix = Meteor.absoluteUrl(rootUrlPathPrefix.replace(/^\/+/,
'')).replace(/\/+$/, ''); // 14
}
// 15

// 16
baseUrl = '/cfs';
// 17
FS.HTTP = FS.HTTP || {};
// 18

// 19
// Note the upload URL so that client uploader packages know what it is
// 20
FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files';
// 21

// 22
/**
// 23
* @method FS.HTTP.setBaseUrl
// 24
* @public
// 25
* @param {String} newBaseUrl - Change the base URL for the HTTP GET and DELETE
endpoints. // 26
* @returns {undefined}
// 27
*/
// 28
FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) {
// 29

// 30
// Adjust the baseUrl if necessary
// 31
if (newBaseUrl.slice(0, 1) !== '/') {
// 32
newBaseUrl = '/' + newBaseUrl;
// 33
}
// 34
if (newBaseUrl.slice(-1) === '/') {
// 35
newBaseUrl = newBaseUrl.slice(0, -1);
// 36
}
// 37

// 38
// Update the base URL
// 39
baseUrl = newBaseUrl;
// 40
// 41
// Change the upload URL so that client uploader packages know what it is
// 42
FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files';
// 43

// 44
// Remount URLs with the new baseUrl, unmounting the old, on the server only.
// 45
// If existingMountPoints is empty, then we haven't run the server startup
// 46
// code yet, so this new URL will be used at that point for the initial mount.
// 47
if (Meteor.isServer && !FS.Utility.isEmpty(_existingMountPoints)) {
// 48
mountUrls();
// 49
}
// 50
};
// 51

// 52
/*
// 53
* FS.File extensions
// 54
*/
// 55

// 56
/**
// 57
* @method FS.File.prototype.url Construct the file url
// 58
* @public
// 59
* @param {Object} [options]
// 60
* @param {String} [options.store] Name of the store to get from. If not defined,
the first store defined in `options.stores` for the collection on the client is
used.
* @param {Boolean} [options.auth=null] Add authentication token to the URL query
string? By default, a token for the current logged in user is added on the client.
Set this to `false` to omit the token. Set this to a string to provide your own
token. Set this to a number to specify an expiration time for the token in seconds.
* @param {Boolean} [options.download=false] Should headers be set to force a
download? Typically this means that clicking the link with this URL will download
the file to the user's Downloads folder instead of displaying the file in the
browser.
* @param {Boolean} [options.brokenIsFine=false] Return the URL even if we know
it's currently a broken link because the file hasn't been saved in the requested
store yet.
* @param {Boolean} [options.metadata=false] Return the URL for the file metadata
access point rather than the file itself.
* @param {String} [options.uploading=null] A URL to return while the file is being
uploaded. // 66
* @param {String} [options.storing=null] A URL to return while the file is being
stored. // 67
* @param {String} [options.filename=null] Override the filename that should appear
at the end of the URL. By default it is the name of the file in the requested
store.
*
// 69
* Returns the HTTP URL for getting the file or its metadata.
// 70
*/
// 71
FS.File.prototype.url = function(options) {
// 72
var self = this;
// 73
options = options || {};
// 74
options = FS.Utility.extend({
// 75
store: null,
// 76
auth: null,
// 77
download: false,
// 78
metadata: false,
// 79
brokenIsFine: false,
// 80
uploading: null, // return this URL while uploading
// 81
storing: null, // return this URL while storing
// 82
filename: null // override the filename that is shown to the user
// 83
}, options.hash || options); // check for "hash" prop if called as helper
// 84

// 85
// Primarily useful for displaying a temporary image while uploading an image
// 86
if (options.uploading && !self.isUploaded()) {
// 87
return options.uploading;
// 88
}
// 89

// 90
if (self.isMounted()) {
// 91
// See if we've stored in the requested store yet
// 92
var storeName = options.store || self.collection.primaryStore.name;
// 93
if (!self.hasStored(storeName)) {
// 94
if (options.storing) {
// 95
return options.storing;
// 96
} else if (!options.brokenIsFine) {
// 97
// We want to return null if we know the URL will be a broken
// 98
// link because then we can avoid rendering broken links, broken
// 99
// images, etc.
// 100
return null;
// 101
}
// 102
}
// 103

// 104
// Add filename to end of URL if we can determine one
// 105
var filename = options.filename || self.name({store: storeName});
// 106
if (typeof filename === "string" && filename.length) {
// 107
filename = '/' + filename;
// 108
} else {
// 109
filename = '';
// 110
}
// 111

// 112
// TODO: Could we somehow figure out if the collection requires login?
// 113
var authToken = '';
// 114
if (Meteor.isClient && typeof Accounts !== "undefined" && typeof
Accounts._storedLoginToken === "function") { // 115
if (options.auth !== false) {
// 116
// Add reactive deps on the user
// 117
Meteor.userId();
// 118

// 119
var authObject = {
// 120
authToken: Accounts._storedLoginToken() || ''
// 121
};
// 122

// 123
// If it's a number, we use that as the expiration time (in seconds)
// 124
if (options.auth === +options.auth) {
// 125
authObject.expiration = FS.HTTP.now() + options.auth * 1000;
// 126
}
// 127

// 128
// Set the authToken
// 129
var authString = JSON.stringify(authObject);
// 130
authToken = FS.Utility.btoa(authString);
// 131
}
// 132
} else if (typeof options.auth === "string") {
// 133
// If the user supplies auth token the user will be responsible for
// 134
// updating
// 135
authToken = options.auth;
// 136
}
// 137

// 138
// Construct query string
// 139
var params = {};
// 140
if (authToken !== '') {
// 141
params.token = authToken;
// 142
}
// 143
if (options.download) {
// 144
params.download = true;
// 145
}
// 146
if (options.store) {
// 147
// We use options.store here instead of storeName because we want to omit the
queryString // 148
// whenever possible, allowing users to have "clean" URLs if they want. The
server will // 149
// assume the first store defined on the server, which means that we are
assuming that // 150
// the first on the client is also the first on the server. If that's not the
case, the // 151
// store option should be supplied.
// 152
params.store = options.store;
// 153
}
// 154
var queryString = FS.Utility.encodeParams(params);
// 155
if (queryString.length) {
// 156
queryString = '?' + queryString;
// 157
}
// 158

// 159
// Determine which URL to use
// 160
var area;
// 161
if (options.metadata) {
// 162
area = '/record';
// 163
} else {
// 164
area = '/files';
// 165
}
// 166

// 167
// Construct and return the http method url
// 168
return rootUrlPathPrefix + baseUrl + area + '/' + self.collection.name + '/' +
self._id + filename + queryString; // 169
}
// 170

// 171
};
// 172

// 173

// 174

// 175
///////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////

}).call(this);

(function () {

///////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////
//
//
// packages/cfs:access-point/access-point-handlers.js
//
//
//
///////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////

//
getHeaders = [];
// 1
getHeadersByCollection = {};
// 2

// 3
FS.HTTP.Handlers = {};
// 4

// 5
/**
// 6
* @method FS.HTTP.Handlers.Del
// 7
* @public
// 8
* @returns {any} response
// 9
*
// 10
* HTTP DEL request handler
// 11
*/
// 12
FS.HTTP.Handlers.Del = function httpDelHandler(ref) {
// 13
var self = this;
// 14
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
// 15

// 16
// If DELETE request, validate with 'remove' allow/deny, delete the file, and
return // 17
FS.Utility.validateAction(ref.collection.files._validators['remove'], ref.file,
self.userId); // 18

// 19
/*
// 20
* From the DELETE spec:
// 21
* A successful response SHOULD be 200 (OK) if the response includes an
// 22
* entity describing the status, 202 (Accepted) if the action has not
// 23
* yet been enacted, or 204 (No Content) if the action has been enacted
// 24
* but the response does not include an entity.
// 25
*/
// 26
self.setStatusCode(200);
// 27

// 28
return {
// 29
deleted: !!ref.file.remove()
// 30
};
// 31
};
// 32

// 33
/**
// 34
* @method FS.HTTP.Handlers.GetList
// 35
* @public
// 36
* @returns {Object} response
// 37
*
// 38
* HTTP GET file list request handler
// 39
*/
// 40
FS.HTTP.Handlers.GetList = function httpGetListHandler() {
// 41
// Not Yet Implemented
// 42
// Need to check publications and return file list based on
// 43
// what user is allowed to see
// 44
};
// 45

// 46
/*
// 47
requestRange will parse the range set in request header - if not possible it
// 48
will throw fitting errors and autofill range for both partial and full ranges
// 49

// 50
throws error or returns the object:
// 51
{
// 52
start
// 53
end
// 54
length
// 55
unit
// 56
partial
// 57
}
// 58
*/
// 59
var requestRange = function(req, fileSize) {
// 60
if (req) {
// 61
if (req.headers) {
// 62
var rangeString = req.headers.range;
// 63

// 64
// Make sure range is a string
// 65
if (rangeString === ''+rangeString) {
// 66

// 67
// range will be in the format "bytes=0-32767"
// 68
var parts = rangeString.split('=');
// 69
var unit = parts[0];
// 70

// 71
// Make sure parts consists of two strings and range is of type "byte"
// 72
if (parts.length == 2 && unit == 'bytes') {
// 73
// Parse the range
// 74
var range = parts[1].split('-');
// 75
var start = Number(range[0]);
// 76
var end = Number(range[1]);
// 77

// 78
// Fix invalid ranges?
// 79
if (range[0] != start) start = 0;
// 80
if (range[1] != end || !end) end = fileSize - 1;
// 81

// 82
// Make sure range consists of a start and end point of numbers and start
is less than end // 83
if (start < end) {
// 84

// 85
var partSize = 0 - start + end + 1;
// 86

// 87
// Return the parsed range
// 88
return {
// 89
start: start,
// 90
end: end,
// 91
length: partSize,
// 92
size: fileSize,
// 93
unit: unit,
// 94
partial: (partSize < fileSize)
// 95
};
// 96

// 97
} else {
// 98
throw new Meteor.Error(416, "Requested Range Not Satisfiable");
// 99
}
// 100

// 101
} else {
// 102
// The first part should be bytes
// 103
throw new Meteor.Error(416, "Requested Range Unit Not Satisfiable");
// 104
}
// 105

// 106
} else {
// 107
// No range found
// 108
}
// 109

// 110
} else {
// 111
// throw new Error('No request headers set for _parseRange function');
// 112
}
// 113
} else {
// 114
throw new Error('No request object passed to _parseRange function');
// 115
}
// 116

// 117
return {
// 118
start: 0,
// 119
end: fileSize - 1,
// 120
length: fileSize,
// 121
size: fileSize,
// 122
unit: 'bytes',
// 123
partial: false
// 124
};
// 125
};
// 126

// 127
/**
// 128
* @method FS.HTTP.Handlers.Get
// 129
* @public
// 130
* @returns {any} response
// 131
*
// 132
* HTTP GET request handler
// 133
*/
// 134
FS.HTTP.Handlers.Get = function httpGetHandler(ref) {
// 135
var self = this;
// 136
// Once we have the file, we can test allow/deny validators
// 137
// XXX: pass on the "share" query eg. ?share=342hkjh23ggj for shared url access?
// 138
FS.Utility.validateAction(ref.collection._validators['download'], ref.file,
self.userId /*, self.query.shareId*/); // 139

// 140
var storeName = ref.storeName;
// 141

// 142
// If no storeName was specified, use the first defined storeName
// 143
if (typeof storeName !== "string") {
// 144
// No store handed, we default to primary store
// 145
storeName = ref.collection.primaryStore.name;
// 146
}
// 147

// 148
// Get the storage reference
// 149
var storage = ref.collection.storesLookup[storeName];
// 150

// 151
if (!storage) {
// 152
throw new Meteor.Error(404, "Not Found", 'There is no store "' + storeName +
'"'); // 153
}
// 154

// 155
// Get the file
// 156
var copyInfo = ref.file.copies[storeName];
// 157

// 158
if (!copyInfo) {
// 159
throw new Meteor.Error(404, "Not Found", 'This file was not stored in the ' +
storeName + ' store'); // 160
}
// 161

// 162
// Set the content type for file
// 163
if (typeof copyInfo.type === "string") {
// 164
self.setContentType(copyInfo.type);
// 165
} else {
// 166
self.setContentType('application/octet-stream');
// 167
}
// 168

// 169
// Add 'Content-Disposition' header if requested a download/attachment URL
// 170
if (typeof ref.download !== "undefined") {
// 171
var filename = ref.filename || copyInfo.name;
// 172
self.addHeader('Content-Disposition', 'attachment; filename="' + filename +
'"'); // 173
} else {
// 174
self.addHeader('Content-Disposition', 'inline');
// 175
}
// 176

// 177
// Get the contents range from request
// 178
var range = requestRange(self.request, copyInfo.size);
// 179

// 180
// Some browsers cope better if the content-range header is
// 181
// still included even for the full file being returned.
// 182
self.addHeader('Content-Range', range.unit + ' ' + range.start + '-' + range.end
+ '/' + range.size); // 183

// 184
// If a chunk/range was requested instead of the whole file, serve that'
// 185
if (range.partial) {
// 186
self.setStatusCode(206, 'Partial Content');
// 187
} else {
// 188
self.setStatusCode(200, 'OK');
// 189
}
// 190

// 191
// Add any other global custom headers and collection-specific custom headers
// 192
FS.Utility.each(getHeaders.concat(getHeadersByCollection[ref.collection.name] ||
[]), function(header) { // 193
self.addHeader(header[0], header[1]);
// 194
});
// 195

// 196
// Inform clients about length (or chunk length in case of ranges)
// 197
self.addHeader('Content-Length', range.length);
// 198

// 199
// Last modified header (updatedAt from file info)
// 200
self.addHeader('Last-Modified', copyInfo.updatedAt.toUTCString());
// 201

// 202
// Inform clients that we accept ranges for resumable chunked downloads
// 203
self.addHeader('Accept-Ranges', range.unit);
// 204

// 205
if (FS.debug) console.log('Read file "' + (ref.filename || copyInfo.name) + '" '
+ range.unit + ' ' + range.start + '-' + range.end + '/' + range.size);

// 207
var readStream = storage.adapter.createReadStream(ref.file, {start: range.start,
end: range.end}); // 208

// 209
readStream.on('error', function(err) {
// 210
// Send proper error message on get error
// 211
if (err.message && err.statusCode) {
// 212
self.Error(new Meteor.Error(err.statusCode, err.message));
// 213
} else {
// 214
self.Error(new Meteor.Error(503, 'Service unavailable'));
// 215
}
// 216
});
// 217

// 218
readStream.pipe(self.createWriteStream());
// 219
};
// 220

const originalHandler = FS.HTTP.Handlers.Get;


FS.HTTP.Handlers.Get = function (ref) {
//console.log(ref.filename);
try {
var userAgent = (this.requestHeaders['user-agent']||'').toLowerCase();

if(userAgent.indexOf('msie') >= 0 || userAgent.indexOf('trident') >= 0 ||


userAgent.indexOf('chrome') >= 0) {
ref.filename = encodeURIComponent(ref.filename);
} else if(userAgent.indexOf('firefox') >= 0) {
ref.filename = new Buffer(ref.filename).toString('binary');
} else {
/* safari*/
ref.filename = new Buffer(ref.filename).toString('binary');
}
} catch (ex){
ref.filename = 'tempfix';
}
return originalHandler.call(this, ref);
};

// 221
/**
// 222
* @method FS.HTTP.Handlers.PutInsert
// 223
* @public
// 224
* @returns {Object} response object with _id property
// 225
*
// 226
* HTTP PUT file insert request handler
// 227
*/
// 228
FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) {
// 229
var self = this;
// 230
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
// 231

// 232
FS.debug && console.log("HTTP PUT (insert) handler");
// 233

// 234
// Create the nice FS.File
// 235
var fileObj = new FS.File();
// 236

// 237
// Set its name
// 238
fileObj.name(opts.filename || null);
// 239

// 240
// Attach the readstream as the file's data
// 241
fileObj.attachData(self.createReadStream(), {type: self.requestHeaders['content-
type'] || 'application/octet-stream'});

// 243
// Validate with insert allow/deny
// 244
FS.Utility.validateAction(ref.collection.files._validators['insert'], fileObj,
self.userId); // 245

// 246
// Insert file into collection, triggering readStream storage
// 247
ref.collection.insert(fileObj);
// 248

// 249
// Send response
// 250
self.setStatusCode(200);
// 251

// 252
// Return the new file id
// 253
return {_id: fileObj._id};
// 254
};
// 255

// 256
/**
// 257
* @method FS.HTTP.Handlers.PutUpdate
// 258
* @public
// 259
* @returns {Object} response object with _id and chunk properties
// 260
*
// 261
* HTTP PUT file update chunk request handler
// 262
*/
// 263
FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) {
// 264
var self = this;
// 265
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
// 266

// 267
var chunk = parseInt(opts.chunk, 10);
// 268
if (isNaN(chunk)) chunk = 0;
// 269

// 270
FS.debug && console.log("HTTP PUT (update) handler received chunk: ", chunk);
// 271

// 272
// Validate with insert allow/deny; also mounts and retrieves the file
// 273
FS.Utility.validateAction(ref.collection.files._validators['insert'], ref.file,
self.userId); // 274

// 275
self.createReadStream().pipe( FS.TempStore.createWriteStream(ref.file, chunk) );
// 276

// 277
// Send response
// 278
self.setStatusCode(200);
// 279

// 280
return { _id: ref.file._id, chunk: chunk };
// 281
};
// 282

// 283
///////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////

}).call(this);

(function () {

///////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////
//
//
// packages/cfs:access-point/access-point-server.js
//
//
//
///////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////

//
var path = Npm.require("path");
// 1

// 2
HTTP.publishFormats({
// 3
fileRecordFormat: function (input) {
// 4
// Set the method scope content type to json
// 5
this.setContentType('application/json');
// 6
if (FS.Utility.isArray(input)) {
// 7
return EJSON.stringify(FS.Utility.map(input, function (obj) {
// 8
return FS.Utility.cloneFileRecord(obj);
// 9
}));
// 10
} else {
// 11
return EJSON.stringify(FS.Utility.cloneFileRecord(input));
// 12
}
// 13
}
// 14
});
// 15

// 16
/**
// 17
* @method FS.HTTP.setHeadersForGet
// 18
* @public
// 19
* @param {Array} headers - List of headers, where each is a two-item array in
which item 1 is the header name and item 2 is the header value.
* @param {Array|String} [collections] - Which collections the headers should be
added for. Omit this argument to add the header for all collections.
* @returns {undefined}
// 22
*/
// 23
FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) {
// 24
if (typeof collections === "string") {
// 25
collections = [collections];
// 26
}
// 27
if (collections) {
// 28
FS.Utility.each(collections, function(collectionName) {
// 29
getHeadersByCollection[collectionName] = headers || [];
// 30
});
// 31
} else {
// 32
getHeaders = headers || [];
// 33
}
// 34
};
// 35

// 36
/**
// 37
* @method FS.HTTP.publish
// 38
* @public
// 39
* @param {FS.Collection} collection
// 40
* @param {Function} func - Publish function that returns a cursor.
// 41
* @returns {undefined}
// 42
*
// 43
* Publishes all documents returned by the cursor at a GET URL
// 44
* with the format baseUrl/record/collectionName. The publish
// 45
* function `this` is similar to normal `Meteor.publish`.
// 46
*/
// 47
FS.HTTP.publish = function fsHttpPublish(collection, func) {
// 48
var name = baseUrl + '/record/' + collection.name;
// 49
// Mount collection listing URL using http-publish package
// 50
HTTP.publish({
// 51
name: name,
// 52
defaultFormat: 'fileRecordFormat',
// 53
collection: collection,
// 54
collectionGet: true,
// 55
collectionPost: false,
// 56
documentGet: true,
// 57
documentPut: false,
// 58
documentDelete: false
// 59
}, func);
// 60

// 61
FS.debug && console.log("Registered HTTP method GET URLs:\n\n" + name + '\n' +
name + '/:id\n'); // 62
};
// 63

// 64
/**
// 65
* @method FS.HTTP.unpublish
// 66
* @public
// 67
* @param {FS.Collection} collection
// 68
* @returns {undefined}
// 69
*
// 70
* Unpublishes a restpoint created by a call to `FS.HTTP.publish`
// 71
*/
// 72
FS.HTTP.unpublish = function fsHttpUnpublish(collection) {
// 73
// Mount collection listing URL using http-publish package
// 74
HTTP.unpublish(baseUrl + '/record/' + collection.name);
// 75
};
// 76

// 77
_existingMountPoints = {};
// 78

// 79
/**
// 80
* @method defaultSelectorFunction
// 81
* @private
// 82
* @returns { collection, file }
// 83
*
// 84
* This is the default selector function
// 85
*/
// 86
var defaultSelectorFunction = function() {
// 87
var self = this;
// 88
// Selector function
// 89
//
// 90
// This function will have to return the collection and the
// 91
// file. If file not found undefined is returned - if null is returned the
// 92
// search was not possible
// 93
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
// 94

// 95
// Get the collection name from the url
// 96
var collectionName = opts.collectionName;
// 97

// 98
// Get the id from the url
// 99
var id = opts.id;
// 100

// 101
// Get the collection
// 102
var collection = FS._collections[collectionName];
// 103

// 104
// Get the file if possible else return null
// 105
var file = (id && collection)? collection.findOne({ _id: id }): null;
// 106

// 107
// Return the collection and the file
// 108
return {
// 109
collection: collection,
// 110
file: file,
// 111
storeName: opts.store,
// 112
download: opts.download,
// 113
filename: opts.filename
// 114
};
// 115
};
// 116

// 117
/*
// 118
* @method FS.HTTP.mount
// 119
* @public
// 120
* @param {array of string} mountPoints mount points to map rest functinality on
// 121
* @param {function} selector_f [selector] function returns `{ collection, file }`
for mount points to work with // 122
*
// 123
*/
// 124
FS.HTTP.mount = function(mountPoints, selector_f) {
// 125
// We take mount points as an array and we get a selector function
// 126
var selectorFunction = selector_f || defaultSelectorFunction;
// 127

// 128
var accessPoint = {
// 129
'stream': true,
// 130
'auth': expirationAuth,
// 131
'post': function(data) {
// 132
// Use the selector for finding the collection and file reference
// 133
var ref = selectorFunction.call(this);
// 134
// 135
// We dont support post - this would be normal insert eg. of filerecord?
// 136
throw new Meteor.Error(501, "Not implemented", "Post is not supported");
// 137
},
// 138
'put': function(data) {
// 139
// Use the selector for finding the collection and file reference
// 140
var ref = selectorFunction.call(this);
// 141

// 142
// Make sure we have a collection reference
// 143
if (!ref.collection)
// 144
throw new Meteor.Error(404, "Not Found", "No collection found");
// 145

// 146
// Make sure we have a file reference
// 147
if (ref.file === null) {
// 148
// No id supplied so we will create a new FS.File instance and
// 149
// insert the supplied data.
// 150
return FS.HTTP.Handlers.PutInsert.apply(this, [ref]);
// 151
} else {
// 152
if (ref.file) {
// 153
return FS.HTTP.Handlers.PutUpdate.apply(this, [ref]);
// 154
} else {
// 155
throw new Meteor.Error(404, "Not Found", 'No file found');
// 156
}
// 157
}
// 158
},
// 159
'get': function(data) {
// 160
// Use the selector for finding the collection and file reference
// 161
var ref = selectorFunction.call(this);
// 162

// 163
// Make sure we have a collection reference
// 164
if (!ref.collection)
// 165
throw new Meteor.Error(404, "Not Found", "No collection found");
// 166

// 167
// Make sure we have a file reference
// 168
if (ref.file === null) {
// 169
// No id supplied so we will return the published list of files ala
// 170
// http.publish in json format
// 171
return FS.HTTP.Handlers.GetList.apply(this, [ref]);
// 172
} else {
// 173
if (ref.file) {
// 174
return FS.HTTP.Handlers.Get.apply(this, [ref]);
// 175
} else {
// 176
throw new Meteor.Error(404, "Not Found", 'No file found');
// 177
}
// 178
}
// 179
},
// 180
'delete': function(data) {
// 181
// Use the selector for finding the collection and file reference
// 182
var ref = selectorFunction.call(this);
// 183

// 184
// Make sure we have a collection reference
// 185
if (!ref.collection)
// 186
throw new Meteor.Error(404, "Not Found", "No collection found");
// 187

// 188
// Make sure we have a file reference
// 189
if (ref.file) {
// 190
return FS.HTTP.Handlers.Del.apply(this, [ref]);
// 191
} else {
// 192
throw new Meteor.Error(404, "Not Found", 'No file found');
// 193
}
// 194
}
// 195
};
// 196

// 197
var accessPoints = {};
// 198

// 199
// Add debug message
// 200
FS.debug && console.log('Registered HTTP method URLs:');
// 201

// 202
FS.Utility.each(mountPoints, function(mountPoint) {
// 203
// Couple mountpoint and accesspoint
// 204
accessPoints[mountPoint] = accessPoint;
// 205
// Remember our mountpoints
// 206
_existingMountPoints[mountPoint] = mountPoint;
// 207
// Add debug message
// 208
FS.debug && console.log(mountPoint);
// 209
});
// 210

// 211
// XXX: HTTP:methods should unmount existing mounts in case of overwriting?
// 212
HTTP.methods(accessPoints);
// 213

// 214
};
// 215

// 216
/**
// 217
* @method FS.HTTP.unmount
// 218
* @public
// 219
* @param {string | array of string} [mountPoints] Optional, if not specified all
mountpoints are unmounted // 220
*
// 221
*/
// 222
FS.HTTP.unmount = function(mountPoints) {
// 223
// The mountPoints is optional, can be string or array if undefined then
// 224
// _existingMountPoints will be used
// 225
var unmountList;
// 226
// Container for the mount points to unmount
// 227
var unmountPoints = {};
// 228

// 229
if (typeof mountPoints === 'undefined') {
// 230
// Use existing mount points - unmount all
// 231
unmountList = _existingMountPoints;
// 232
} else if (mountPoints === ''+mountPoints) {
// 233
// Got a string
// 234
unmountList = [mountPoints];
// 235
} else if (mountPoints.length) {
// 236
// Got an array
// 237
unmountList = mountPoints;
// 238
}
// 239

// 240
// If we have a list to unmount
// 241
if (unmountList) {
// 242
// Iterate over each item
// 243
FS.Utility.each(unmountList, function(mountPoint) {
// 244
// Check _existingMountPoints to make sure the mount point exists in our
// 245
// context / was created by the FS.HTTP.mount
// 246
if (_existingMountPoints[mountPoint]) {
// 247
// Mark as unmount
// 248
unmountPoints[mountPoint] = false;
// 249
// Release
// 250
delete _existingMountPoints[mountPoint];
// 251
}
// 252
});
// 253
FS.debug && console.log('FS.HTTP.unmount:');
// 254
FS.debug && console.log(unmountPoints);
// 255
// Complete unmount
// 256
HTTP.methods(unmountPoints);
// 257
}
// 258
};
// 259

// 260
// ### FS.Collection maps on HTTP pr. default on the following restpoints:
// 261
// *
// 262
// baseUrl + '/files/:collectionName/:id/:filename',
// 263
// baseUrl + '/files/:collectionName/:id',
// 264
// baseUrl + '/files/:collectionName'
// 265
//
// 266
// Change/ replace the existing mount point by:
// 267
// ```js
// 268
// // unmount all existing
// 269
// FS.HTTP.unmount();
// 270
// // Create new mount point
// 271
// FS.HTTP.mount([
// 272
// '/cfs/files/:collectionName/:id/:filename',
// 273
// '/cfs/files/:collectionName/:id',
// 274
// '/cfs/files/:collectionName'
// 275
// ]);
// 276
// ```
// 277
//
// 278
mountUrls = function mountUrls() {
// 279
// We unmount first in case we are calling this a second time
// 280
FS.HTTP.unmount();
// 281
// 282
FS.HTTP.mount([
// 283
baseUrl + '/files/:collectionName/:id/:filename',
// 284
baseUrl + '/files/:collectionName/:id',
// 285
baseUrl + '/files/:collectionName'
// 286
]);
// 287
};
// 288

// 289
// Returns the userId from URL token
// 290
var expirationAuth = function expirationAuth() {
// 291
var self = this;
// 292

// 293
// Read the token from '/hello?token=base64'
// 294
var encodedToken = self.query.token;
// 295

// 296
FS.debug && console.log("token: "+encodedToken);
// 297

// 298
if (!encodedToken || !Meteor.users) return false;
// 299

// 300
// Check the userToken before adding it to the db query
// 301
// Set the this.userId
// 302
var tokenString = FS.Utility.atob(encodedToken);
// 303

// 304
var tokenObject;
// 305
try {
// 306
tokenObject = JSON.parse(tokenString);
// 307
} catch(err) {
// 308
throw new Meteor.Error(400, 'Bad Request');
// 309
}
// 310

// 311
// XXX: Do some check here of the object
// 312
var userToken = tokenObject.authToken;
// 313
if (userToken !== ''+userToken) {
// 314
throw new Meteor.Error(400, 'Bad Request');
// 315
}
// 316

// 317
// If we have an expiration token we should check that it's still valid
// 318
if (tokenObject.expiration != null) {
// 319
// check if its too old
// 320
var now = Date.now();
// 321
if (tokenObject.expiration < now) {
// 322
FS.debug && console.log('Expired token: ' + tokenObject.expiration + ' is
less than ' + now); // 323
throw new Meteor.Error(500, 'Expired token');
// 324
}
// 325
}
// 326

// 327
// We are not on a secure line - so we have to look up the user...
// 328
var user = Meteor.users.findOne({
// 329
$or: [
// 330
{'services.resume.loginTokens.hashedToken':
Accounts._hashLoginToken(userToken)}, // 331
{'services.resume.loginTokens.token': userToken}
// 332
]
// 333
});
// 334

// 335
// Set the userId in the scope
// 336
return user && user._id;
// 337
};
// 338

// 339
HTTP.methods(
// 340
{'/cfs/servertime': {
// 341
get: function(data) {
// 342
return Date.now().toString();
// 343
}
// 344
}
// 345
});
// 346

// 347
// Unify client / server api
// 348
FS.HTTP.now = function() {
// 349
return Date.now();
// 350
};
// 351

// 352
// Start up the basic mount points
// 353
Meteor.startup(function () {
// 354
mountUrls();
// 355
});
// 356

// 357
///////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////

}).call(this);

///////////////////////////////////////////////////////////////////////

}).call(this);

/* Exports */
if (typeof Package === 'undefined') Package = {};
Package['cfs:access-point'] = {};

})();