filters.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. /**
  2. * @method FS.Collection.prototype.filters
  3. * @public
  4. * @param {Object} filters - File filters for this collection.
  5. * @returns {undefined}
  6. */
  7. FS.Collection.prototype.filters = function fsColFilters(filters) {
  8. var self = this;
  9. // Check filter option values and normalize them for quicker checking later
  10. if (filters) {
  11. // check/adjust allow/deny
  12. FS.Utility.each(['allow', 'deny'], function (type) {
  13. if (!filters[type]) {
  14. filters[type] = {};
  15. } else if (typeof filters[type] !== "object") {
  16. throw new Error(type + ' filter must be an object');
  17. }
  18. });
  19. // check/adjust maxSize
  20. if (typeof filters.maxSize === "undefined") {
  21. filters.maxSize = null;
  22. } else if (filters.maxSize && typeof filters.maxSize !== "number") {
  23. throw new Error('maxSize filter must be an number');
  24. }
  25. // check/adjust extensions
  26. FS.Utility.each(['allow', 'deny'], function (type) {
  27. if (!filters[type].extensions) {
  28. filters[type].extensions = [];
  29. } else if (!FS.Utility.isArray(filters[type].extensions)) {
  30. throw new Error(type + '.extensions filter must be an array of extensions');
  31. } else {
  32. //convert all to lowercase
  33. for (var i = 0, ln = filters[type].extensions.length; i < ln; i++) {
  34. filters[type].extensions[i] = filters[type].extensions[i].toLowerCase();
  35. }
  36. }
  37. });
  38. // check/adjust content types
  39. FS.Utility.each(['allow', 'deny'], function (type) {
  40. if (!filters[type].contentTypes) {
  41. filters[type].contentTypes = [];
  42. } else if (!FS.Utility.isArray(filters[type].contentTypes)) {
  43. throw new Error(type + '.contentTypes filter must be an array of content types');
  44. }
  45. });
  46. self.options.filter = filters;
  47. }
  48. // Define deny functions to enforce file filters on the server
  49. // for inserts and updates that initiate from untrusted code.
  50. self.files.deny({
  51. insert: function(userId, fsFile) {
  52. return !self.allowsFile(fsFile);
  53. },
  54. update: function(userId, fsFile, fields, modifier) {
  55. // TODO will need some kind of additional security here:
  56. // Don't allow them to change the type, size, name, and
  57. // anything else that would be security or data integrity issue.
  58. // Such security should probably be added by cfs-collection package, not here.
  59. return !self.allowsFile(fsFile);
  60. },
  61. fetch: []
  62. });
  63. // If insecure package is in use, we need to add allow rules that return
  64. // true. Otherwise, it would seemingly turn off insecure mode.
  65. if (Package && Package.insecure) {
  66. self.allow({
  67. insert: function() {
  68. return true;
  69. },
  70. update: function() {
  71. return true;
  72. },
  73. remove: function() {
  74. return true;
  75. },
  76. download: function() {
  77. return true;
  78. },
  79. fetch: [],
  80. transform: null
  81. });
  82. }
  83. // If insecure package is NOT in use, then adding the deny function
  84. // does not have any effect on the main app's security paradigm. The
  85. // user will still be required to add at least one allow function of her
  86. // own for each operation for this collection. And the user may still add
  87. // additional deny functions, but does not have to.
  88. };
  89. /**
  90. * @method FS.Collection.prototype.allowsFile Does the collection allow the specified file?
  91. * @public
  92. * @returns {boolean} True if the collection allows this file.
  93. *
  94. * Checks based on any filters defined on the collection. If the
  95. * file is not valid according to the filters, this method returns false
  96. * and also calls the filter `onInvalid` method defined for the
  97. * collection, passing it an English error string that explains why it
  98. * failed.
  99. */
  100. FS.Collection.prototype.allowsFile = function fsColAllowsFile(fileObj) {
  101. var self = this;
  102. // Get filters
  103. var filter = self.options.filter;
  104. if (!filter) {
  105. return true;
  106. }
  107. var saveAllFileExtensions = (filter.allow.extensions.length === 0);
  108. var saveAllContentTypes = (filter.allow.contentTypes.length === 0);
  109. // Get info about the file
  110. var filename = fileObj.name();
  111. var contentType = fileObj.type();
  112. if (!saveAllContentTypes && !contentType) {
  113. filter.onInvalid && filter.onInvalid(filename + " has an unknown content type");
  114. return false;
  115. }
  116. var fileSize = fileObj.size();
  117. if (!fileSize || isNaN(fileSize)) {
  118. filter.onInvalid && filter.onInvalid(filename + " has an unknown file size");
  119. return false;
  120. }
  121. // Do extension checks only if we have a filename
  122. if (filename) {
  123. var ext = fileObj.getExtension();
  124. if (!((saveAllFileExtensions ||
  125. FS.Utility.indexOf(filter.allow.extensions, ext) !== -1) &&
  126. FS.Utility.indexOf(filter.deny.extensions, ext) === -1)) {
  127. filter.onInvalid && filter.onInvalid(filename + ' has the extension "' + ext + '", which is not allowed');
  128. return false;
  129. }
  130. }
  131. // Do content type checks
  132. if (!((saveAllContentTypes ||
  133. contentTypeInList(filter.allow.contentTypes, contentType)) &&
  134. !contentTypeInList(filter.deny.contentTypes, contentType))) {
  135. filter.onInvalid && filter.onInvalid(filename + ' is of the type "' + contentType + '", which is not allowed');
  136. return false;
  137. }
  138. // Do max size check
  139. if (typeof filter.maxSize === "number" && fileSize > filter.maxSize) {
  140. filter.onInvalid && filter.onInvalid(filename + " is too big");
  141. return false;
  142. }
  143. return true;
  144. };
  145. /**
  146. * @method contentTypeInList Is the content type string in the list?
  147. * @private
  148. * @param {String[]} list - Array of content types
  149. * @param {String} contentType - The content type
  150. * @returns {Boolean}
  151. *
  152. * Returns true if the content type is in the list, or if it matches
  153. * one of the special types in the list, e.g., "image/*".
  154. */
  155. function contentTypeInList(list, contentType) {
  156. var listType, found = false;
  157. for (var i = 0, ln = list.length; i < ln; i++) {
  158. listType = list[i];
  159. if (listType === contentType) {
  160. found = true;
  161. break;
  162. }
  163. if (listType === "image/*" && contentType.indexOf("image/") === 0) {
  164. found = true;
  165. break;
  166. }
  167. if (listType === "audio/*" && contentType.indexOf("audio/") === 0) {
  168. found = true;
  169. break;
  170. }
  171. if (listType === "video/*" && contentType.indexOf("video/") === 0) {
  172. found = true;
  173. break;
  174. }
  175. }
  176. return found;
  177. }