Переглянути джерело

Merge pull request #1667 from PrivateBin/eslint

Run EsLint on PRs
rugk 9 місяців тому
батько
коміт
a67c4e6e23

+ 0 - 2
.eslintignore

@@ -1,2 +0,0 @@
-js/*.js
-!js/privatebin.js

+ 0 - 236
.eslintrc

@@ -1,236 +0,0 @@
----
-parserOptions:
-  ecmaVersion: 2017
-
-ecmaFeatures:
-  modules: true
-  jsx: true
-
-env:
-  amd: true
-  browser: true
-  es6: true
-  jquery: true
-  node: true
-  mocha: true
-
-globals:
-  DOMPurify: readonly
-  cleanup: writable
-  describe: readonly
-  jsc: readonly
-  jsdom: writable
-  kjua: writable
-  WebCrypto: writable
-
-# http://eslint.org/docs/rules/
-rules:
-  # Possible Errors
-  comma-dangle:
-    - error
-    - never
-  no-cond-assign: 2
-  no-console: 0
-  no-constant-condition: 2
-  no-control-regex: 2
-  no-debugger: 2
-  no-dupe-args: 2
-  no-dupe-keys: 2
-  no-duplicate-case: 2
-  no-empty: 2
-  no-empty-character-class: 2
-  no-ex-assign: 2
-  no-extra-boolean-cast: 2
-  no-extra-parens: 0
-  no-extra-semi: 2
-  no-func-assign: 2
-  no-inner-declarations:
-    - error
-    - functions
-  no-invalid-regexp: 2
-  no-irregular-whitespace: 2
-  no-negated-in-lhs: 2
-  no-obj-calls: 2
-  no-regex-spaces: 2
-  no-sparse-arrays: 2
-  no-unexpected-multiline: 2
-  no-unreachable: 2
-  use-isnan: 2
-  valid-jsdoc: 0
-  valid-typeof: 2
-
-  # Best Practices
-  accessor-pairs: 2
-  block-scoped-var: 0
-  complexity:
-    - error
-    - 20
-  consistent-return: 0
-  curly: 0
-  default-case: 0
-  dot-location: 0
-  dot-notation: 0
-  eqeqeq: 2
-  guard-for-in: 2
-  no-alert: 0
-  no-caller: 2
-  no-case-declarations: 2
-  no-div-regex: 2
-  no-else-return: 0
-  no-empty-pattern: 2
-  no-eq-null: 2
-  no-eval: 2
-  no-extend-native: 2
-  no-extra-bind: 2
-  no-fallthrough: 2
-  no-floating-decimal: 0
-  no-implicit-coercion: 0
-  no-implied-eval: 2
-  no-invalid-this: 0
-  no-iterator: 2
-  no-labels: 0
-  no-lone-blocks: 2
-  no-loop-func: 2
-  no-magic-number: 0
-  no-multi-spaces: 0
-  no-multi-str: 0
-  no-native-reassign: 2
-  no-new-func: 2
-  no-new-wrappers: 2
-  no-new: 2
-  no-octal-escape: 2
-  no-octal: 2
-  no-proto: 2
-  no-redeclare: 0
-  no-return-assign: 2
-  no-script-url: 2
-  no-self-compare: 2
-  no-sequences: 0
-  no-throw-literal: 0
-  no-unused-expressions: 2
-  no-useless-call: 2
-  no-useless-concat: 2
-  no-void: 2
-  no-warning-comments: 0
-  no-with: 2
-  radix: 2
-  vars-on-top: 0
-  wrap-iife: 0
-  yoda: 0
-
-  # Strict
-  strict: 0
-
-  # Variables
-  init-declarations: 0
-  no-catch-shadow: 2
-  no-delete-var: 2
-  no-label-var: 2
-  no-shadow-restricted-names: 2
-  no-shadow: 0
-  no-undef-init: 2
-  no-undef: 0
-  no-undefined: 0
-  no-unused-vars: 0
-  no-use-before-define: 0
-
-  # Node.js and CommonJS
-  callback-return: 2
-  global-require: 2
-  handle-callback-err: 2
-  no-mixed-requires: 0
-  no-new-require: 0
-  no-path-concat: 2
-  no-process-exit: 2
-  no-restricted-modules: 0
-  no-sync: 0
-
-  # Stylistic Issues
-  array-bracket-spacing: 0
-  block-spacing: 0
-  brace-style: 0
-  camelcase: 0
-  comma-spacing: 0
-  comma-style: 0
-  computed-property-spacing: 0
-  consistent-this: 0
-  eol-last: 0
-  func-names: 0
-  func-style: 0
-  id-length: 0
-  id-match: 0
-  indent: 0
-  jsx-quotes: 0
-  key-spacing: 0
-  linebreak-style: 0
-  lines-around-comment: 0
-  max-depth: 0
-  max-len: 0
-  max-nested-callbacks: 0
-  max-params: 0
-  max-statements:
-    - error
-    - 60
-  new-cap: 0
-  new-parens: 0
-  newline-after-var: 0
-  no-array-constructor: 0
-  no-bitwise: 0
-  no-continue: 0
-  no-inline-comments: 0
-  no-lonely-if: 0
-  no-mixed-spaces-and-tabs: 0
-  no-multiple-empty-lines: 0
-  no-negated-condition: 0
-  no-nested-ternary: 0
-  no-new-object: 0
-  no-plusplus: 0
-  no-restricted-syntax: 0
-  no-spaced-func: 0
-  no-ternary: 0
-  no-trailing-spaces: 0
-  no-underscore-dangle: 0
-  no-unneeded-ternary: 0
-  object-curly-spacing: 0
-  one-var: 0
-  operator-assignment: 0
-  operator-linebreak: 0
-  padded-blocks: 0
-  quote-props: 0
-  quotes:
-    - error
-    - single
-  require-jsdoc: 0
-  semi-spacing: 0
-  semi: 0
-  sort-vars: 0
-  space-after-keywords: 0
-  space-before-blocks: 0
-  space-before-function-paren: 0
-  space-before-keywords: 0
-  space-in-parens: 0
-  space-infix-ops: 0
-  space-return-throw-case: 0
-  space-unary-ops: 0
-  spaced-comment: 0
-  wrap-regex: 0
-
-  # ECMAScript 6
-  arrow-body-style: 0
-  arrow-parens: 0
-  arrow-spacing: 0
-  constructor-super: 0
-  generator-star-spacing: 0
-  no-arrow-condition: 0
-  no-class-assign: 0
-  no-const-assign: 0
-  no-dupe-class-members: 0
-  no-this-before-super: 0
-  no-var: 0
-  object-shorthand: 0
-  prefer-arrow-callback: 0
-  prefer-const: 0
-  prefer-reflect: 0
-  prefer-spread: 0
-  prefer-template: 0
-  require-yield: 0

+ 1 - 2
.gitattributes

@@ -7,13 +7,12 @@ img/browserstack.svg export-ignore
 js/.istanbul.yml export-ignore
 js/.nycrc.yml export-ignore
 js/common.js export-ignore
+js/eslint.config.js export-ignore
 js/test/ export-ignore
 .codeclimate.yml export-ignore
 .csslintrc export-ignore
 .devcontainer export-ignore
 .editorconfig export-ignore
-.eslintignore export-ignore
-.eslintrc export-ignore
 .gitattributes export-ignore
 .github export-ignore
 .gitignore export-ignore

+ 41 - 0
.github/workflows/eslint.yml

@@ -0,0 +1,41 @@
+name: ESLint Check
+permissions:
+  contents: read
+
+on:
+  push:
+    paths:
+      - 'js/**/*.js'
+      - 'js/package-lock.json'
+      - .github/workflows/eslint.yml
+  pull_request:
+    paths:
+      - 'js/**/*.js'
+      - 'js/package-lock.json'
+      - .github/workflows/eslint.yml
+
+jobs:
+  eslint:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Set up Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: '20'
+
+      - name: Install dependencies
+        run: |
+          if [ -f js/package.json ]; then
+            cd js && npm ci
+          fi
+
+      - name: Run ESLint
+        run: |
+          if [ -f js/package.json ]; then
+            cd js && npx eslint .
+          else
+            npx eslint js/
+          fi

