@reality.eth/reality-eth-lib 3.1.14 → 3.1.16

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,9 @@ 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');
10
+ const { convert} = require('html-to-text');
8
11
 
9
12
  exports.delimiter = function() {
10
13
  return '\u241f'; // Thought about '\u0000' but it seems to break something;
@@ -176,7 +179,7 @@ exports.parseQuestionJSON = function(data, errors_to_title) {
176
179
  // Strip unicode null-terminated-string control characters if there are any.
177
180
  // These seem to be stripped already if we got data via The Graph, and only passed to us on RPC.
178
181
  data = data.replace(/\u0000/g, "");
179
-
182
+
180
183
  var question_json;
181
184
  try {
182
185
  question_json = JSON.parse(data);
@@ -187,12 +190,15 @@ exports.parseQuestionJSON = function(data, errors_to_title) {
187
190
  'errors': {"json_parse_failed": true}
188
191
  };
189
192
  }
193
+
190
194
  if (question_json['outcomes'] && question_json['outcomes'].length > QUESTION_MAX_OUTCOMES) {
191
- question_json['errors'] = {'too_many_outcomes': true}
195
+ if (!question_json['errors']) question_json['errors'] = {};
196
+ question_json['errors']['too_many_outcomes'] = true;
192
197
  }
193
198
  if ('type' in question_json && question_json['type'] == 'datetime' && 'precision' in question_json) {
194
199
  if (!(['Y', 'm', 'd', 'H', 'i', 's'].includes(question_json['precision']))) {
195
- question_json['errors'] = {'invalid_precision': true};
200
+ if (!question_json['errors']) question_json['errors'] = {};
201
+ question_json['errors']['invalid_precision'] = true;
196
202
  }
197
203
  }
198
204
  // If errors_to_title is specified, we add any error message to the title to make sure we don't lose it
@@ -202,13 +208,57 @@ exports.parseQuestionJSON = function(data, errors_to_title) {
202
208
  'invalid_precision': 'Invalid date format',
203
209
  'too_many_outcomes': 'Too many outcomes'
204
210
  }
205
- for (var e in question_json['errors']) {
211
+ for (const e in question_json['errors']) {
206
212
  if (e in prependers) {
207
213
  question_json['title'] = '['+prependers[e]+'] ' + question_json['title'];
208
214
  }
209
215
  }
210
216
  }
211
217
  }
218
+
219
+ if (!question_json['format']) {
220
+ question_json['format'] = 'text/plain';
221
+ }
222
+
223
+ if (question_json['format'] == 'text/plain') {
224
+ question_json['title_text'] = question_json['title'];
225
+ } else if (question_json['format'] == 'text/markdown') {
226
+ try{
227
+ const safeMarkdown = DOMPurify.sanitize(question_json['title'], { USE_PROFILES: {html: false}});
228
+ if (safeMarkdown !== question_json['title']) {
229
+ if (!question_json['errors']) question_json['errors'] = {};
230
+ question_json['errors']['unsafe_markdown'] = true;
231
+ } else {
232
+ question_json['title_html'] = marked.parse(safeMarkdown).replace(/<img.*src=\"(.*?)\".*alt=\"(.*?)\".*\/?>/, '<a href="$1">$2</a>');
233
+ question_json['title_text'] = convert(question_json['title_html'], {
234
+ selectors: [{selector: 'h1', options: { uppercase: false }}, {selector: 'h2', options: { uppercase: false }}]
235
+ });
236
+ }
237
+ } catch(e){
238
+ if (!question_json['errors']) question_json['errors'] = {};
239
+ question_json['errors']['markdown_parse_failed'] = true
240
+ }
241
+ } else {
242
+ if (!question_json['errors']) question_json['errors'] = {};
243
+ question_json['errors']['invalid_format'] = true;
244
+ }
245
+
246
+ // If errors_to_title is specified, we add any error message to the title to make sure we don't lose it
247
+ if (errors_to_title) {
248
+ if ('errors' in question_json) {
249
+ const prependers = {
250
+ 'invalid_format': 'Invalid format',
251
+ 'unsafe_markdown': 'Unsafe markdown',
252
+ 'markdown_parse_failed': 'Bad markdown parse'
253
+ }
254
+ for (const e in question_json['errors']) {
255
+ if (e in prependers) {
256
+ question_json['title'] = '['+prependers[e]+'] ' + question_json['title'];
257
+ }
258
+ }
259
+ }
260
+ }
261
+
212
262
  return question_json;
213
263
 
214
264
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reality.eth/reality-eth-lib",
3
- "version": "3.1.14",
3
+ "version": "3.1.16",
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,13 @@
16
16
  "author": "Edmund Edgar (https://reality.eth.link)",
17
17
  "license": "GPL-3.0",
18
18
  "dependencies": {
19
- "@reality.eth/contracts": "^3.0.16",
19
+ "@reality.eth/contracts": "^3.0.17",
20
20
  "bignumber.js": "^7.2.1",
21
21
  "bn.js": "^5.2.1",
22
22
  "ethereumjs-abi": "^0.6.5",
23
+ "html-to-text": "^8.2.1",
24
+ "isomorphic-dompurify": "^0.23.0",
25
+ "marked": "^4.1.1",
23
26
  "sprintf-js": "^1.1.1"
24
27
  },
25
28
  "devDependencies": {
@@ -36,5 +39,5 @@
36
39
  "url": "https://github.com/RealityETH/monorepo/issues"
37
40
  },
38
41
  "homepage": "https://reality.eth.link",
39
- "gitHead": "bf4b54cd772d0763ab0a88ee631415d8ee90b2bb"
42
+ "gitHead": "cb5d687e6d5fe075c7e01bf5cb06517eb211dc24"
40
43
  }
@@ -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,32 @@ describe('Broken questions', function() {
241
240
  });
242
241
  });
