@reality.eth/reality-eth-lib 3.1.13 → 3.1.15

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.
@@ -5,6 +5,8 @@ const BigNumber = require('bignumber.js');
5
5
  const ethereumjs_abi = require('ethereumjs-abi')
6
6
  const vsprintf = require("sprintf-js").vsprintf
7
7
  const QUESTION_MAX_OUTCOMES = 128;
8
+ const marked = require('marked');
9
+ const DOMPurify = require('isomorphic-dompurify');
8
10
 
9
11
  exports.delimiter = function() {
10
12
  return '\u241f'; // Thought about '\u0000' but it seems to break something;
@@ -173,6 +175,10 @@ exports.secondsTodHms = function(sec) {
173
175
 
174
176
  exports.parseQuestionJSON = function(data, errors_to_title) {
175
177
 
178
+ // Strip unicode null-terminated-string control characters if there are any.
179
+ // These seem to be stripped already if we got data via The Graph, and only passed to us on RPC.
180
+ data = data.replace(/\u0000/g, "");
181
+
176
182
  var question_json;
177
183
  try {
178
184
  question_json = JSON.parse(data);
@@ -183,28 +189,79 @@ exports.parseQuestionJSON = function(data, errors_to_title) {
183
189
  'errors': {"json_parse_failed": true}
184
190
  };
185
191
  }
186
- if (question_json['outcomes'] && question_json['outcomes'].length > QUESTION_MAX_OUTCOMES) {
187
- question_json['errors'] = {'too_many_outcomes': true}
192
+
193
+ if (question_json['outcomes'] && question_json['outcomes'].length > QUESTION_MAX_OUTCOMES)
194
+ if(question_json['errors'])
195
+ question_json['errors']['too_many_outcomes'] = true
196
+ else
197
+ question_json['errors'] = {'too_many_outcomes': true};
198
+ if ('type' in question_json && question_json['type'] == 'datetime' && 'precision' in question_json)
199
+ if (!(['Y', 'm', 'd', 'H', 'i', 's'].includes(question_json['precision'])))
200
+ if(question_json['errors'])
201
+ question_json['errors']['invalid_precision'] = true
202
+ else
203
+ question_json['errors'] = {'invalid_precision': true};
204
+ // If errors_to_title is specified, we add any error message to the title to make sure we don't lose it
205
+ if (errors_to_title) {
206
+ if ('errors' in question_json) {
207
+ const prependers = {
208
+ 'invalid_precision': 'Invalid date format',
209
+ 'too_many_outcomes': 'Too many outcomes'
210
+ }
211
+ for (const e in question_json['errors']) {
212
+ if (e in prependers) {
213
+ question_json['title'] = '['+prependers[e]+'] ' + question_json['title'];
214
+ }
215
+ }
216
+ }
188
217
  }
189
- if ('type' in question_json && question_json['type'] == 'datetime' && 'precision' in question_json) {
190
- if (!(['Y', 'm', 'd', 'H', 'i', 's'].includes(question_json['precision']))) {
191
- question_json['errors'] = {'invalid_precision': true};
218
+
219
+ try{
220
+ switch(question_json['format']){
221
+ case 'text/markdown':{
222
+ const safeMarkdown = DOMPurify.sanitize(question_json['title'], { USE_PROFILES: {html: false}});
223
+ if (safeMarkdown !== question_json['title'])
224
+ if(question_json['errors'])
225
+ question_json['errors']['unsafe_markdown'] = true;
226
+ else
227
+ question_json['errors'] = {'unsafe_markdown': true};
228
+ else
229
+ question_json['title-markdown-html'] = marked.parse(safeMarkdown).replace(/<img.*src=\"(.*?)\".*alt=\"(.*?)\".*\/?>/, '<a href="$1">$2</a>');
230
+ break;
231
+ }
232
+ case 'text/plain': {
233
+ break;
234
+ }
235
+ case undefined:{
236
+ question_json['format'] = 'text/plain';
237
+ break;
238
+ }
239
+ default:{
240
+ question_json['errors'] = {'invalid_format': true};
241
+ break;
242
+ }
192
243
  }
244
+ } catch(e){
245
+ if(question_json && question_json['errors'])
246
+ question_json['errors']['markdown_parse_failed'] = true
193
247
  }
248
+
194
249
  // If errors_to_title is specified, we add any error message to the title to make sure we don't lose it
195
250
  if (errors_to_title) {
196
251
  if ('errors' in question_json) {
197
252
  const prependers = {
198
- 'invalid_precision': 'Invalid date format',
199
- 'too_many_outcomes': 'Too many outcomes'
253
+ 'invalid_format': 'Invalid format',
254
+ 'unsafe_markdown': 'Unsafe markdown',
255
+ 'markdown_parse_failed': 'Bad markdown parse'
200
256
  }
201
- for (var e in question_json['errors']) {
257
+ for (const e in question_json['errors']) {
202
258
  if (e in prependers) {
203
259
  question_json['title'] = '['+prependers[e]+'] ' + question_json['title'];
204
260
  }
205
261
  }
206
262
  }
207
263
  }
264
+
208
265
  return question_json;
209
266
 
210
267
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reality.eth/reality-eth-lib",
3
- "version": "3.1.13",
3
+ "version": "3.1.15",
4
4
  "description": "Tools for handling questions in the reality.eth fact verification platform",
5
5
  "scripts": {
6
6
  "test": "env TZ='Asia/Kabul' mocha ./test"
@@ -16,10 +16,12 @@
16
16
  "author": "Edmund Edgar (https://reality.eth.link)",
17
17
  "license": "GPL-3.0",
18
18
  "dependencies": {
19
- "@reality.eth/contracts": "^3.0.15",
19
+ "@reality.eth/contracts": "^3.0.16",
20
20
  "bignumber.js": "^7.2.1",
21
21
  "bn.js": "^5.2.1",
22
22
  "ethereumjs-abi": "^0.6.5",
23
+ "isomorphic-dompurify": "^0.23.0",
24
+ "marked": "^4.1.1",
23
25
  "sprintf-js": "^1.1.1"
24
26
  },
25
27
  "devDependencies": {
@@ -36,5 +38,5 @@
36
38
  "url": "https://github.com/RealityETH/monorepo/issues"
37
39
  },
38
40
  "homepage": "https://reality.eth.link",
39
- "gitHead": "4601364942fcc90d5f93f10f53e3934c7e772d46"
41
+ "gitHead": "5b0b4dd179e68567d2a0086fc10a6d0f9967af47"
40
42
  }
@@ -200,7 +200,6 @@ describe('Answer strings', function() {
200
200
  var qtext = rc_question.encodeText('multiple-select', 'oink', outcomes, 'my-category');
201
201
  var q1 = rc_question.populatedJSONForTemplate(rc_template.defaultTemplateForType('multiple-select'), qtext);
202
202
  expect(q1.errors.too_many_outcomes).to.equal(true);
203
- console.log(q1.title);
204
203
  expect(q1.title).to.equal('oink');
205
204
  var q2 = rc_question.populatedJSONForTemplate(rc_template.defaultTemplateForType('multiple-select'), qtext, true);
206
205
  expect(q2.errors.too_many_outcomes).to.equal(true);
@@ -241,6 +240,20 @@ describe('Broken questions', function() {
241
240
  });
242
241
  });
243
242
 
243
+ describe('Unsafe markdown questions', function() {
244
+ it('Sets an error if a question includes unsafe html in markdown', function() {
245
+ const qUnsafeMarkdown = "{\"title\": \"# Title <p>abc<iframe\/\/src=jAva&Tab;script:alert(3)>def<\/p>\", \"type\": \"bool\", \"category\": \"art\", \"lang\": \"en_US\", \"format\": \"text/markdown\"}";
246
+ const q = rc_question.parseQuestionJSON(qUnsafeMarkdown, true);
247
+ expect(q.errors.unsafe_markdown).to.equal(true);
248
+ });
249
+ it('Sets no error if a question includes valid markdown without html', function() {
250
+ const qUnsafeMarkdown = "{\"title\": \"# Title\", \"type\": \"bool\", \"category\": \"art\", \"lang\": \"en_US\", \"format\": \"text/markdown\"}";
251
+ const q = rc_question.parseQuestionJSON(qUnsafeMarkdown, true);
252
+ expect(q.errors).to.equal(undefined);
253
+ expect(q.format).to.equal('text/markdown');
254
+ });
255
+ });
256
+
244
257
  describe('Commitment ID tests', function() {
245
258
  // Using rinkeby question:
246
259
  // 0xa09ce5e7943f281a782a0dc021c4029f9088bec4-0x0ade9a55d4dfca644062792d8e66cec9fbd5579761d760a6e0ae9856e81086a4