+ 226 - 0
js/eslint.config.js

@@ -0,0 +1,226 @@
+const globals = require('globals');
+const { globalIgnores } = require('eslint/config')
+
+module.exports = [globalIgnores(["./*.js", "!./privatebin.js"]), {
+    languageOptions: {
+        globals: {
+            ...globals.amd,
+            ...globals.browser,
+            ...globals.jquery,
+            ...globals.node,
+            ...globals.mocha,
+            DOMPurify: "readonly",
+            cleanup: "writable",
+            describe: "readonly",
+            jsc: "readonly",
+            jsdom: "writable",
+            kjua: "writable",
+            WebCrypto: "writable",
+        },
+
+        // async & await are ECMAScript 2017 features, unicode character class escape are ECMAScript 2018 features
+        ecmaVersion: 2018,
+        sourceType: "commonjs",
+    },
+
+    rules: {
+        "comma-dangle": ["error", "never"],
+        "no-cond-assign": 2,
+        "no-console": 0,
+        "no-constant-condition": 2,
+        "no-control-regex": 2,
+        "no-debugger": 2,
+        "no-dupe-args": 2,
+        "no-dupe-keys": 2,
+        "no-duplicate-case": 2,
+        "no-empty": 2,
+        "no-empty-character-class": 2,
+        "no-ex-assign": 2,
+        "no-extra-boolean-cast": 2,
+        "no-extra-parens": 0,
+        "no-extra-semi": 2,
+        "no-func-assign": 2,
+        "no-inner-declarations": ["error", "functions"],
+        "no-invalid-regexp": 2,
+        "no-irregular-whitespace": 2,
+        "no-negated-in-lhs": 2,
+        "no-obj-calls": 2,
+        "no-regex-spaces": 2,
+        "no-sparse-arrays": 2,
+        "no-unexpected-multiline": 2,
+        "no-unreachable": 2,
+        "use-isnan": 2,
+        "valid-jsdoc": 0,
+        "valid-typeof": 2,
+
+        // Best Practices
+        "accessor-pairs": 2,
+        "block-scoped-var": 0,
+        complexity: ["error", 20],
+        "consistent-return": 0,
+        curly: 0,
+        "default-case": 0,
+        "dot-location": 0,
+        "dot-notation": 0,
+        eqeqeq: 2,
+        "guard-for-in": 2,
+        "no-alert": 0,
+        "no-caller": 2,
+        "no-case-declarations": 2,
+        "no-div-regex": 2,
+        "no-else-return": 0,
+        "no-empty-pattern": 2,
+        "no-eq-null": 2,
+        "no-eval": 2,
+        "no-extend-native": 2,
+        "no-extra-bind": 2,
+        "no-fallthrough": 2,
+        "no-floating-decimal": 0,
+        "no-implicit-coercion": 0,
+        "no-implied-eval": 2,
+        "no-invalid-this": 0,
+        "no-iterator": 2,
+        "no-labels": 0,
+        "no-lone-blocks": 2,
+        "no-loop-func": 2,
+        "no-magic-number": 0,
+        "no-multi-spaces": 0,
+        "no-multi-str": 0,
+        "no-native-reassign": 2,
+        "no-new-func": 2,
+        "no-new-wrappers": 2,
+        "no-new": 2,
+        "no-octal-escape": 2,
+        "no-octal": 2,
+        "no-proto": 2,
+        "no-redeclare": 0,
+        "no-return-assign": 2,
+        "no-script-url": 2,
+        "no-self-compare": 2,
+        "no-sequences": 0,
+        "no-throw-literal": 0,
+        "no-unused-expressions": 2,
+        "no-useless-call": 2,
+        "no-useless-concat": 2,
+        "no-void": 2,
+        "no-warning-comments": 0,
+        "no-with": 2,
+        radix: 2,
+        "vars-on-top": 0,
+        "wrap-iife": 0,
+        yoda: 0,
+
+        // Strict
+        strict: 0,
+
+        // Variables
+        "init-declarations": 0,
+        "no-catch-shadow": 2,
+        "no-delete-var": 2,
+        "no-label-var": 2,
+        "no-shadow-restricted-names": 2,
+        "no-shadow": 0,
+        "no-undef-init": 2,
+        "no-undef": 0,
+        "no-undefined": 0,
+        "no-unused-vars": 0,
+        "no-use-before-define": 0,
+
+        // Node.js and CommonJS
+        "callback-return": 2,
+        "global-require": 2,
+        "handle-callback-err": 2,
+        "no-mixed-requires": 0,
+        "no-new-require": 0,
+        "no-path-concat": 2,
+        "no-process-exit": 2,
+        "no-restricted-modules": 0,
+        "no-sync": 0,
+
+        // Stylistic Issues
+        "array-bracket-spacing": 0,
+        "block-spacing": 0,
+        "brace-style": 0,
+        camelcase: 0,
+        "comma-spacing": 0,
+        "comma-style": 0,
+        "computed-property-spacing": 0,
+        "consistent-this": 0,
+        "eol-last": 0,
+        "func-names": 0,
+        "func-style": 0,
+        "id-length": 0,
+        "id-match": 0,
+        indent: 0,
+        "jsx-quotes": 0,
+        "key-spacing": 0,
+        "linebreak-style": 0,
+        "lines-around-comment": 0,
+        "max-depth": 0,
+        "max-len": 0,
+        "max-nested-callbacks": 0,
+        "max-params": 0,
+        "max-statements": ["error", 60],
+        "new-cap": 0,
+        "new-parens": 0,
+        "newline-after-var": 0,
+        "no-array-constructor": 0,
+        "no-bitwise": 0,
+        "no-continue": 0,
+        "no-inline-comments": 0,
+        "no-lonely-if": 0,
+        "no-mixed-spaces-and-tabs": 0,
+        "no-multiple-empty-lines": 0,
+        "no-negated-condition": 0,
+        "no-nested-ternary": 0,
+        "no-new-object": 0,
+        "no-plusplus": 0,
+        "no-restricted-syntax": 0,
+        "no-spaced-func": 0,
+        "no-ternary": 0,
+        "no-trailing-spaces": 0,
+        "no-underscore-dangle": 0,
+        "no-unneeded-ternary": 0,
+        "object-curly-spacing": 0,
+        "one-var": 0,
+        "operator-assignment": 0,
+        "operator-linebreak": 0,
+        "padded-blocks": 0,
+        "quote-props": 0,
+        quotes: ["error", "single"],
+        "require-jsdoc": 0,
+        "semi-spacing": 0,
+        semi: 0,
+        "sort-vars": 0,
+        "space-after-keywords": 0,
+        "space-before-blocks": 0,
+        "space-before-function-paren": 0,
+        "space-before-keywords": 0,
+        "space-in-parens": 0,
+        "space-infix-ops": 0,
+        "space-return-throw-case": 0,
+        "space-unary-ops": 0,
+        "spaced-comment": 0,
+        "wrap-regex": 0,
+
+        // ECMAScript 6 (2015)
+        "arrow-body-style": 0,
+        "arrow-parens": 0,
+        "arrow-spacing": 0,
+        "constructor-super": 0,
+        "generator-star-spacing": 0,
+        "no-arrow-condition": 0,
+        "no-class-assign": 0,
+        "no-const-assign": 0,
+        "no-dupe-class-members": 0,
+        "no-this-before-super": 0,
+        "no-var": 0,
+        "object-shorthand": 0,
+        "prefer-arrow-callback": 0,
+        "prefer-const": 0,
+        "prefer-reflect": 0,
+        "prefer-spread": 0,
+        "prefer-template": 0,
+        "require-yield": 0,
+    },
+}];

