alchemymvc 1.4.0-alpha.6 → 1.4.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1745,12 +1745,43 @@ Alchemy.setMethod(function addAppcacheEntry(entry) {
1745
1745
  ac_entries[entry.type].push(entry);
1746
1746
  });
1747
1747
 
1748
+ /**
1749
+ * Get a size limit for the given route
1750
+ *
1751
+ * @author Jelle De Loecker <jelle@elevenways.be>
1752
+ * @since 1.4.0
1753
+ * @version 1.4.0
1754
+ *
1755
+ * @param {Route?} route
1756
+ * @param {string} name
1757
+ */
1758
+ function getRouteSizeLimit(route, name) {
1759
+
1760
+ let global_size = alchemy.settings.network[name];
1761
+
1762
+ if (!route) {
1763
+ return global_size;
1764
+ }
1765
+
1766
+ let route_value = route.options[name];
1767
+
1768
+ if (route_value == null || typeof route_value != 'number') {
1769
+ return global_size;
1770
+ }
1771
+
1772
+ if (route_value <= 0) {
1773
+ return Infinity;
1774
+ }
1775
+
1776
+ return route_value;
1777
+ }
1778
+
1748
1779
  /**
1749
1780
  * Get the body of an IncomingMessage
1750
1781
  *
1751
1782
  * @author Jelle De Loecker <jelle@elevenways.be>
1752
1783
  * @since 1.1.0
1753
- * @version 1.3.18
1784
+ * @version 1.4.0
1754
1785
  *
1755
1786
  * @param {IncomingMessage} req
1756
1787
  * @param {OutgoingMessage} res Optional
@@ -1777,24 +1808,44 @@ Alchemy.setMethod(function parseRequestBody(req, res, callback) {
1777
1808
 
1778
1809
  let content_type = req.headers['content-type'];
1779
1810
 
1811
+ let request_body_size_limit = getRouteSizeLimit(conduit?.route, 'request_body_size_limit');
1812
+
1780
1813
  // Multipart data is handled by "formidable"
1781
1814
  if (content_type && content_type.startsWith('multipart/form-data')) {
1782
1815
 
1816
+ let request_individual_file_size_limit = getRouteSizeLimit(conduit?.route, 'request_individual_file_size_limit'),
1817
+ request_total_file_size_limit = getRouteSizeLimit(conduit?.route, 'request_total_file_size_limit');
1818
+
1783
1819
  let form = new this.formidable.IncomingForm({
1784
1820
  multiples : true,
1785
1821
  hashAlgorithm : this.settings.data_management.file_hash_algorithm || 'sha1',
1822
+ minFileSize : 0,
1823
+ allowEmptyFiles : true,
1824
+ maxFileSize : request_individual_file_size_limit,
1825
+ maxFieldsSize : request_body_size_limit,
1826
+ maxTotalFileSize : request_total_file_size_limit,
1786
1827
  });
1787
1828
 
1788
1829
  form.parse(req, function parsedMultipart(err, form_fields, form_files) {
1789
1830
 
1790
- var fields = {},
1791
- files = {},
1792
- key;
1831
+ if (err) {
1832
+
1833
+ // Ignore the error if the request was already aborted
1834
+ if (conduit?.aborted) {
1835
+ return callback(null);
1836
+ }
1793
1837
 
1794
- if (err && req.conduit && req.conduit.aborted) {
1795
- return callback(null);
1838
+ if (conduit) {
1839
+ return conduit.error(err);
1840
+ }
1841
+
1842
+ return callback(err);
1796
1843
  }
1797
1844
 
1845
+ let fields = {},
1846
+ files = {},
1847
+ key;
1848
+
1798
1849
  // Since formidable v3, all the fields are now arrays.
1799
1850
  // We already had a lot of logic to deal with this,
1800
1851
  // so we just have to un-array everything
@@ -1811,18 +1862,12 @@ Alchemy.setMethod(function parseRequestBody(req, res, callback) {
1811
1862
  Object.setFormPath(files, key, form_files[key]);
1812
1863
  }
1813
1864
 
1814
- if (err) {
1815
- log.error('Error parsing multipart POST', {err: err});
1816
- req.body = {};
1817
- req.files = {};
1818
- } else {
1819
- req.body = fields;
1820
- req.files = files;
1865
+ req.body = fields;
1866
+ req.files = files;
1821
1867
 
1822
- if (conduit) {
1823
- conduit.setRequestBody(fields);
1824
- conduit.setRequestFiles(files);
1825
- }
1868
+ if (conduit) {
1869
+ conduit.setRequestBody(fields);
1870
+ conduit.setRequestFiles(files);
1826
1871
  }
1827
1872
 
1828
1873
  callback(null, fields);
@@ -1834,24 +1879,29 @@ Alchemy.setMethod(function parseRequestBody(req, res, callback) {
1834
1879
  // Regular form-encoded data
1835
1880
  if (content_type && content_type.indexOf('form-urlencoded') > -1) {
1836
1881
 
1837
- this.url_form_body(req, res, function parsedBody(err) {
1882
+ this.url_form_body(req, res, {limit: request_body_size_limit}, function parsedBody(err) {
1883
+
1884
+ if (err) {
1885
+
1886
+ // Ignore the error if the request was already aborted
1887
+ if (conduit?.aborted) {
1888
+ return callback(null);
1889
+ }
1890
+
1891
+ if (conduit) {
1892
+ return conduit.error(err);
1893
+ }
1838
1894
 
1839
- if (err && req.conduit && req.conduit.aborted) {
1840
- return callback(null);
1895
+ return callback(err);
1841
1896
  }
1842
1897
 
1843
1898
  // You can't send files using a regular post
1844
1899
  req.files = {};
1845
1900
 
1846
- if (err) {
1847
- log.error('Error parsing x-www-form-urlencoded body data', {err: err});
1848
- req.body = {};
1849
- } else {
1850
- req.body = req.body;
1901
+ req.body = req.body;
1851
1902
 
1852
- if (conduit) {
1853
- conduit.setRequestBody(req.body);
1854
- }
1903
+ if (conduit) {
1904
+ conduit.setRequestBody(req.body);
1855
1905
  }
1856
1906
 
1857
1907
  callback(null, req.body);
@@ -1861,25 +1911,30 @@ Alchemy.setMethod(function parseRequestBody(req, res, callback) {
1861
1911
  }
1862
1912
 
1863
1913
  // Any other encoded data (like JSON)
1864
- this.any_body(req, function parsedBody(err, body) {
1914
+ this.any_body(req, res, {limit: request_body_size_limit}, function parsedBody(err, body) {
1865
1915
 
1866
1916
  function handleResponse(err, body) {
1867
- if (err && req.conduit && req.conduit.aborted) {
1868
- return callback(null);
1917
+
1918
+ if (err) {
1919
+
1920
+ // Ignore the error if the request was already aborted
1921
+ if (conduit?.aborted) {
1922
+ return callback(null);
1923
+ }
1924
+
1925
+ if (conduit) {
1926
+ return conduit.error(err);
1927
+ }
1928
+
1929
+ return callback(err);
1869
1930
  }
1870
-
1931
+
1871
1932
  // You can't send files using a regular post
1872
1933
  req.files = {};
1934
+ req.body = body;
1873
1935
 
1874
- if (err) {
1875
- log.error('Error parsing body data', {err: err});
1876
- req.body = {};
1877
- } else {
1878
- req.body = body;
1879
-
1880
- if (conduit) {
1881
- conduit.setRequestBody(body);
1882
- }
1936
+ if (conduit) {
1937
+ conduit.setRequestBody(body);
1883
1938
  }
1884
1939
 
1885
1940
  callback(null, req.body);
@@ -1,6 +1,7 @@
1
1
  let mkdirp = alchemy.use('mkdirp')?.mkdirp,
2
2
  ncp = alchemy.use('ncp').ncp,
3
3
  fs = alchemy.use('fs'),
4
+ fsp = fs.promises,
4
5
  libpath = alchemy.use('path'),
5
6
  child = alchemy.use('child_process'),
6
7
  crypto = alchemy.use('crypto'),
@@ -1106,6 +1107,53 @@ Alchemy.setMethod(function request(url, options, callback) {
1106
1107
  return Blast.fetch(options, callback);
1107
1108
  });
1108
1109
 
1110
+ /**
1111
+ * Turn a `data:` uri into a file
1112
+ *
1113
+ * @author Jelle De Loecker <jelle@elevenways.be>
1114
+ * @since 1.4.0
1115
+ * @version 1.4.0
1116
+ *
1117
+ * @param {string} data_uri
1118
+ *
1119
+ * @return {Pledge<File>}
1120
+ */
1121
+ function convertDataUriToFile(data_uri) {
1122
+
1123
+ // Split the data uri into its 2 parts
1124
+ let pieces = data_uri.slice(5).split(',');
1125
+
1126
+ // Split the info bit
1127
+ let info = pieces[0].split(';');
1128
+
1129
+ // Get the expected mime type
1130
+ let mime_type = info[0];
1131
+
1132
+ // And is it base64?
1133
+ let is_b64 = info[1] && info[1].toLowerCase() == 'base64';
1134
+
1135
+ let buffer;
1136
+
1137
+ if (is_b64) {
1138
+ buffer = Buffer.from(pieces[1], 'base64');
1139
+ } else {
1140
+ buffer = Buffer.from(String.decodeURI(pieces[1]));
1141
+ }
1142
+
1143
+ return Pledge.Swift.waterfall(
1144
+ () => Blast.createTempDir({prefix: 'aldl'}),
1145
+ async (temp_dir) => {
1146
+ let full_path = libpath.resolve(temp_dir, 'buffer_' + alchemy.ObjectId());
1147
+
1148
+ await fsp.writeFile(full_path, buffer);
1149
+
1150
+ return full_path;
1151
+ },
1152
+ (full_path) => Classes.Alchemy.Inode.Inode.from(full_path)
1153
+ );
1154
+
1155
+ }
1156
+
1109
1157
  /**
1110
1158
  * Download a file
1111
1159
  *
@@ -1119,6 +1167,14 @@ Alchemy.setMethod(function request(url, options, callback) {
1119
1167
  */
