From eec7fff909033ebdc527c2728009b5f450651de4 Mon Sep 17 00:00:00 2001
From: Berkeley Martinez <Berkeley@robotie.com>
Date: Tue, 24 Nov 2015 13:48:14 -0800
Subject: [PATCH] Fix jQuery challenges

---
 .gitignore                                         |  1 +
 .../commonFramework/detect-unsafe-code-stream.js   | 55 ++++++++++++++
 client/commonFramework/end.js                      |  6 +-
 client/commonFramework/execute-challenge-stream.js | 88 ++++++----------------
 client/faux.js                                     |  5 +-
 gulpfile.js                                        |  5 +-
 6 files changed, 90 insertions(+), 70 deletions(-)
 create mode 100644 client/commonFramework/detect-unsafe-code-stream.js

diff --git a/.gitignore b/.gitignore
index a59cd15..8b70d1c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,6 +45,7 @@ public/js/sandbox*
 public/js/iFrameScripts*
 public/js/plugin*
 public/js/vendor*
+public/js/faux*
 public/css/main*
 
 server/rev-manifest.json
diff --git a/client/commonFramework/detect-unsafe-code-stream.js b/client/commonFramework/detect-unsafe-code-stream.js
new file mode 100644
index 0000000..14ed08f
--- /dev/null
+++ b/client/commonFramework/detect-unsafe-code-stream.js
@@ -0,0 +1,55 @@
+window.common = (function(global) {
+  const {
+    Rx: { Observable },
+    common = { init: [] }
+  } = global;
+
+  const detectFunctionCall = /function\s*?\(|function\s+\w+\s*?\(/gi;
+  const detectUnsafeJQ = /\$\s*?\(\s*?\$\s*?\)/gi;
+  const detectUnsafeConsoleCall = /if\s\(null\)\sconsole\.log\(1\);/gi;
+
+  common.detectUnsafeCode$ = function detectUnsafeCode$(code) {
+    const openingComments = code.match(/\/\*/gi);
+    const closingComments = code.match(/\*\//gi);
+
+    // checks if the number of opening comments(/*) matches the number of
+    // closing comments(*/)
+    if (
+      openingComments &&
+      (
+        !closingComments ||
+        openingComments.length > closingComments.length
+      )
+    ) {
+
+      return Observable.throw(
+        new Error('SyntaxError: Unfinished multi-line comment')
+      );
+    }
+
+    if (code.match(detectUnsafeJQ)) {
+      return Observable.throw(
+        new Error('Unsafe $($)')
+      );
+    }
+
+    if (
+      code.match(/function/g) &&
+      !code.match(detectFunctionCall)
+    ) {
+      return Observable.throw(
+        new Error('SyntaxError: Unsafe or unfinished function declaration')
+      );
+    }
+
+    if (code.match(detectUnsafeConsoleCall)) {
+      return Observable.throw(
+        new Error('Invalid if (null) console.log(1); detected')
+      );
+    }
+
+    return Observable.just(code);
+  };
+
+  return common;
+}(window));
diff --git a/client/commonFramework/end.js b/client/commonFramework/end.js
index c0cd761..d2fa7f5 100644
--- a/client/commonFramework/end.js
+++ b/client/commonFramework/end.js
@@ -26,7 +26,8 @@ $(document).ready(function() {
   code$
     .flatMap(code => {
       if (common.hasJs(code)) {
-        return common.detectLoops$(code)
+        return common.detectUnsafeCode$(code)
+          .flatMap(code => common.detectLoops$(code))
           .flatMap(
             ({ err }) => err ? Observable.throw(err) : Observable.just(code)
           );
@@ -101,12 +102,13 @@ $(document).ready(function() {
       }
     );
 
+  // initial challenge run to populate tests
   if (challengeType === challengeTypes.HTML) {
     var $preview = $('#preview');
     return Observable.fromCallback($preview.ready, $preview)()
       .delay(500)
       .flatMap(() => common.executeChallenge$())
-      .catch(err => Observable.just(err))
+      .catch(err => Observable.just({ err }))
       .subscribe(
         ({ err, tests }) => {
           if (err) {
diff --git a/client/commonFramework/execute-challenge-stream.js b/client/commonFramework/execute-challenge-stream.js
index 86541bb..d9a7be1 100644
--- a/client/commonFramework/execute-challenge-stream.js
+++ b/client/commonFramework/execute-challenge-stream.js
@@ -12,9 +12,6 @@ window.common = (function(global) {
   } = global;
 
   let attempts = 0;
-  const detectFunctionCall = /function\s*?\(|function\s+\w+\s*?\(/gi;
-  const detectUnsafeJQ = /\$\s*?\(\s*?\$\s*?\)/gi;
-  const detectUnsafeConsoleCall = /if\s\(null\)\sconsole\.log\(1\);/gi;
 
   common.executeChallenge$ = function executeChallenge$() {
     const code = common.editor.getValue();
@@ -25,101 +22,62 @@ window.common = (function(global) {
 
     ga('send', 'event', 'Challenge', 'ran-code', common.challengeName);
 
-    let openingComments = code.match(/\/\*/gi);
-
-    // checks if the number of opening comments(/*) matches the number of
-    // closing comments(*/)
-    return Observable.just({ code })
-      .flatMap(({ code }) => {
-        if (
-          code.match(/\$\s*?\(\s*?\$\s*?\)/gi) &&
-          openingComments &&
-          openingComments.length > code.match(/\*\//gi).length
-        ) {
-
-          return Observable.throw({
-            err: 'SyntaxError: Unfinished multi-line comment',
-            code
-          });
-        }
-
-        if (code.match(detectUnsafeJQ)) {
-          return Observable.throw({
-            err: 'Unsafe $($)',
-            code
-          });
-        }
-
-        if (
-          code.match(/function/g) &&
-          !code.match(detectFunctionCall)
-        ) {
-          return Observable.throw({
-            err: 'SyntaxError: Unsafe or unfinished function declaration',
-            code
-          });
-        }
-
-        if (code.match(detectUnsafeConsoleCall)) {
-          return Observable.throw({
-            err: 'Invalid if (null) console.log(1); detected',
-            code
-          });
-        }
-
-        // add head and tail and detect loops
-        return Observable.just({ code: head + code + tail, original: code });
-      })
-      .flatMap(data => {
+    // run checks for unsafe code
+    return common.detectUnsafeCode$(code)
+      // add head and tail and detect loops
+      .map(code => head + code + tail)
+      .flatMap(code => {
         if (common.challengeType === common.challengeTypes.HTML) {
 
           if (common.hasJs(code)) {
-            return common.addFaux$(data)
+            // html has a script code
+            // add faux code and test in webworker
+            return common.addFaux$(code)
               .flatMap(code => common.detectLoops$(code))
               .flatMap(({ err }) => {
                 if (err) {
-                  return Observable.throw({ err });
+                  return Observable.throw(err);
                 }
-                return common.runPreviewTests$({
-                  code: data.code,
-                  tests: common.tests.slice()
-                });
+                return common.updatePreview$(code)
+                  .flatMap(() => common.runPreviewTests$({
+                    code,
+                    tests: common.tests.slice()
+                  }));
               });
           }
 
-          return common.updatePreview$(data.code)
+          // no script code detected in html code
+          // Update preview and run tests in iframe
+          return common.updatePreview$(code)
             .flatMap(code => common.runPreviewTests$({
               code,
               tests: common.tests.slice()
             }));
         }
 
+        // js challenge
+        // remove comments and add tests to string
         return common.addTestsToString(Object.assign(
-          data,
           {
             code: common.removeComments(code),
             tests: common.tests.slice()
           }
         ))
         .flatMap(common.detectLoops$)
-        .flatMap(({ err, code, data, userTests, original }) => {
+        .flatMap(({ err, code, data, userTests }) => {
             if (err) {
-              return Observable.throw({ err });
+              return Observable.throw(err);
             }
 
+            // run tests
+            // for now these are running in the browser
             return common.runTests$({
               data,
               code,
               userTests,
-              original,
               output: data.output.replace(/\\\"/gi, '')
             });
         });
-      })
-      .catch(e => {
-        return e && e.err ?
-          Observable.throw(e) :
-          Observable.throw({ err: e });
       });
   };
 
diff --git a/client/faux.js b/client/faux.js
index f1eb4e4..ca4563f 100644
--- a/client/faux.js
+++ b/client/faux.js
@@ -1,8 +1,9 @@
-document = {};
+/* eslint-disable no-unused-vars */
+var document = {};
 var navigator = function() {
   this.geolocation = function() {
     this.getCurrentPosition = function() {
-      this.coords = {latitude: "", longitude: ""};
+      this.coords = {latitude: '', longitude: '' };
       return this;
     };
     return this;
diff --git a/gulpfile.js b/gulpfile.js
index 034491c..f86b971 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -103,7 +103,8 @@ var paths = {
   js: [
     'client/main.js',
     'client/iFrameScripts.js',
-    'client/plugin.js'
+    'client/plugin.js',
+    'client/faux.js'
   ],
 
   commonFramework: [
@@ -114,6 +115,7 @@ var paths = {
     'code-storage',
     'code-uri',
     'create-editor',
+    'detect-unsafe-code-stream',
     'detect-loops-stream',
     'display-test-results',
     'execute-challenge-stream',
@@ -421,6 +423,7 @@ gulp.task('js', function() {
 });
 
 // commonFramework depend on iFrameScripts
+// and faux.js
 gulp.task('dependents', ['js'], function() {
   var manifestName = 'dependents-manifest.json';
   var dest = paths.publicJs;