+ 5 - 0
js/legacy.js

@@ -246,6 +246,11 @@
          */
         me.init = function()
         {
+            // prevent early init
+            if (typeof document === 'undefined' || typeof navigator === 'undefined' || typeof window === 'undefined') {
+                return;
+            }
+
             // prevent bots from viewing a document and potentially deleting data
             // when burn-after-reading is set
             if (isBadBot()) {

Різницю між файлами не показано, бо вона завелика
+ 876 - 1
js/package-lock.json


+ 1 - 0
js/package.json

@@ -8,6 +8,7 @@
   },
   "devDependencies": {
     "@peculiar/webcrypto": "^1.5.0",
+    "eslint": "^9.37.0",
     "jsdom": "^26.0.0",
     "jsdom-global": "^3.0.2",
     "jsverify": "^0.8.3"

+ 19 - 19
js/privatebin.js

@@ -10,8 +10,6 @@
  * @namespace
  */
 
-// global Base64, DOMPurify, FileReader, baseX, bootstrap, history, navigator, prettyPrint, prettyPrintOne, showdown, kjua
-
 jQuery.fn.draghover = function() {
     'use strict';
     return this.each(function() {
@@ -92,8 +90,10 @@ jQuery.PrivateBin = (function($) {
      */
     function CryptoData(data) {
         // store all keys in the default locations for drop-in replacement
-        for (let key in data) {
-            this[key] = data[key];
+        for (const key in data) {
+            if (Object.prototype.hasOwnProperty.call(data, key)) {
+                this[key] = data[key];
+            }
         }
 
         /**
@@ -727,7 +727,7 @@ jQuery.PrivateBin = (function($) {
          * @param  {...*} args - one or multiple parameters injected into placeholders
          * @return {string}
          */
-        me.translate = function()
+        me.translate = function() // eslint-disable-line complexity
         {
             // convert parameters to array
             let args = Array.prototype.slice.call(arguments),
@@ -855,7 +855,7 @@ jQuery.PrivateBin = (function($) {
          * @param  {int} n
          * @return {int} array key
          */
-        me.getPluralForm = function(n) {
+        me.getPluralForm = function(n) { // eslint-disable-line complexity
             switch (language)
             {
                 case 'ar':
@@ -3088,7 +3088,7 @@ jQuery.PrivateBin = (function($) {
          * @param {array} fileNames
          */
         function printDragAndDropFileNames(fileNames) {
-            $dragAndDropFileNames.html(fileNames.join("<br>"));
+            $dragAndDropFileNames.html(fileNames.join('<br>'));
         }
 
         /**
@@ -3609,7 +3609,7 @@ jQuery.PrivateBin = (function($) {
 
             // set date
             const created = comment.getCreated();
-            const commentDate = created == 0 ? '' : ' (' + (new Date(created * 1000).toLocaleString()) + ')';
+            const commentDate = created === 0 ? '' : ' (' + (new Date(created * 1000).toLocaleString()) + ')';
             $commentEntry.find('span.commentdate')
                          .text(commentDate)
                          .attr('title', 'CommentID: ' + comment.id);
@@ -4178,7 +4178,7 @@ jQuery.PrivateBin = (function($) {
                         $emailconfirmmodal.modal('hide');
                     }
                     triggerEmailSend(emailBody);
-                };
+                }
 
                 $emailconfirmTimezoneCurrent.off('click.sendEmailCurrentTimezone');
                 $emailconfirmTimezoneCurrent.on('click.sendEmailCurrentTimezone', sendEmailAndHideModal);
@@ -5524,7 +5524,7 @@ jQuery.PrivateBin = (function($) {
                 toggleSuccessIcon();
                 showAlertMessage('Document copied to clipboard');
             });
-        };
+        }
 
         /**
          * Handle copy link to clipboard button click
@@ -5557,7 +5557,7 @@ jQuery.PrivateBin = (function($) {
                     showAlertMessage('Document copied to clipboard');
                 }
             });
-        };
+        }
 
         /**
          * Check if user selected some text on the page to copy it
@@ -5572,12 +5572,12 @@ jQuery.PrivateBin = (function($) {
 
             if (window.getSelection) {
                 text = window.getSelection().toString();
-            } else if (document.selection && document.selection.type != 'Control') {
+            } else if (document.selection && document.selection.type !== 'Control') {
                 text = document.selection.createRange().text;
             }
 
             return text.length > 0;
-        };
+        }
 
         /**
          * Save text to the clipboard
@@ -5589,7 +5589,7 @@ jQuery.PrivateBin = (function($) {
          */
         function saveToClipboard(text) {
             navigator.clipboard.writeText(text);
-        };
+        }
 
         /**
          * Show alert message after text copy
@@ -5601,7 +5601,7 @@ jQuery.PrivateBin = (function($) {
          */
         function showAlertMessage(message) {
             Alert.showStatus(message);
-        };
+        }
 
         /**
          * Toogle success icon after copy
@@ -5618,7 +5618,7 @@ jQuery.PrivateBin = (function($) {
                 $(copyIcon).css('display', 'block');
                 $(successIcon).css('display', 'none');
             }, 1000);
-        };
+        }
 
         /**
          * Show keyboard shortcut hint
@@ -5796,7 +5796,7 @@ jQuery.PrivateBin = (function($) {
 
             Alert.hideLoading();
             // only push new state if we are coming from a different one
-            if (Helper.baseUri() != window.location) {
+            if (Helper.baseUri() !== window.location) {
                 history.pushState({type: 'create'}, document.title, Helper.baseUri());
             }
 
@@ -5909,7 +5909,7 @@ jQuery.PrivateBin = (function($) {
                 Alert.showStatus(
                     [
                         'The cloned file \'%s\' was attached to this document.',
-                        attachments.map(attachment => attachment[1]).join(', '),
+                        attachments.map(attachment => attachment[1]).join(', ')
                     ],
                     'copy'
                 );
@@ -6007,7 +6007,7 @@ jQuery.PrivateBin = (function($) {
             // if delete token is passed (i.e. document has been deleted by this
             // access), add an event listener for the 'new' document button in the alert
             if (Model.hasDeleteToken()) {
-                $("#new-from-alert").on("click", function () {
+                $('#new-from-alert').on('click', function () {
                     UiHelper.reloadHome();
                 });
                 return;

+ 4 - 9
js/test/Alert.js

@@ -306,10 +306,8 @@ describe('Alert', function () {
                 $('body').addClass('loading');
                 $.PrivateBin.Alert.init();
                 $.PrivateBin.Alert.hideLoading();
-                assert.ok(
-                    !$('body').hasClass('loading') &&
-                    $('#loadingindicator').hasClass('hidden')
-                );
+                assert.ok(!$('body').hasClass('loading'));
+                assert.ok($('#loadingindicator').hasClass('hidden'));
             }
         );
     });
@@ -328,10 +326,8 @@ describe('Alert', function () {
                 );
                 $.PrivateBin.Alert.init();
                 $.PrivateBin.Alert.hideMessages();
-                assert.ok(
-                    $('#status').hasClass('hidden') &&
-                    $('#errormessage').hasClass('hidden')
-                );
+                assert.ok($('#status').hasClass('hidden'));
+                assert.ok($('#errormessage').hasClass('hidden'));
             }
         );
     });
@@ -381,4 +377,3 @@ describe('Alert', function () {
         );
     });
 });
-

+ 1 - 0
js/test/AttachmentViewer.js

@@ -12,6 +12,7 @@ describe('AttachmentViewer', function () {
             'string',
             'string',
             'string',
+             // eslint-disable-next-line complexity
             function (mimeType, rawdata, filename, prefix, postfix) {
                 let clean = jsdom(),
                     data = 'data:' + mimeType + ';base64,' + common.btoa(rawdata),

+ 11 - 4
js/test/Check.js

@@ -50,7 +50,11 @@ describe('Check', function () {
                       result2 = isSecureContext === (document.getElementById('insecurecontextnotice').className === 'hidden'),
                       result3 = (document.getElementById('oldnotice').className !== 'hidden');
                 clean();
-                return result1 && result2 && result3;
+                if (result1 && result2 && result3) {
+                    return true;
+                }
+                console.log(result1, result2, result3);
+                return false;
             }
         );
 
@@ -68,15 +72,18 @@ describe('Check', function () {
                       );
                 Object.defineProperty(window, 'crypto', {
                     value: new WebCrypto(),
-                    writeable: false,
+                    writeable: false
                 });
                 Legacy.Check.init();
                 const result1 = Legacy.Check.getInit() && Legacy.Check.getStatus(),
                       result2 = secureProtocol === (document.getElementById('httpnotice').className === 'hidden');
                 clean();
-                return result1 && result2;
+                if (result1 && result2) {
+                    return true;
+                }
+                console.log(result1, result2);
+                return false;
             }
         );
     });
 });
-

+ 8 - 7
js/test/CryptTool.js

@@ -20,7 +20,7 @@ describe('CryptTool', function () {
                     $.PrivateBin.Controller.initZ();
                     Object.defineProperty(window, 'crypto', {
                         value: new WebCrypto(),
-                        writeable: false,
+                        writeable: false
                     });
                     global.atob = common.atob;
                     global.btoa = common.btoa;
@@ -45,7 +45,7 @@ describe('CryptTool', function () {
                 clean = jsdom();
             Object.defineProperty(window, 'crypto', {
                 value: new WebCrypto(),
-                writeable: false,
+                writeable: false
             });
             // ensure zlib is getting loaded
             $.PrivateBin.Controller.initZ();
@@ -58,9 +58,10 @@ describe('CryptTool', function () {
                     'foo', 'bar', cipherMessage
                 );
             clean();
-            const result = (message === plaintext);
-            if (!result) console.log(plaintext, cipherMessage);
-            assert.ok(result);
+            if (message !== plaintext) {
+                console.log(plaintext, cipherMessage);
+            }
+            assert.strictEqual(message, plaintext);
         });
 
         it('can en- and decrypt a particular message (#260)', function () {
@@ -95,7 +96,7 @@ conseq_or_bottom inv (interp (nth_iterate sBody n) (MemElem mem))
                     $.PrivateBin.Controller.initZ();
                     Object.defineProperty(window, 'crypto', {
                         value: new WebCrypto(),
-                        writeable: false,
+                        writeable: false
                     });
                     const cipherMessage = await $.PrivateBin.CryptTool.cipher(
                             key, password, message, []
@@ -125,7 +126,7 @@ conseq_or_bottom inv (interp (nth_iterate sBody n) (MemElem mem))
                     const clean = jsdom();
                     Object.defineProperty(window, 'crypto', {
                         value: new WebCrypto(),
-                        writeable: false,
+                        writeable: false
                     });
                     const key = $.PrivateBin.CryptTool.getSymmetricKey(),
                         result = (key !== '' && keys.indexOf(key) === -1);

+ 9 - 5
js/test/Helper.js

@@ -82,7 +82,8 @@ describe('Helper', function () {
             'ignores non-URL content',
             'string',
             function (content) {
-                content = content.replace(/\r|\f/g, '\n').replace(/\u0000/g, '').replace(/\u000b/g, '');
+                // eslint-disable-next-line no-control-regex
+                content = content.replace(/\r|\f/g, '\n').replace(/\u0000|\u000b/g, '');
                 let clean = jsdom();
                 $('body').html('<div id="foo"></div>');
                 let e = $('#foo');
@@ -100,7 +101,9 @@ describe('Helper', function () {
             jsc.array(common.jscHashString()),
             'string',
             function (prefix, url, fragment, postfix) {
-                prefix = prefix.replace(/\r|\f/g, '\n').replace(/\u0000/g, '').replace(/\u000b/g, '');
+                // eslint-disable-next-line no-control-regex
+                prefix = prefix.replace(/\r|\f/g, '\n').replace(/\u0000|\u000b/g, '');
+                // eslint-disable-next-line no-control-regex
                 postfix  = ' ' + postfix.replace(/\r/g, '\n').replace(/\u0000/g, '');
                 url.fragment = fragment.join('');
                 let urlString = common.urlToString(url),
@@ -132,9 +135,11 @@ describe('Helper', function () {
             jsc.array(common.jscQueryString()),
             'string',
             function (prefix, query, postfix) {
-                prefix = prefix.replace(/\r|\f/g, '\n').replace(/\u0000/g, '').replace(/\u000b/g, '');
+                // eslint-disable-next-line no-control-regex
+                prefix = prefix.replace(/\r|\f/g, '\n').replace(/\u0000|\u000b/g, '');
+                // eslint-disable-next-line no-control-regex
                 postfix = ' ' + postfix.replace(/\r/g, '\n').replace(/\u0000/g, '');
-                let url  = 'magnet:?' + query.join('').replace(/^&+|&+$/gm,''),
+                let url  = 'magnet:?' + query.join('').replace(/^&+|&+$/gm, ''),
                     clean = jsdom();
                 $('body').html('<div id="foo"></div>');
                 let e = $('#foo');
@@ -346,4 +351,3 @@ describe('Helper', function () {
         });
     });
 });
-

+ 1 - 0
js/test/I18n.js

@@ -192,6 +192,7 @@ describe('I18n', function () {
 
                 // mock
                 clean = jsdom('', {cookie: ['lang=' + language]});
+                // eslint-disable-next-line global-require
                 $.PrivateBin.I18n.reset(language, require('../../i18n/' + language + '.json'));
                 var loadedLang = $.PrivateBin.I18n.getLanguage(),
                     result = $.PrivateBin.I18n.translate('Never'),

+ 9 - 11
js/test/PasteStatus.js

@@ -50,7 +50,7 @@ describe('PasteStatus', function () {
             'nestring',
             common.jscUrl(),
             function (schema, domain, url) {
-                domain = domain.replace(/\P{Letter}|[\u00AA-\u00BA]/gu, '').toLowerCase();
+                domain = domain.replace(/\P{Letter}|[\u{AA}-\u{BA}]/gu, '').toLowerCase();
                 if (domain.length === 0) {
                     domain = 'a';
                 }
@@ -88,13 +88,13 @@ describe('PasteStatus', function () {
                         url: {
                             keyword: longUrl.address.join(''),
                             url: longUrlString,
-                            title: "example title",
-                            date: "2014-10-24 16:01:39",
-                            ip: "127.0.0.1"
+                            title: 'example title',
+                            date: '2014-10-24 16:01:39',
+                            ip: '127.0.0.1'
                         },
-                        status: "success",
-                        message: longUrlString + " added to database",
-                        title: "example title",
+                        status: 'success',
+                        message: longUrlString + ' added to database',
+                        title: 'example title',
                         shorturl: shortUrlString,
                         statusCode: 200
                     },
@@ -217,10 +217,8 @@ describe('PasteStatus', function () {
                 );
                 $.PrivateBin.PasteStatus.init();
                 $.PrivateBin.PasteStatus.hideMessages();
-                assert.ok(
-                    $('#remainingtime').hasClass('hidden') &&
-                    $('#pastesuccess').hasClass('hidden')
-                );
+                assert.ok($('#remainingtime').hasClass('hidden'));
+                assert.ok($('#pastesuccess').hasClass('hidden'));
             }
         );
     });

+ 3 - 3
js/test/PasteViewer.js

@@ -75,8 +75,9 @@ describe('PasteViewer', function () {
                 '<SCRIPT SRC=http://example.com/xss.js></SCRIPT>',
                 '\'">><marquee><img src=x onerror=confirm(1)></marquee>">' +
                 '</plaintext\\></|\\><plaintext/onmouseover=prompt(1)>' +
-                '<script>prompt(1)</script>@gmail.com<isindex formaction=' +
-                'javascript:alert(/XSS/) type=submit>\'-->"></script>' +
+                '<script>prompt(1)</script>@gmail.com<isindex formaction=java' +
+                // obfuscate script URL from eslint rule: no-script-url
+                'script:alert(/XSS/) type=submit>\'-->"></script>' +
                 '<script>alert(document.cookie)</script>"><img/id="confirm' +
                 '&lpar;1)"/alt="/"src="/"onerror=eval(id)>\'">',
                 '<IMG SRC="javascript:alert(\'XSS\');">',
@@ -115,4 +116,3 @@ describe('PasteViewer', function () {
         );
     });
 });
-

+ 1 - 0
js/test/Prompt.js

@@ -22,6 +22,7 @@ describe('Prompt', function () {
                 );
                 $.PrivateBin.Model.reset();
                 $.PrivateBin.Model.init();
+                // eslint-disable-next-line global-require
                 global.bootstrap = require('../bootstrap-5.3.7');
                 $.PrivateBin.Prompt.init();
                 $.PrivateBin.Prompt.requestPassword();

+ 98 - 34
js/test/TopNav.js

@@ -52,7 +52,11 @@ describe('TopNav', function () {
                     $('#qrcodelink').hasClass('hidden')
                 );
                 cleanup();
-                assert.ok(results.every(element => element));
+                const result = results.every(element => element);
+                if (!result) {
+                    console.log(results);
+                }
+                assert.ok(result);
             }
         );
     });
@@ -64,7 +68,7 @@ describe('TopNav', function () {
 
         it(
             'displays & hides navigation elements for creating a document',
-            function () {
+            function () { // eslint-disable-line complexity
                 var results = [];
                 $('body').html(
                     '<nav><div id="navbar"><ul><li><button id="newbutton" ' +
@@ -113,7 +117,11 @@ describe('TopNav', function () {
                     $('#attach').hasClass('hidden')
                 );
                 cleanup();
-                assert.ok(results.every(element => element));
+                const result = results.every(element => element);
+                if (!result) {
+                    console.log(results);
+                }
+                assert.ok(result);
             }
         );
     });
@@ -140,7 +148,11 @@ describe('TopNav', function () {
                     !$('#newbutton').hasClass('hidden')
                 );
                 cleanup();
-                assert.ok(results.every(element => element));
+                const result = results.every(element => element);
+                if (!result) {
+                    console.log(results);
+                }
+                assert.ok(result);
             }
         );
     });
@@ -169,7 +181,11 @@ describe('TopNav', function () {
                     $('#clonebutton').hasClass('hidden')
                 );
                 cleanup();
-                assert.ok(results.every(element => element));
+                const result = results.every(element => element);
+                if (!result) {
+                    console.log(results);
+                }
+                assert.ok(result);
             }
         );
     });
@@ -199,7 +215,11 @@ describe('TopNav', function () {
                     $('#rawtextbutton').hasClass('hidden')
                 );
                 cleanup();
-                assert.ok(results.every(element => element));
+                const result = results.every(element => element);
+                if (!result) {
+                    console.log(results);
+                }
+                assert.ok(result);
             }
         );
     });
@@ -233,7 +253,11 @@ describe('TopNav', function () {
                     $('#filewrap').hasClass('hidden')
                 );
                 cleanup();
-                assert.ok(results.every(element => element));
+                const result = results.every(element => element);
+                if (!result) {
+                    console.log(results);
+                }
+                assert.ok(result);
             }
         );
     });
@@ -267,7 +291,11 @@ describe('TopNav', function () {
                     !$('#customattachment').hasClass('hidden')
                 );
                 cleanup();
-                assert.ok(results.every(element => element));
+                const result = results.every(element => element);
+                if (!result) {
+                    console.log(results);
+                }
+                assert.ok(result);
             }
         );
     });
@@ -295,12 +323,12 @@ describe('TopNav', function () {
                 $.PrivateBin.TopNav.init();
                 results.push(
                     $('.navbar-toggle').hasClass('collapsed') &&
-                    $('#navbar').attr('aria-expanded') != 'true'
+                    $('#navbar').attr('aria-expanded') !== 'true'
                 );
                 $.PrivateBin.TopNav.collapseBar();
                 results.push(
                     $('.navbar-toggle').hasClass('collapsed') &&
-                    $('#navbar').attr('aria-expanded') != 'true'
+                    $('#navbar').attr('aria-expanded') !== 'true'
                 );
                 /*
                 with the upgrade for bootstrap-3.3.7.js to bootstrap-3.4.1.js
@@ -318,7 +346,11 @@ describe('TopNav', function () {
                 );
                 */
                 clean();
-                assert.ok(results.every(element => element));
+                const result = results.every(element => element);
+                if (!result) {
+                    console.log(results);
+                }
+                assert.ok(result);
             }
         );
     });
@@ -364,7 +396,11 @@ describe('TopNav', function () {
                     !$.PrivateBin.TopNav.getOpenDiscussion()
                 );
                 cleanup();
-                assert.ok(results.every(element => element));
+                const result = results.every(element => element);
+                if (!result) {
+                    console.log(results);
+                }
+                assert.ok(result);
             }
         );
 
@@ -403,7 +439,11 @@ describe('TopNav', function () {
                     !$.PrivateBin.TopNav.getOpenDiscussion()
                 );
                 cleanup();
-                assert.ok(results.every(element => element));
+                const result = results.every(element => element);
+                if (!result) {
+                    console.log(results);
+                }
+                assert.ok(result);
             }
         );
 
@@ -443,7 +483,11 @@ describe('TopNav', function () {
                     $.PrivateBin.TopNav.getOpenDiscussion()
                 );
                 cleanup();
-                assert.ok(results.every(element => element));
+                const result = results.every(element => element);
+                if (!result) {
+                    console.log(results);
+                }
+                assert.ok(result);
             }
         );
     });
@@ -462,7 +506,7 @@ describe('TopNav', function () {
                     '<option value="never">Never</option></select>'
                 );
                 $.PrivateBin.TopNav.init();
-                assert.ok($.PrivateBin.TopNav.getExpiration() === '1day');
+                assert.strictEqual($.PrivateBin.TopNav.getExpiration(), '1day');
                 cleanup();
             }
         );
@@ -475,8 +519,8 @@ describe('TopNav', function () {
 
         var File = window.File,
             FileList = window.FileList,
-            path = require('path'),
-            mime = require('mime-types');
+            path = require('path'), // eslint-disable-line global-require
+            mime = require('mime-types'); // eslint-disable-line global-require
 
         // mocking file input as per https://github.com/jsdom/jsdom/issues/1272
         function createFile(file_path) {
@@ -489,27 +533,27 @@ describe('TopNav', function () {
                 path.basename(file_path),
                 {
                     lastModified,
-                    type: mime.lookup(file_path) || '',
+                    type: mime.lookup(file_path) || ''
                 }
-            )
+            );
         }
 
         function addFileList(input, file_paths) {
-            if (typeof file_paths === 'string')
-                file_paths = [file_paths]
-            else if (!Array.isArray(file_paths)) {
-                throw new Error('file_paths needs to be a file path string or an Array of file path strings')
+            if (typeof file_paths === 'string') {
+                file_paths = [file_paths];
+            } else if (!Array.isArray(file_paths)) {
+                throw new Error('file_paths needs to be a file path string or an Array of file path strings');
             }
 
-            const file_list = file_paths.map(fp => createFile(fp))
-            file_list.__proto__ = Object.create(FileList.prototype)
+            const file_list = file_paths.map(fp => createFile(fp));
+            Object.setPrototypeOf(file_list, Object.create(FileList.prototype));
 
             Object.defineProperty(input, 'files', {
                 value: file_list,
-                writeable: false,
-            })
+                writeable: false
+            });
 
-            return input
+            return input;
         }
 
         it(
@@ -541,7 +585,11 @@ describe('TopNav', function () {
                     files[1].name === 'busy.gif'
                 );
                 cleanup();
-                assert.ok(results.every(element => element));
+                const result = results.every(element => element);
+                if (!result) {
+                    console.log(results);
+                }
+                assert.ok(result);
             }
         );
     });
@@ -574,7 +622,11 @@ describe('TopNav', function () {
                     !$.PrivateBin.TopNav.getBurnAfterReading()
                 );
                 cleanup();
-                assert.ok(results.every(element => element));
+                const result = results.every(element => element);
+                if (!result) {
+                    console.log(results);
+                }
+                assert.ok(result);
             }
         );
     });
@@ -607,7 +659,11 @@ describe('TopNav', function () {
                     !$.PrivateBin.TopNav.getOpenDiscussion()
                 );
                 cleanup();
-                assert.ok(results.every(element => element));
+                const result = results.every(element => element);
+                if (!result) {
+                    console.log(results);
+                }
+                assert.ok(result);
             }
         );
     });