243
242
 
243
+ describe('Markdown questions', function() {
244
+ it('Sets title, title_html and title_text appropriatly', function() {
245
+ const qMarkdown = "{\"title\": \"# my title oh yes\", \"type\": \"bool\", \"category\": \"art\", \"lang\": \"en_US\", \"format\": \"text/markdown\"}";
246
+ const q = rc_question.parseQuestionJSON(qMarkdown, true);
247
+ expect(q.errors).to.equal(undefined);
248
+ expect(q.format).to.equal('text/markdown');
249
+ expect(q.title_text).to.equal('my title oh yes');
250
+ expect(q.title).to.equal('# my title oh yes');
251
+ expect(q.title_html).to.equal('<h1 id="my-title-oh-yes">my title oh yes</h1>'+"\n");
252
+ });
253
+ });
254
+
255
+ describe('Unsafe markdown questions', function() {
256
+ it('Sets an error if a question includes unsafe html in markdown', function() {
257
+ 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\"}";
258
+ const q = rc_question.parseQuestionJSON(qUnsafeMarkdown, true);
259
+ expect(q.errors.unsafe_markdown).to.equal(true);
260
+ });
261
+ it('Sets no error if a question includes valid markdown without html', function() {
262
+ const qUnsafeMarkdown = "{\"title\": \"# Title\", \"type\": \"bool\", \"category\": \"art\", \"lang\": \"en_US\", \"format\": \"text/markdown\"}";
263
+ const q = rc_question.parseQuestionJSON(qUnsafeMarkdown, true);
264
+ expect(q.errors).to.equal(undefined);
265
+ expect(q.format).to.equal('text/markdown');
266
+ });
267
+ });
268
+
244
269
  describe('Commitment ID tests', function() {
245
270
  // Using rinkeby question:
246
271
  // 0xa09ce5e7943f281a782a0dc021c4029f9088bec4-0x0ade9a55d4dfca644062792d8e66cec9fbd5579761d760a6e0ae9856e81086a4