1120
1168
  Alchemy.setMethod(function download(url, options) {
1121
1169
 
1170
+ if (url.startsWith('data:')) {
1171
+ try {
1172
+ return convertDataUriToFile(url, options);
1173
+ } catch (err) {
1174
+ return Pledge.reject(err);
1175
+ }
1176
+ }
1177
+
1122
1178
  const pledge = new Pledge();
1123
1179
 
1124
1180
  // Get the file
@@ -73,6 +73,24 @@ network.addSetting('use_compression', {
73
73
  description : 'Compress responses using gzip/deflate',
74
74
  });
75
75
 
76
+ network.addSetting('request_body_size_limit', {
77
+ type : 'number',
78
+ default : 20 * 1024 * 1024,
79
+ description : 'Maximum allowed size in bytes for the request body, excluding files',
80
+ });
81
+
82
+ network.addSetting('request_individual_file_size_limit', {
83
+ type : 'number',
84
+ default : 200 * 1024 * 1024,
85
+ description : 'Maximum allowed size in bytes for individual uploaded files',
86
+ });
87
+
88
+ network.addSetting('request_total_file_size_limit', {
89
+ type : 'number',
90
+ default : 200 * 1024 * 1024,
91
+ description : 'Maximum allowed size in bytes for all uploaded files',
92
+ });
93
+
76
94
  network.addSetting('use_json_dry_responses', {
77
95
  type : 'boolean',
78
96
  default : true,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "alchemymvc",
3
3
  "description": "MVC framework for Node.js",
4
- "version": "1.4.0-alpha.6",
4
+ "version": "1.4.0-alpha.7",
5
5
  "author": "Jelle De Loecker <jelle@elevenways.be>",
6
6
  "keywords": [
7
7
  "alchemy",