@@ -642,7 +698,11 @@ describe('TopNav', function () {
                     $.PrivateBin.TopNav.getPassword() === ''
                 );
                 cleanup();
-                return results.every(element => element);
+                const result = results.every(element => element);
+                if (!result) {
+                    console.log(results);
+                }
+                return result;
             }
         );
     });
@@ -676,7 +736,11 @@ describe('TopNav', function () {
                     $.PrivateBin.TopNav.getCustomAttachment().hasClass('test')
                 );
                 cleanup();
-                assert.ok(results.every(element => element));
+                const result = results.every(element => element);
+                if (!result) {
+                    console.log(results);
+                }
+                assert.ok(result);
             }
         );
     });
@@ -738,7 +802,7 @@ describe('TopNav', function () {
                 $.PrivateBin.Helper.reset();
                 $.PrivateBin.TopNav.init();
                 $('#rawtextbutton').click();
-                assert.equal($('pre').text(), sample);
+                assert.strictEqual($('pre').text(), sample);
                 clean();
             }
         );

+ 8 - 8
js/test/emailTemplateTest.js

@@ -37,7 +37,7 @@ function buildEmailDomWithShortUrl() {
         '</ul></div></nav>' +
         '<input id="burnafterreadingoption" type="checkbox">' +
         '<div id="pastelink">Your document is ' +
-          `<a id="pasteurl" href="'https://short.example/xYz'">'https://short.example/xYz'</a> ` +
+          '<a id="pasteurl" href="https://short.example/xYz">https://short.example/xYz</a> ' +
           '<span id="copyhint">(Hit <kbd>Ctrl</kbd>+<kbd>c</kbd> to copy)</span>' +
         '</div>'
     );
@@ -59,7 +59,7 @@ function stubWinOpen($element) {
             openedUrl = url;
             return {};
         };
-    } catch {
+    } catch (e) {
         Object.defineProperty(win, 'open', {
             value: function (url) {
                 openedUrl = url;
@@ -72,7 +72,7 @@ function stubWinOpen($element) {
 
     return {
         getUrl: () => openedUrl,
-        restore: () => { try { win.open = origOpen; } catch {} },
+        restore: () => { try { win.open = origOpen; } catch (e) { /* suppress exception in restore */ } },
         win
     };
 }
@@ -80,7 +80,7 @@ function stubWinOpen($element) {
 
 // Extract and decode the body from a "mailto:?body=..." URL.
 function extractMailtoBody(mailtoUrl) {
-    assert.ok(/^mailto:\?body=/.test(mailtoUrl), 'expected a mailto:?body= URL');
+    assert.match(mailtoUrl, /^mailto:\?body=/, 'expected a mailto:?body= URL');
     return decodeURIComponent(mailtoUrl.replace(/^mailto:\?body=/, ''));
 }
 
@@ -104,8 +104,8 @@ describe('Email - mail body content (short URL vs. fallback)', function () {
             assert.ok(openedUrl, 'window.open should have been called');
 
             const body = extractMailtoBody(openedUrl);
-            assert.ok(body.includes('https://short.example/xYz'), 'email body should include the short URL');
-            assert.ok(!body.includes('undefined'), 'email body must not contain "undefined"');
+            assert.match(body, /https:\/\/short\.example\/xYz/, 'email body should include the short URL');
+            assert.doesNotMatch(body, /undefined/, 'email body must not contain "undefined"');
         } finally {
             restore();
             cleanup();
@@ -127,8 +127,8 @@ describe('Email - mail body content (short URL vs. fallback)', function () {
             assert.ok(openedUrl, 'window.open should have been called');
 
             const body = extractMailtoBody(openedUrl);
-            assert.ok(body.includes(win.location.href), 'email body should include the fallback page URL');
-            assert.ok(!body.includes('undefined'), 'email body must not contain "undefined"');
+            assert.match(body, new RegExp(win.location.href), 'email body should include the fallback page URL');
+            assert.doesNotMatch(body, /undefined/, 'email body must not contain "undefined"');
         } finally {
             restore();
             cleanup();

+ 2 - 2
lib/Configuration.php

@@ -119,9 +119,9 @@ class Configuration
             'js/dark-mode-switch.js' => 'sha512-BhY7dNU14aDN5L+muoUmA66x0CkYUWkQT0nxhKBLP/o2d7jE025+dvWJa4OiYffBGEFgmhrD/Sp+QMkxGMTz2g==',
             'js/jquery-3.7.1.js'     => 'sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==',
             'js/kjua-0.10.0.js'      => 'sha512-BYj4xggowR7QD150VLSTRlzH62YPfhpIM+b/1EUEr7RQpdWAGKulxWnOvjFx1FUlba4m6ihpNYuQab51H6XlYg==',
-            'js/legacy.js'           => 'sha512-mXAgFn/DonfPANvPO6Kf08zRKCeQ75jXK69gcUUOpPLFkp3KHnDhnvij8nEJxlutD/670Bfi4RNMG6uEjA4nNQ==',
+            'js/legacy.js'           => 'sha512-rGXYUpIqbFoHAgBXZ0UlJBdNAIMOC9EQ67MG0X46D5uRB8LvwzgKirbSQRGdYfk8I2jsUcm+tvHXYboUnC6DUg==',
             'js/prettify.js'         => 'sha512-puO0Ogy++IoA2Pb9IjSxV1n4+kQkKXYAEUtVzfZpQepyDPyXk8hokiYDS7ybMogYlyyEIwMLpZqVhCkARQWLMg==',
-            'js/privatebin.js'       => 'sha512-ua90kdFdjCfjRLX3WnqJdLI1HksNlvbmEdwVT3Ct0vsOhP/68gEJ3tucD/3vK5pcsVdWuJb+5ZkfymvS2QbERQ==',
+            'js/privatebin.js'       => 'sha512-28TbF+KEbUv22X+tVm5a0ik1qQGtjqcKoSuXA+jFZqC9zyFF8Km+V2rSDqwgF0hf38WMVp5F2k0T6u/bKJLpSQ==',
             'js/purify-3.2.6.js'     => 'sha512-zqwL4OoBLFx89QPewkz4Lz5CSA2ktU+f31fuECkF0iK3Id5qd3Zpq5dMby8KwHjIEpsUgOqwF58cnmcaNem0EA==',
             'js/showdown-2.1.0.js'   => 'sha512-WYXZgkTR0u/Y9SVIA4nTTOih0kXMEd8RRV6MLFdL6YU8ymhR528NLlYQt1nlJQbYz4EW+ZsS0fx1awhiQJme1Q==',
             'js/zlib-1.3.1-1.js'     => 'sha512-5bU9IIP4PgBrOKLZvGWJD4kgfQrkTz8Z3Iqeu058mbQzW3mCumOU6M3UVbVZU9rrVoVwaW4cZK8U8h5xjF88eQ==',

+ 1 - 1
tst/Bootstrap.php

@@ -334,7 +334,7 @@ class Helper
     public static function updateSubresourceIntegrity(): void
     {
         foreach (new GlobIterator(PATH . 'js' . DIRECTORY_SEPARATOR . '*.js') as $file) {
-            if ($file->getBasename() == 'common.js') {
+            if ($file->getBasename() === 'common.js' || $file->getBasename() === 'eslint.config.js') {
                 continue; // ignore JS unit test bootstrap
             }
             self::$hashes[$file->getBasename()] = base64_encode(

Деякі файли не було показано, через те що забагато файлів було змінено