<commit_before>"use strict";
var async           = require('async');
var co              = require('co');
var Q               = require('q');
var constants       = require('../constants');
var base58          = require('../crypto/base58');
var childProcess    = require('child_process');
var path            = require('path');
var Block           = require('../entity/block');

module.exports = () => new BlockGenerator();

function BlockGenerator() {

  var conf, dal, pair, logger;

  this.setConfDAL = (newConf, newDAL, newPair) => {
    dal = newDAL;
    conf = newConf;
    pair = newPair;
    logger = require('../logger')(dal.profile);
  };

  var cancels = [];

  var debug = process.execArgv.toString().indexOf('--debug') !== -1;
  if(debug) {
    //Set an unused port number.
    process.execArgv = [];
  }
  var powWorker;

  var powFifo = async.queue(function (task, callback) {
    task(callback);
  }, 1);

  // Callback used to start again computation of next PoW
  var computeNextCallback = null;

  // Flag indicating the PoW has begun
  var computing = false;

  this.computing = () => computing = true;

  this.notComputing = () => computing = false;

  this.waitForContinue = () => Q.Promise(function(resolve){
    computeNextCallback = resolve;
  });

  this.cancel = () => {
    // If PoW computation process is waiting, trigger it
    if (computeNextCallback)
      computeNextCallback();
    if (conf.participate && !cancels.length && computing) {
      powFifo.push(function (taskDone) {
        cancels.push(taskDone);
      });
    }
  };

  this.waitBeforePoW = () => Q.Promise(function(resolve, reject){
    var timeoutToClear = setTimeout(function() {
      clearTimeout(timeoutToClear);
      computeNextCallback = null;
      resolve();
    }, (conf.powDelay) * 1000);
    // Offer the possibility to break waiting
    computeNextCallback = function() {
      clearTimeout(timeoutToClear);
      reject('Waiting canceled.');
    };
  });

  this.prove = function (block, difficulty, forcedTime) {

    const remainder = difficulty % 16;
    const nbZeros = (difficulty - remainder) / 16;
    const highMark = constants.PROOF_OF_WORK.UPPER_BOUND[remainder];

    return Q.Promise(function(resolve, reject){
      if (!powWorker) {
        powWorker = new Worker();
      }
      if (block.number == 0) {
        // On initial block, difficulty is the one given manually
        block.powMin = difficulty;
      }
      // Start
      powWorker.setOnPoW(function(err, powBlock) {
        const theBlock = (powBlock && new Block(powBlock)) || null;
        resolve(theBlock);
      });

      powWorker.setOnError((err) => {
        reject(err);
      });

      block.nonce = 0;
      console.log("Prover send");
      powWorker.powProcess.send({ conf: conf, block: block, zeros: nbZeros, highMark: highMark, forcedTime: forcedTime,
        pair: {
          secretKeyEnc: base58.encode(pair.secretKey)
        }
      });
      logger.info('Generating proof-of-work with %s leading zeros followed by [0-' + highMark + ']... (CPU usage set to %s%)', nbZeros, (conf.cpu * 100).toFixed(0));
    });
  };

  function Worker() {

    var stopped = true;
    var that = this;
    var onPoWFound = function() { throw 'Proof-of-work found, but no listener is attached.'; };
    var onPoWError = function() { throw 'Proof-of-work error, but no listener is attached.'; };
    that.powProcess = childProcess.fork(path.join(__dirname, '../proof.js'));
    var start = null;
    var speedMesured = false;

    that.powProcess.on('message', function(msg) {
      var block = msg.block;
      if (msg.error) {
        onPoWError(msg.error);
        stopped = true;
      }
      if (stopped) {
        // Started...
        start = new Date();
        stopped = false;
      }
      if (!stopped && msg.found) {
        var end = new Date();
        var duration = (end.getTime() - start.getTime());
        var testsPerSecond = (1000 / duration * msg.testsCount).toFixed(2);
        logger.info('Done: %s in %ss (%s tests, ~%s tests/s)', msg.pow, (duration / 1000).toFixed(2), msg.testsCount, testsPerSecond);
        stopped = true;
        start = null;
        block.hash = msg.pow;
        onPoWFound(null, block);
      } else if (!stopped && msg.testsPerRound) {
        logger.info('Mesured max speed is ~%s tests/s. Proof will try with ~%s tests/s.', msg.testsPerSecond, msg.testsPerRound);
        speedMesured = true;
      } else if (!stopped && msg.nonce > block.nonce + constants.PROOF_OF_WORK.RELEASE_MEMORY) {
        // Reset fork process (release memory)...
        logger.trace('Release mem... lastCount = %s, nonce = %s', block.nonce);
        block.nonce = msg.nonce;
        speedMesured = false;
        that.powProcess.kill();
        powWorker = new Worker();
        that.powProcess.send({ conf: conf, block: block, zeros: msg.nbZeros, pair: {
          secretKeyEnc: base58.encode(pair.secretKey)
        }
        });
      } else if (!stopped) {

        if (!msg.found) {
          var pow = msg.pow;
          for (let i = 5; i >= 3; i--) {
            var lowPowRegexp = new RegExp('^0{' + (i) + '}[^0]');
            if (pow.match(lowPowRegexp)) {
              logger.info('Matched %s zeros %s with Nonce = %s for block#%s', i, pow, msg.block.nonce, msg.block.number);
              break;
            }
          }
        }
        // Continue...
        //console.log('Already made: %s tests...', msg.nonce);
        // Look for incoming block
        if (speedMesured && cancels.length) {
          speedMesured = false;
          stopped = true;
          that.powProcess.kill();
          that.powProcess = null;
          powWorker = null;
          onPoWFound();
          logger.debug('Proof-of-work computation canceled.');
          start = null;
          var cancelConfirm = cancels.shift();
          cancelConfirm();
        }
      }
    });

    this.kill = function() {
      if (that.powProcess) {
        that.powProcess.kill();
        that.powProcess = null;
      }
    };

    this.setOnPoW = function(onPoW) {
      onPoWFound = onPoW;
    };

    this.setOnError = function(onError) {
      onPoWError = onError;
    };
  }
}
<commit_msg>Clean blockProver<commit_after>"use strict";
const async           = require('async');
const co              = require('co');
const Q               = require('q');
const constants       = require('../constants');
const base58          = require('../crypto/base58');
const childProcess    = require('child_process');
const path            = require('path');
const Block           = require('../entity/block');

module.exports = () => new BlockGenerator();

function BlockGenerator() {

  let conf, dal, pair, logger;

  this.setConfDAL = (newConf, newDAL, newPair) => {
    dal = newDAL;
    conf = newConf;
    pair = newPair;
    logger = require('../logger')(dal.profile);
  };

  var cancels = [];

  const debug = process.execArgv.toString().indexOf('--debug') !== -1;
  if(debug) {
    //Set an unused port number.
    process.execArgv = [];
  }
  let powWorker;

  const powFifo = async.queue(function (task, callback) {
    task(callback);
  }, 1);

  // Callback used to start again computation of next PoW
  let computeNextCallback = null;

  // Flag indicating the PoW has begun
  let computing = false;

  this.computing = () => computing = true;

  this.notComputing = () => computing = false;

  this.waitForContinue = () => Q.Promise(function(resolve){
    computeNextCallback = resolve;
  });

  this.cancel = () => {
    // If PoW computation process is waiting, trigger it
    if (computeNextCallback)
      computeNextCallback();
    if (conf.participate && !cancels.length && computing) {
      powFifo.push(function (taskDone) {
        cancels.push(taskDone);
      });
    }
  };

  this.waitBeforePoW = () => Q.Promise(function(resolve, reject){
    var timeoutToClear = setTimeout(function() {
      clearTimeout(timeoutToClear);
      computeNextCallback = null;
      resolve();
    }, (conf.powDelay) * 1000);
    // Offer the possibility to break waiting
    computeNextCallback = function() {
      clearTimeout(timeoutToClear);
      reject('Waiting canceled.');
    };
  });

  this.prove = function (block, difficulty, forcedTime) {

    const remainder = difficulty % 16;
    const nbZeros = (difficulty - remainder) / 16;
    const highMark = constants.PROOF_OF_WORK.UPPER_BOUND[remainder];

    return Q.Promise(function(resolve, reject){
      if (!powWorker) {
        powWorker = new Worker();
      }
      if (block.number == 0) {
        // On initial block, difficulty is the one given manually
        block.powMin = difficulty;
      }
      // Start
      powWorker.setOnPoW(function(err, powBlock) {
        const theBlock = (powBlock && new Block(powBlock)) || null;
        resolve(theBlock);
      });

      powWorker.setOnError((err) => {
        reject(err);
      });

      block.nonce = 0;
      console.log("Prover send");
      powWorker.powProcess.send({ conf: conf, block: block, zeros: nbZeros, highMark: highMark, forcedTime: forcedTime,
        pair: {
          secretKeyEnc: base58.encode(pair.secretKey)
        }
      });
      logger.info('Generating proof-of-work with %s leading zeros followed by [0-' + highMark + ']... (CPU usage set to %s%)', nbZeros, (conf.cpu * 100).toFixed(0));
    });
  };

  function Worker() {

    let stopped = true;
    const that = this;
    let onPoWFound = function() { throw 'Proof-of-work found, but no listener is attached.'; };
    let onPoWError = function() { throw 'Proof-of-work error, but no listener is attached.'; };
    that.powProcess = childProcess.fork(path.join(__dirname, '../proof.js'));
    let start = null;
    let speedMesured = false;

    that.powProcess.on('message', function(msg) {
      const block = msg.block;
      if (msg.error) {
        onPoWError(msg.error);
        stopped = true;
      }
      if (stopped) {
        // Started...
        start = new Date();
        stopped = false;
      }
      if (!stopped && msg.found) {
        const end = new Date();
        const duration = (end.getTime() - start.getTime());
        const testsPerSecond = (1000 / duration * msg.testsCount).toFixed(2);
        logger.info('Done: %s in %ss (%s tests, ~%s tests/s)', msg.pow, (duration / 1000).toFixed(2), msg.testsCount, testsPerSecond);
        stopped = true;
        start = null;
        block.hash = msg.pow;
        onPoWFound(null, block);
      } else if (!stopped && msg.testsPerRound) {
        logger.info('Mesured max speed is ~%s tests/s. Proof will try with ~%s tests/s.', msg.testsPerSecond, msg.testsPerRound);
        speedMesured = true;
      } else if (!stopped && msg.nonce > block.nonce + constants.PROOF_OF_WORK.RELEASE_MEMORY) {
        // Reset fork process (release memory)...
        logger.trace('Release mem... lastCount = %s, nonce = %s', block.nonce);
        block.nonce = msg.nonce;
        speedMesured = false;
        that.powProcess.kill();
        powWorker = new Worker();
        that.powProcess.send({ conf: conf, block: block, zeros: msg.nbZeros, pair: {
          secretKeyEnc: base58.encode(pair.secretKey)
        }
        });
      } else if (!stopped) {

        if (!msg.found) {
          const pow = msg.pow;
          for (let i = 5; i >= 3; i--) {
            const lowPowRegexp = new RegExp('^0{' + (i) + '}[^0]');
            if (pow.match(lowPowRegexp)) {
              logger.info('Matched %s zeros %s with Nonce = %s for block#%s', i, pow, msg.block.nonce, msg.block.number);
              break;
            }
          }
        }
        // Continue...
        //console.log('Already made: %s tests...', msg.nonce);
        // Look for incoming block
        if (speedMesured && cancels.length) {
          speedMesured = false;
          stopped = true;
          that.powProcess.kill();
          that.powProcess = null;
          powWorker = null;
          onPoWFound();
          logger.debug('Proof-of-work computation canceled.');
          start = null;
          const cancelConfirm = cancels.shift();
          cancelConfirm();
        }
      }
    });

    this.kill = function() {
      if (that.powProcess) {
        that.powProcess.kill();
        that.powProcess = null;
      }
    };

    this.setOnPoW = function(onPoW) {
      onPoWFound = onPoW;
    };

    this.setOnError = function(onError) {
      onPoWError = onError;
    };
  }
}
<|endoftext|>


<commit_before>const urlModule = require('url');
const _ = require('lodash');
const crypto = require('crypto');
const ContentSecurityPolicy = require('../assets/ContentSecurityPolicy');

const directiveByRelationType = {
    HtmlStyle: 'styleSrc',
    CssImport: 'styleSrc',
    HtmlScript: 'scriptSrc',
    CssFontFaceSrc: 'fontSrc',
    HtmlObject: 'objectSrc',
    HtmlApplet: 'objectSrc',
    HtmlImage: 'imgSrc',
    CssImage: 'imgSrc',
    HtmlShortcutIcon: 'imgSrc',
    HtmlPictureSource: 'imgSrc',
    HtmlFluidIconLink: 'imgSrc',
    SrcSetEntry: 'imgSrc',
    HtmlLogo: 'imgSrc',
    HtmlAppleTouchStartupImage: 'imgSrc',
    HtmlVideoPoster: 'imgSrc',
    HtmlVideo: 'mediaSrc',
    HtmlAudio: 'mediaSrc',
    HtmlFrame: 'frameSrc',
    HtmlIFrame: 'frameSrc',
    HtmlApplicationManifest: 'manifestSrc',
    JavaScriptFetch: 'connectSrc'
};

function toCamelCase(str) {
    return str.replace(/-([a-z])/g, ($0, ch) => ch.toUpperCase());
}

function fromCamelCase(str) {
    return str.replace(/[A-Z]/g, $0 => '-' + $0.toLowerCase());
}

function isNonceOrHash(sourceExpression) {
    return /^'nonce-|^'sha\d+-/.test(sourceExpression);
}

module.exports = (queryObj, options) => {
    options = options || {};
    return function reviewContentSecurityPolicy(assetGraph) {
        const infoObject = {};
        const includePathByDirective = {};
        if (options.includePath) {
            for (const directive of _.flatten(options.includePath)) {
                includePathByDirective[toCamelCase(directive)] = true;
            }
        }

        function originFromUrl(url, directive) {
            if (assetGraph.root && url.startsWith(assetGraph.root)) {
                return "'self'";
            } else if (options.includePath === true || includePathByDirective[directive]) {
                return url.replace(/^https?:\/\//, '');
            } else {
                const urlObj = urlModule.parse(url);
                let host = urlObj.hostname;
                if (urlObj.port && parseInt(urlObj.port, 10) !== {'https:': 443, 'http:': 80}[urlObj.protocol]) {
                    host += ':' + urlObj.port;
                }
                return host;
            }
        }

        for (const htmlAsset of assetGraph.findAssets(queryObj || { type: 'Html', isInline: false, isFragment: false, isLoaded: true })) {
            const htmlContentSecurityPolicies = assetGraph.findRelations({from: htmlAsset, type: 'HtmlContentSecurityPolicy'});
            for (const htmlContentSecurityPolicy of htmlContentSecurityPolicies) {
                const contentSecurityPolicy = htmlContentSecurityPolicy.to;
                const defaultSrc = contentSecurityPolicy.parseTree.defaultSrc;

                function supportsUnsafeInline(directive) {
                    return (
                        contentSecurityPolicy.parseTree[directive] ?
                            contentSecurityPolicy.parseTree[directive].includes("'unsafe-inline'") :
                            defaultSrc && ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && defaultSrc.includes("'unsafe-inline'")
                    );
                }

                function supportsHashSource(directive, hashSource) {
                    return (
                        contentSecurityPolicy.parseTree[directive] ?
                            contentSecurityPolicy.parseTree[directive].includes(hashSource) :
                            defaultSrc && ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && defaultSrc.includes(hashSource)
                    );
                }

                function supportsData(directive) {
                    return (
                        contentSecurityPolicy.parseTree[directive] ?
                            contentSecurityPolicy.parseTree[directive].includes('data:') :
                            defaultSrc && ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && defaultSrc.includes('data:')
                    );
                }

                function hasNonceOrHash(directive) {
                    return (
                        contentSecurityPolicy.parseTree[directive] ?
                            contentSecurityPolicy.parseTree[directive].some(isNonceOrHash) :
                            defaultSrc && ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && defaultSrc.some(isNonceOrHash)
                    );
                }

                const disallowedRelationsByDirectiveAndOrigin = {};
                const seenNoncesByDirective = {};
                function noteAsset(asset, incomingRelation, directive) {
                    directive = directive || (incomingRelation && directiveByRelationType[incomingRelation.type]);
                    if (directive) {
                        if (asset.isInline) {
                            if ((directive === 'styleSrc' || directive === 'scriptSrc') && (!supportsUnsafeInline(directive) || hasNonceOrHash(directive))) {
                                const hashSource = "'sha256-" + crypto.createHash('sha256').update(asset.rawSrc).digest('base64') + "'";
                                if (!supportsHashSource(directive, hashSource)) {
                                    disallowedRelationsByDirectiveAndOrigin[directive] = disallowedRelationsByDirectiveAndOrigin[directive] || {};
                                    (disallowedRelationsByDirectiveAndOrigin[directive][hashSource] = disallowedRelationsByDirectiveAndOrigin[directive][hashSource] || []).push(incomingRelation);
                                }
                            } else if (/^data:/.test(incomingRelation.href) && !supportsData(directive)) {
                                disallowedRelationsByDirectiveAndOrigin[directive] = disallowedRelationsByDirectiveAndOrigin[directive] || {};
                                (disallowedRelationsByDirectiveAndOrigin[directive]['data:'] = disallowedRelationsByDirectiveAndOrigin[directive]['data:'] || []).push(incomingRelation);
                            }
                        } else if (!contentSecurityPolicy.allows(directive, asset.url)) {
                            disallowedRelationsByDirectiveAndOrigin[directive] = disallowedRelationsByDirectiveAndOrigin[directive] || {};
                            const origin = originFromUrl(asset.url, directive);
                            (disallowedRelationsByDirectiveAndOrigin[directive][origin] = disallowedRelationsByDirectiveAndOrigin[directive][origin] || []).push(incomingRelation);
                        }
                        if (incomingRelation.from.type === 'Html') {
                            const nonce = incomingRelation.node.getAttribute('nonce');
                            if (nonce) {
                                (seenNoncesByDirective[directive] = seenNoncesByDirective[directive] || []).push("'nonce-" + nonce + "'");
                                if (options.update) {
                                    incomingRelation.node.removeAttribute('nonce');
                                    incomingRelation.from.markDirty();
                                }
                            }
                        }
                    }
                }

                const isSeenByAssetId = {
                    [htmlAsset.id]: true
                };
                assetGraph._traverse(htmlAsset, {type: {$nin: ['HtmlAnchor', 'HtmlMetaRefresh', 'JavaScriptSourceUrl', 'JavaScriptSourceMappingUrl', 'CssSourceUrl', 'CssSourceMappingUrl']}}, (asset, incomingRelation) => {
                    isSeenByAssetId[asset.id] = true;
                    if (incomingRelation && incomingRelation.type === 'HttpRedirect') {
                        // Work backwards through all the seen assets to find all relevant paths through this redirect:
                        const isSeenByRelationId = {};
                        const queue = [
                            ...incomingRelation.from.incomingRelations.filter(
                                relation => isSeenByAssetId[relation.from.id]
                            )
                        ];
                        while (queue.length > 0) {
                            const relation = queue.shift();
                            if (!isSeenByRelationId[relation.id]) {
                                isSeenByRelationId[relation.id] = true;
                                if (relation.type === 'HttpRedirect') {
                                    queue.push(...relation.from.incomingRelations.filter(relation => {
                                        return isSeenByAssetId[relation.from.id] && !isSeenByRelationId[relation.id];
                                    }));
                                } else {
                                    noteAsset(asset, incomingRelation, directiveByRelationType[relation.type]);
                                }
                            }
                        }
                    } else {
                        noteAsset(asset, incomingRelation);
                    }
                });

                const directives = _.uniq([
                    ...Object.keys(disallowedRelationsByDirectiveAndOrigin),
                    ...Object.keys(seenNoncesByDirective),
                    ...Object.keys(contentSecurityPolicy.parseTree)
                ]);

                infoObject[contentSecurityPolicy.id] = {
                    additions: disallowedRelationsByDirectiveAndOrigin
                };

                for (const directive of directives) {
                    let origins = Object.keys(disallowedRelationsByDirectiveAndOrigin[directive] || {});
                    if (options.update) {
                        if (contentSecurityPolicy.parseTree[directive]) {
                            origins = _.uniq([...origins, ...contentSecurityPolicy.parseTree[directive]]);
                        } else if (ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && contentSecurityPolicy.parseTree.defaultSrc) {
                            origins = _.uniq([...origins, ...contentSecurityPolicy.parseTree.defaultSrc]);
                        }
                        let noncesWereRemoved = false;
                        const seenNonces = seenNoncesByDirective[directive] || [];
                        const originsWithNoncesSubtracted = _.difference(
                            origins,
                            ["'nonce-developmentonly'", ...seenNonces]
                        );
                        if (originsWithNoncesSubtracted.length < origins.length) {
                            noncesWereRemoved = true;
                        }
                        origins = originsWithNoncesSubtracted;

                        // If we're removing 'nonce-developmentonly' when 'unsafe-inline' is in effect and there are no other
                        // nonces or hashes whitelisted, add the sha256 hash of the empty string to the list of allowed origins.
                        // This prevents 'unsafe-inline' from taking effect in CSP2+ compliant browsers that would otherwise
                        // ignore 'unsafe-inline' when at least one nonce or hash is present for the directive.
                        // https://www.w3.org/TR/CSP3/#match-element-to-source-list
                        if (noncesWereRemoved && origins.includes("'unsafe-inline'") && !origins.some(isNonceOrHash)) {
                            origins.push("'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='");
                        } else if (origins.some(isNonceOrHash) && !origins.includes("'unsafe-inline'")) {
                            origins.push("'unsafe-inline'");
                        }
                        if (options.level < 2) {
                            origins = origins.filter(origin => !isNonceOrHash(origin));
                        }
                        if (origins.length > 1) {
                            const indexOfNoneToken = origins.indexOf("'none'");
                            if (indexOfNoneToken !== -1) {
                                origins.splice(indexOfNoneToken, 1);
                            }
                        }
                        if (directive !== 'defaultSrc' && defaultSrc && ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && _.difference(defaultSrc, origins).length === 0 && _.difference(origins, defaultSrc).length === 0) {
                            delete contentSecurityPolicy.parseTree[directive];
                        } else {
                            contentSecurityPolicy.parseTree[directive] = origins.sort();
                        }
                        contentSecurityPolicy.markDirty();
                    } else {
                        let allowedOrigins = contentSecurityPolicy.parseTree[directive];
                        let directiveInEffect = directive;
                        if (!allowedOrigins) {
                            if (directive !== 'frameAncestors' && contentSecurityPolicy.parseTree.defaultSrc) {
                                directiveInEffect = 'defaultSrc';
                                allowedOrigins = contentSecurityPolicy.parseTree.defaultSrc;
                            } else {
                                allowedOrigins = ["'none'"];
                            }
                        }
                        for (const nonWhitelistedOrigin of origins) {
                            let relations = disallowedRelationsByDirectiveAndOrigin[directive][nonWhitelistedOrigin];
                            if (relations) {
                                relations = relations.filter(relation => {
                                    let nonce;
                                    if (relation.from.type === 'Html') {
                                        nonce = relation.node.getAttribute('nonce');
                                    }
                                    return !nonce || !allowedOrigins.includes("'nonce-" + nonce + "'");
                                });
                                if (relations.length > 0) {
                                    assetGraph.emit(
                                        'warn',
                                        new Error(
                                            htmlAsset.urlOrDescription + ': ' + (relations.length === 1 ? 'An asset violates' : relations.length + ' relations violate') + ' the ' + fromCamelCase(directiveInEffect) + ' ' + allowedOrigins.join(' ') + ' Content-Security-Policy directive:\n  ' +
                                            relations.map(asset => asset.to.urlOrDescription).join('\n  ')
                                        )
                                    );
                                }
                            }
                        }
                    }
                }
            }
        }
        return infoObject;
    };
};
<commit_msg>Use Set for seen assets/relations housekeeping<commit_after>const urlModule = require('url');
const _ = require('lodash');
const crypto = require('crypto');
const ContentSecurityPolicy = require('../assets/ContentSecurityPolicy');

const directiveByRelationType = {
    HtmlStyle: 'styleSrc',
    CssImport: 'styleSrc',
    HtmlScript: 'scriptSrc',
    CssFontFaceSrc: 'fontSrc',
    HtmlObject: 'objectSrc',
    HtmlApplet: 'objectSrc',
    HtmlImage: 'imgSrc',
    CssImage: 'imgSrc',
    HtmlShortcutIcon: 'imgSrc',
    HtmlPictureSource: 'imgSrc',
    HtmlFluidIconLink: 'imgSrc',
    SrcSetEntry: 'imgSrc',
    HtmlLogo: 'imgSrc',
    HtmlAppleTouchStartupImage: 'imgSrc',
    HtmlVideoPoster: 'imgSrc',
    HtmlVideo: 'mediaSrc',
    HtmlAudio: 'mediaSrc',
    HtmlFrame: 'frameSrc',
    HtmlIFrame: 'frameSrc',
    HtmlApplicationManifest: 'manifestSrc',
    JavaScriptFetch: 'connectSrc'
};

function toCamelCase(str) {
    return str.replace(/-([a-z])/g, ($0, ch) => ch.toUpperCase());
}

function fromCamelCase(str) {
    return str.replace(/[A-Z]/g, $0 => '-' + $0.toLowerCase());
}

function isNonceOrHash(sourceExpression) {
    return /^'nonce-|^'sha\d+-/.test(sourceExpression);
}

module.exports = (queryObj, options) => {
    options = options || {};
    return function reviewContentSecurityPolicy(assetGraph) {
        const infoObject = {};
        const includePathByDirective = {};
        if (options.includePath) {
            for (const directive of _.flatten(options.includePath)) {
                includePathByDirective[toCamelCase(directive)] = true;
            }
        }

        function originFromUrl(url, directive) {
            if (assetGraph.root && url.startsWith(assetGraph.root)) {
                return "'self'";
            } else if (options.includePath === true || includePathByDirective[directive]) {
                return url.replace(/^https?:\/\//, '');
            } else {
                const urlObj = urlModule.parse(url);
                let host = urlObj.hostname;
                if (urlObj.port && parseInt(urlObj.port, 10) !== {'https:': 443, 'http:': 80}[urlObj.protocol]) {
                    host += ':' + urlObj.port;
                }
                return host;
            }
        }

        for (const htmlAsset of assetGraph.findAssets(queryObj || { type: 'Html', isInline: false, isFragment: false, isLoaded: true })) {
            const htmlContentSecurityPolicies = assetGraph.findRelations({from: htmlAsset, type: 'HtmlContentSecurityPolicy'});
            for (const htmlContentSecurityPolicy of htmlContentSecurityPolicies) {
                const contentSecurityPolicy = htmlContentSecurityPolicy.to;
                const defaultSrc = contentSecurityPolicy.parseTree.defaultSrc;

                function supportsUnsafeInline(directive) {
                    return (
                        contentSecurityPolicy.parseTree[directive] ?
                            contentSecurityPolicy.parseTree[directive].includes("'unsafe-inline'") :
                            defaultSrc && ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && defaultSrc.includes("'unsafe-inline'")
                    );
                }

                function supportsHashSource(directive, hashSource) {
                    return (
                        contentSecurityPolicy.parseTree[directive] ?
                            contentSecurityPolicy.parseTree[directive].includes(hashSource) :
                            defaultSrc && ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && defaultSrc.includes(hashSource)
                    );
                }

                function supportsData(directive) {
                    return (
                        contentSecurityPolicy.parseTree[directive] ?
                            contentSecurityPolicy.parseTree[directive].includes('data:') :
                            defaultSrc && ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && defaultSrc.includes('data:')
                    );
                }

                function hasNonceOrHash(directive) {
                    return (
                        contentSecurityPolicy.parseTree[directive] ?
                            contentSecurityPolicy.parseTree[directive].some(isNonceOrHash) :
                            defaultSrc && ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && defaultSrc.some(isNonceOrHash)
                    );
                }

                const disallowedRelationsByDirectiveAndOrigin = {};
                const seenNoncesByDirective = {};
                function noteAsset(asset, incomingRelation, directive) {
                    directive = directive || (incomingRelation && directiveByRelationType[incomingRelation.type]);
                    if (directive) {
                        if (asset.isInline) {
                            if ((directive === 'styleSrc' || directive === 'scriptSrc') && (!supportsUnsafeInline(directive) || hasNonceOrHash(directive))) {
                                const hashSource = "'sha256-" + crypto.createHash('sha256').update(asset.rawSrc).digest('base64') + "'";
                                if (!supportsHashSource(directive, hashSource)) {
                                    disallowedRelationsByDirectiveAndOrigin[directive] = disallowedRelationsByDirectiveAndOrigin[directive] || {};
                                    (disallowedRelationsByDirectiveAndOrigin[directive][hashSource] = disallowedRelationsByDirectiveAndOrigin[directive][hashSource] || []).push(incomingRelation);
                                }
                            } else if (/^data:/.test(incomingRelation.href) && !supportsData(directive)) {
                                disallowedRelationsByDirectiveAndOrigin[directive] = disallowedRelationsByDirectiveAndOrigin[directive] || {};
                                (disallowedRelationsByDirectiveAndOrigin[directive]['data:'] = disallowedRelationsByDirectiveAndOrigin[directive]['data:'] || []).push(incomingRelation);
                            }
                        } else if (!contentSecurityPolicy.allows(directive, asset.url)) {
                            disallowedRelationsByDirectiveAndOrigin[directive] = disallowedRelationsByDirectiveAndOrigin[directive] || {};
                            const origin = originFromUrl(asset.url, directive);
                            (disallowedRelationsByDirectiveAndOrigin[directive][origin] = disallowedRelationsByDirectiveAndOrigin[directive][origin] || []).push(incomingRelation);
                        }
                        if (incomingRelation.from.type === 'Html') {
                            const nonce = incomingRelation.node.getAttribute('nonce');
                            if (nonce) {
                                (seenNoncesByDirective[directive] = seenNoncesByDirective[directive] || []).push("'nonce-" + nonce + "'");
                                if (options.update) {
                                    incomingRelation.node.removeAttribute('nonce');
                                    incomingRelation.from.markDirty();
                                }
                            }
                        }
                    }
                }

                const seenAssets = new Set();
                seenAssets.add(htmlAsset);
                assetGraph._traverse(htmlAsset, {type: {$nin: ['HtmlAnchor', 'HtmlMetaRefresh', 'JavaScriptSourceUrl', 'JavaScriptSourceMappingUrl', 'CssSourceUrl', 'CssSourceMappingUrl']}}, (asset, incomingRelation) => {
                    seenAssets.add(asset.id);
                    if (incomingRelation && incomingRelation.type === 'HttpRedirect') {
                        // Work backwards through all the seen assets to find all relevant paths through this redirect:
                        const seenRelations = new Set();
                        const queue = [
                            ...incomingRelation.from.incomingRelations.filter(
                                relation => seenAssets.has(relation.from)
                            )
                        ];
                        while (queue.length > 0) {
                            const relation = queue.shift();
                            if (!seenRelations.has(relation)) {
                                seenRelations.add(relation.id);
                                if (relation.type === 'HttpRedirect') {
                                    queue.push(...relation.from.incomingRelations.filter(relation => {
                                        return seenAssets.has(relation.from) && !seenRelations.has(relation);
                                    }));
                                } else {
                                    noteAsset(asset, incomingRelation, directiveByRelationType[relation.type]);
                                }
                            }
                        }
                    } else {
                        noteAsset(asset, incomingRelation);
                    }
                });

                const directives = _.uniq([
                    ...Object.keys(disallowedRelationsByDirectiveAndOrigin),
                    ...Object.keys(seenNoncesByDirective),
                    ...Object.keys(contentSecurityPolicy.parseTree)
                ]);

                infoObject[contentSecurityPolicy.id] = {
                    additions: disallowedRelationsByDirectiveAndOrigin
                };

                for (const directive of directives) {
                    let origins = Object.keys(disallowedRelationsByDirectiveAndOrigin[directive] || {});
                    if (options.update) {
                        if (contentSecurityPolicy.parseTree[directive]) {
                            origins = _.uniq([...origins, ...contentSecurityPolicy.parseTree[directive]]);
                        } else if (ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && contentSecurityPolicy.parseTree.defaultSrc) {
                            origins = _.uniq([...origins, ...contentSecurityPolicy.parseTree.defaultSrc]);
                        }
                        let noncesWereRemoved = false;
                        const seenNonces = seenNoncesByDirective[directive] || [];
                        const originsWithNoncesSubtracted = _.difference(
                            origins,
                            ["'nonce-developmentonly'", ...seenNonces]
                        );
                        if (originsWithNoncesSubtracted.length < origins.length) {
                            noncesWereRemoved = true;
                        }
                        origins = originsWithNoncesSubtracted;

                        // If we're removing 'nonce-developmentonly' when 'unsafe-inline' is in effect and there are no other
                        // nonces or hashes whitelisted, add the sha256 hash of the empty string to the list of allowed origins.
                        // This prevents 'unsafe-inline' from taking effect in CSP2+ compliant browsers that would otherwise
                        // ignore 'unsafe-inline' when at least one nonce or hash is present for the directive.
                        // https://www.w3.org/TR/CSP3/#match-element-to-source-list
                        if (noncesWereRemoved && origins.includes("'unsafe-inline'") && !origins.some(isNonceOrHash)) {
                            origins.push("'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='");
                        } else if (origins.some(isNonceOrHash) && !origins.includes("'unsafe-inline'")) {
                            origins.push("'unsafe-inline'");
                        }
                        if (options.level < 2) {
                            origins = origins.filter(origin => !isNonceOrHash(origin));
                        }
                        if (origins.length > 1) {
                            const indexOfNoneToken = origins.indexOf("'none'");
                            if (indexOfNoneToken !== -1) {
                                origins.splice(indexOfNoneToken, 1);
                            }
                        }
                        if (directive !== 'defaultSrc' && defaultSrc && ContentSecurityPolicy.directiveFallsBackToDefaultSrc(directive) && _.difference(defaultSrc, origins).length === 0 && _.difference(origins, defaultSrc).length === 0) {
                            delete contentSecurityPolicy.parseTree[directive];
                        } else {
                            contentSecurityPolicy.parseTree[directive] = origins.sort();
                        }
                        contentSecurityPolicy.markDirty();
                    } else {
                        let allowedOrigins = contentSecurityPolicy.parseTree[directive];
                        let directiveInEffect = directive;
                        if (!allowedOrigins) {
                            if (directive !== 'frameAncestors' && contentSecurityPolicy.parseTree.defaultSrc) {
                                directiveInEffect = 'defaultSrc';
                                allowedOrigins = contentSecurityPolicy.parseTree.defaultSrc;
                            } else {
                                allowedOrigins = ["'none'"];
                            }
                        }
                        for (const nonWhitelistedOrigin of origins) {
                            let relations = disallowedRelationsByDirectiveAndOrigin[directive][nonWhitelistedOrigin];
                            if (relations) {
                                relations = relations.filter(relation => {
                                    let nonce;
                                    if (relation.from.type === 'Html') {
                                        nonce = relation.node.getAttribute('nonce');
                                    }
                                    return !nonce || !allowedOrigins.includes("'nonce-" + nonce + "'");
                                });
                                if (relations.length > 0) {
                                    assetGraph.emit(
                                        'warn',
                                        new Error(
                                            htmlAsset.urlOrDescription + ': ' + (relations.length === 1 ? 'An asset violates' : relations.length + ' relations violate') + ' the ' + fromCamelCase(directiveInEffect) + ' ' + allowedOrigins.join(' ') + ' Content-Security-Policy directive:\n  ' +
                                            relations.map(asset => asset.to.urlOrDescription).join('\n  ')
                                        )
                                    );
                                }
                            }
                        }
                    }
                }
            }
        }
        return infoObject;
    };
};
<|endoftext|>


<commit_before>/**
 * Shopware 5
 * Copyright (c) shopware AG
 *
 * According to our dual licensing model, this program can be used either
 * under the terms of the GNU Affero General Public License, version 3,
 * or under a proprietary license.
 *
 * The texts of the GNU Affero General Public License with an additional
 * permission and of our proprietary license can be found at and
 * in the LICENSE file you have received along with this program.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * "Shopware" is a registered trademark of shopware AG.
 * The licensing of the program under the AGPLv3 does not imply a
 * trademark license. Therefore any rights, title and interest in
 * our trademarks remain entirely with us.
 *
 * @category   Shopware
 * @package    Customer
 * @subpackage Detail
 * @version    $Id$
 * @author shopware AG
 */

//{namespace name=backend/customer/view/detail}

/**
 * Shopware UI - Customer detail page
 *
 * The base field set contains the base data of the customer
 * which is stored in the base model and filled over the s_user table
 *
 */
//{block name="backend/customer/view/detail/base"}
Ext.define('Shopware.apps.Customer.view.detail.Base', {
    /**
     * Define that the base field set is an extension of the Ext.form.FieldSet
     * @string
     */
    extend:'Ext.form.FieldSet',
    /**
     * List of short aliases for class names. Most useful for defining xtypes for widgets.
     * @string
     */
    alias:'widget.customer-base-field-set',

    /**
     * Set css class for this component
     * @string
     */
    cls: Ext.baseCSSPrefix + 'base-field-set',

    /**
     * Layout type for the component.
     * @string
     */
    layout: 'column',

    /**
     * Contains all snippets for the view component
     * @object
     */
    snippets:{
        title:'{s name=base/title}Base data{/s}',
        email:{
            message:'{s name=base/validate_email_message}Email address is already in use{/s}',
            label:'{s name=base/email}Email{/s}'
        },
        confirm:{
            label:'{s name=base/password_confirm}Confirm password{/s}',
            support:'{s name=base/confirm_support}Please confirm the password you have entered{/s}',
            helpTitle:'{s name=base/confirm_help_title}Password confirmation{/s}',
            helpText:'{s name=base/confirm_help_text}For security reasons, please enter the password again.{/s}'
        },
        active:{
            box:'{s name=base/active_box_label}Mark the account as active{/s}',
            field:'{s name=base/active_field_label}Active{/s}'
        },
        password:{
            label:'{s name=base/password}Password{/s}',
            support:'{s name=base/password_support}To automatically create the password, use the button on the right side.{/s}',
            button:'{s name=base/password_generate}Generate password{/s}',
            message:'{s name=base/password_error}The passwords do not match.{/s}'
        },
        group:'{s name=base/customer_group}Customer group{/s}',
        shop:'{s name=base/shop}Shop{/s}',
        number: {
            label:'{s name=base/customer_number}Customer number{/s}',
            helpTitle: '{s name=base/customer_number_help_title}Customer number generation{/s}',
            helpText: '{s name=base/customer_number_help_text}If the parameter sShopwareManagedCustomerNumbers is set to 1 in the shopware configuration, the customer number will be set automatically when the customer is saved and the field is not editable.{/s}'
        }
    },

    /**
     * Component event method which is fired when the component
     * is initials. The component is initials when the user
     * want to create a new customer or edit an existing customer
     * @return void
     */
    initComponent:function () {
        var me = this;
        me.title = me.snippets.title;
        me.registerEvents();

        me.items = me.createBaseForm();

        me.callParent(arguments);
    },

    /**
     * Registers the generatePassword event which is fired when the
     * user clicks on the generatePassword button.
     *
     * @return void
     */
    registerEvents:function () {
        this.addEvents(
            /**
             * Event will be fired when the user clicks the button to
             * generate a new password
             *
             * @event generatePassword
             * @param [object] passwordField - Associated password field
             * @param [object] confirmField - Associated confirm password field
             */
            'generatePassword'
        );
    },

    /**
     * Creates the both containers for the field set
     * to display the form fields in two columns.
     *
     * @return [Array] Contains the left and right container
     */
    createBaseForm:function () {
        var leftContainer, rightContainer, me = this;

        leftContainer = Ext.create('Ext.container.Container', {
            columnWidth:0.5,
            border:false,
            cls: Ext.baseCSSPrefix + 'field-set-container',
            layout:'anchor',
            defaults:{
                anchor:'95%',
                labelWidth:155,
                minWidth:250,
                xtype:'textfield'
            },
            items:me.createBaseFormLeft()
        });

        rightContainer = Ext.create('Ext.container.Container', {
            columnWidth:0.5,
            border:false,
            layout:'anchor',
            cls: Ext.baseCSSPrefix + 'field-set-container',
            defaults:{
                labelWidth:155,
                xtype:'textfield',
                anchor: '95%'
            },
            items: me.createBaseFormRight()
        });

        return [ leftContainer, rightContainer ];
    },

    /**
     * Creates the left container of the base field set.
     * Contains the email field and the combo boxes for
     * the shop and customer group
     *
     * @return [Array] Contains the different form field of the left container
     */
    createBaseFormLeft:function () {
        var me = this;

        me.customerGroupCombo = Ext.create('Ext.form.field.ComboBox', {
            triggerAction:'all',
            queryMode: 'local',
            name:'groupKey',
            fieldLabel:me.snippets.group,
            valueField:'key',
            displayField:'name',
            editable:false,
            allowBlank:false,
            anchor:'95%',
            labelWidth:155,
            minWidth:250
        });

        me.shopStoreCombo = Ext.create('Ext.form.field.ComboBox', {
            triggerAction:'all',
            name:'languageId',
            queryMode: 'local',
            fieldLabel:me.snippets.shop,
            valueField:'id',
            displayField:'name',
            allowBlank:false,
            editable:false,
            anchor:'95%',
            labelWidth:155,
            minWidth:250,
            listeners: {
                // When the selected job changes, validate the mail address again
                change: function(combo, newValue, oldValue, eOpts) {
                    var me = this,
                        fieldSet = me.up('fieldset');

                    fieldSet.customerMail.validationRequestParams = { 'param' : fieldSet.record.get('id'),'subshopId' : newValue};

                    // set oldValue to null in order to force a re-check
                    // else VType 'remote' will just return "oldValid"
                    fieldSet.customerMail.oldValue = null;
                    fieldSet.customerMail.validate();
                }
            }
        });

        me.customerMail = Ext.create('Ext.form.field.Text', {
            fieldLabel:me.snippets.email.label,
            labelWidth:155,
            anchor: '95%',
            name:'email',
            allowBlank:false,
            required:true,
            enableKeyEvents:true,
            checkChangeBuffer:700,
            vtype:'remote',
            validationUrl: null,
            validationRequestParams:{ 'param' : me.record.get('id'),'subshopId' : me.record.get('shopId')},
            validationErrorMsg:me.snippets.email.message,
            listeners: {
                scope: me,
                afterrender: function(field) {
                    window.setTimeout(function() {
                        field.validationUrl = '{url action="validateEmail"}';
                    }, 500);
                }
            }
        });

        return [
            me.customerMail,
            me.customerGroupCombo,
            me.shopStoreCombo,
            {
                /*{if {config name=shopwareManagedCustomerNumbers}==1}*/
                xtype: 'displayfield',
                /*{/if}*/
                name: 'number',
                anchor: '95%',
                labelWidth: 155,
                fieldLabel: me.snippets.number.label,
                helpText: me.snippets.number.helpText,
                helpWidth: 360,
                helpTitle: me.snippets.number.helpTitle
            }
        ];
    },

    /**
     * Creates the right container of the base field set.
     * Contains the active checkbox and the two text fields
     * for the password
     *
     * @return [Array] Contains the three form fields
     */
    createBaseFormRight:function () {
        var me = this,
            pwRequired = false;

        if ( me.record.data.id === 0 ) {
            pwRequired = true;
        }

        //create the confirm password field to get access in the create password
        //button handler to pass the field to the generate password event
        me.confirmField = Ext.create('Ext.form.field.Text', {
            name:'confirm',
            inputType:'password',
            anchor: '95%',
            labelWidth: 155,
            allowBlank:!pwRequired,
            required:pwRequired,
            fieldLabel:me.snippets.confirm.label,
            supportText:me.snippets.confirm.support,
            helpTitle:me.snippets.confirm.helpTitle,
            helpText:me.snippets.confirm.helpText,
            validator:function (value) {
                if ( Ext.String.trim(value) == Ext.String.trim(me.passwordField.getValue()) ) {
                    me.passwordField.clearInvalid();
                    return true;
                } else {
                    return me.snippets.password.message;
                }
            }
        });

        me.passwordContainer = me.createPasswordContainer();

        return [{
            name:'active',
            anchor: '95%',
            boxLabel:me.snippets.active.box,
            fieldLabel:me.snippets.active.field,
            xtype:'checkbox',
            value: true,
            labelWidth:155,
            uncheckedValue:false,
            inputValue:true
        },  me.passwordContainer, me.confirmField ];
    },

    /**
     * Creates the container for the password field and the generatePassword button.
     * @return [Ext.container.Container] - Contains the text field and the button
     */
    createPasswordContainer:function () {
        var me = this,
            pwRequired = false;

        if ( me.record.data.id === 0 ) {
            pwRequired = true;
        }

        //create the password generation button
        me.passwordButton = Ext.create('Ext.button.Button', {
            cls: Ext.baseCSSPrefix + 'password-button',
            iconCls:'sprite-license-key',
            action:'create-password',
            labelWidth: 155,
            tooltip:me.snippets.password.button,
            width: 25,
            /**
             * Add button handler to fire the generatePassword event which is handled
             * in the detail controller. The detail controller generates a password and set it into the password field
             */
            handler:function () {
                me.fireEvent('generatePassword', me.passwordField, me.confirmField);
            }
        });

        //create the password field to get access in the create password
        //button handler to pass the field to the generate password event
        me.passwordField = Ext.create('Ext.form.field.Text', {
            name:'newPassword',
            flex: 1,
            inputType:'password',
            labelWidth: 155,
            allowBlank:!pwRequired,
            required:pwRequired,
            fieldLabel:me.snippets.password.label,
            supportText:me.snippets.password.support,
            cls: Ext.baseCSSPrefix + 'password-field',
            validateOnBlur: true,
            validateOnChange: false,
            validator:function (value) {
                if ( Ext.String.trim(value) == Ext.String.trim(me.confirmField.getValue()) ) {
                    me.confirmField.clearInvalid();
                    return true;
                } else {
                    return me.snippets.password.message;
                }
            }
        });

        //create a container for the password field to append the generate password button
        return Ext.create('Ext.container.Container', {
            layout:'hbox',
            anchor: '95%',
            cls: Ext.baseCSSPrefix + 'password-container',
            height:70,
            items:[ me.passwordField, me.passwordButton ]
        });
    }

});
//{/block}
<commit_msg>SW-17340 - Force shop selection in customer module<commit_after>/**
 * Shopware 5
 * Copyright (c) shopware AG
 *
 * According to our dual licensing model, this program can be used either
 * under the terms of the GNU Affero General Public License, version 3,
 * or under a proprietary license.
 *
 * The texts of the GNU Affero General Public License with an additional
 * permission and of our proprietary license can be found at and
 * in the LICENSE file you have received along with this program.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * "Shopware" is a registered trademark of shopware AG.
 * The licensing of the program under the AGPLv3 does not imply a
 * trademark license. Therefore any rights, title and interest in
 * our trademarks remain entirely with us.
 *
 * @category   Shopware
 * @package    Customer
 * @subpackage Detail
 * @version    $Id$
 * @author shopware AG
 */

//{namespace name=backend/customer/view/detail}

/**
 * Shopware UI - Customer detail page
 *
 * The base field set contains the base data of the customer
 * which is stored in the base model and filled over the s_user table
 *
 */
//{block name="backend/customer/view/detail/base"}
Ext.define('Shopware.apps.Customer.view.detail.Base', {
    /**
     * Define that the base field set is an extension of the Ext.form.FieldSet
     * @string
     */
    extend:'Ext.form.FieldSet',
    /**
     * List of short aliases for class names. Most useful for defining xtypes for widgets.
     * @string
     */
    alias:'widget.customer-base-field-set',

    /**
     * Set css class for this component
     * @string
     */
    cls: Ext.baseCSSPrefix + 'base-field-set',

    /**
     * Layout type for the component.
     * @string
     */
    layout: 'column',

    /**
     * Contains all snippets for the view component
     * @object
     */
    snippets:{
        title:'{s name=base/title}Base data{/s}',
        email:{
            message:'{s name=base/validate_email_message}Email address is already in use{/s}',
            label:'{s name=base/email}Email{/s}'
        },
        confirm:{
            label:'{s name=base/password_confirm}Confirm password{/s}',
            support:'{s name=base/confirm_support}Please confirm the password you have entered{/s}',
            helpTitle:'{s name=base/confirm_help_title}Password confirmation{/s}',
            helpText:'{s name=base/confirm_help_text}For security reasons, please enter the password again.{/s}'
        },
        active:{
            box:'{s name=base/active_box_label}Mark the account as active{/s}',
            field:'{s name=base/active_field_label}Active{/s}'
        },
        password:{
            label:'{s name=base/password}Password{/s}',
            support:'{s name=base/password_support}To automatically create the password, use the button on the right side.{/s}',
            button:'{s name=base/password_generate}Generate password{/s}',
            message:'{s name=base/password_error}The passwords do not match.{/s}'
        },
        group:'{s name=base/customer_group}Customer group{/s}',
        shop:'{s name=base/shop}Shop{/s}',
        number: {
            label:'{s name=base/customer_number}Customer number{/s}',
            helpTitle: '{s name=base/customer_number_help_title}Customer number generation{/s}',
            helpText: '{s name=base/customer_number_help_text}If the parameter sShopwareManagedCustomerNumbers is set to 1 in the shopware configuration, the customer number will be set automatically when the customer is saved and the field is not editable.{/s}'
        }
    },

    /**
     * Component event method which is fired when the component
     * is initials. The component is initials when the user
     * want to create a new customer or edit an existing customer
     * @return void
     */
    initComponent:function () {
        var me = this;
        me.title = me.snippets.title;
        me.registerEvents();

        me.items = me.createBaseForm();

        me.callParent(arguments);
    },

    /**
     * Registers the generatePassword event which is fired when the
     * user clicks on the generatePassword button.
     *
     * @return void
     */
    registerEvents:function () {
        this.addEvents(
            /**
             * Event will be fired when the user clicks the button to
             * generate a new password
             *
             * @event generatePassword
             * @param [object] passwordField - Associated password field
             * @param [object] confirmField - Associated confirm password field
             */
            'generatePassword'
        );
    },

    /**
     * Creates the both containers for the field set
     * to display the form fields in two columns.
     *
     * @return [Array] Contains the left and right container
     */
    createBaseForm:function () {
        var leftContainer, rightContainer, me = this;

        leftContainer = Ext.create('Ext.container.Container', {
            columnWidth:0.5,
            border:false,
            cls: Ext.baseCSSPrefix + 'field-set-container',
            layout:'anchor',
            defaults:{
                anchor:'95%',
                labelWidth:155,
                minWidth:250,
                xtype:'textfield'
            },
            items:me.createBaseFormLeft()
        });

        rightContainer = Ext.create('Ext.container.Container', {
            columnWidth:0.5,
            border:false,
            layout:'anchor',
            cls: Ext.baseCSSPrefix + 'field-set-container',
            defaults:{
                labelWidth:155,
                xtype:'textfield',
                anchor: '95%'
            },
            items: me.createBaseFormRight()
        });

        return [ leftContainer, rightContainer ];
    },

    /**
     * Creates the left container of the base field set.
     * Contains the email field and the combo boxes for
     * the shop and customer group
     *
     * @return [Array] Contains the different form field of the left container
     */
    createBaseFormLeft:function () {
        var me = this;

        me.customerGroupCombo = Ext.create('Ext.form.field.ComboBox', {
            triggerAction:'all',
            queryMode: 'local',
            name:'groupKey',
            fieldLabel:me.snippets.group,
            valueField:'key',
            displayField:'name',
            editable:false,
            allowBlank:false,
            anchor:'95%',
            labelWidth:155,
            minWidth:250
        });

        me.shopStoreCombo = Ext.create('Ext.form.field.ComboBox', {
            triggerAction:'all',
            name:'languageId',
            queryMode: 'local',
            fieldLabel:me.snippets.shop,
            valueField:'id',
            displayField:'name',
            allowBlank:false,
            editable:false,
            anchor:'95%',
            forceSelection: true,
            labelWidth:155,
            minWidth:250,
            listeners: {
                // When the selected job changes, validate the mail address again
                change: function(combo, newValue, oldValue, eOpts) {
                    var me = this,
                        fieldSet = me.up('fieldset');

                    fieldSet.customerMail.validationRequestParams = { 'param' : fieldSet.record.get('id'),'subshopId' : newValue};

                    // set oldValue to null in order to force a re-check
                    // else VType 'remote' will just return "oldValid"
                    fieldSet.customerMail.oldValue = null;
                    fieldSet.customerMail.validate();
                }
            }
        });

        me.customerMail = Ext.create('Ext.form.field.Text', {
            fieldLabel:me.snippets.email.label,
            labelWidth:155,
            anchor: '95%',
            name:'email',
            allowBlank:false,
            required:true,
            enableKeyEvents:true,
            checkChangeBuffer:700,
            vtype:'remote',
            validationUrl: null,
            validationRequestParams:{ 'param' : me.record.get('id'),'subshopId' : me.record.get('shopId')},
            validationErrorMsg:me.snippets.email.message,
            listeners: {
                scope: me,
                afterrender: function(field) {
                    window.setTimeout(function() {
                        field.validationUrl = '{url action="validateEmail"}';
                    }, 500);
                }
            }
        });

        return [
            me.customerMail,
            me.customerGroupCombo,
            me.shopStoreCombo,
            {
                /*{if {config name=shopwareManagedCustomerNumbers}==1}*/
                xtype: 'displayfield',
                /*{/if}*/
                name: 'number',
                anchor: '95%',
                labelWidth: 155,
                fieldLabel: me.snippets.number.label,
                helpText: me.snippets.number.helpText,
                helpWidth: 360,
                helpTitle: me.snippets.number.helpTitle
            }
        ];
    },

    /**
     * Creates the right container of the base field set.
     * Contains the active checkbox and the two text fields
     * for the password
     *
     * @return [Array] Contains the three form fields
     */
    createBaseFormRight:function () {
        var me = this,
            pwRequired = false;

        if ( me.record.data.id === 0 ) {
            pwRequired = true;
        }

        //create the confirm password field to get access in the create password
        //button handler to pass the field to the generate password event
        me.confirmField = Ext.create('Ext.form.field.Text', {
            name:'confirm',
            inputType:'password',
            anchor: '95%',
            labelWidth: 155,
            allowBlank:!pwRequired,
            required:pwRequired,
            fieldLabel:me.snippets.confirm.label,
            supportText:me.snippets.confirm.support,
            helpTitle:me.snippets.confirm.helpTitle,
            helpText:me.snippets.confirm.helpText,
            validator:function (value) {
                if ( Ext.String.trim(value) == Ext.String.trim(me.passwordField.getValue()) ) {
                    me.passwordField.clearInvalid();
                    return true;
                } else {
                    return me.snippets.password.message;
                }
            }
        });

        me.passwordContainer = me.createPasswordContainer();

        return [{
            name:'active',
            anchor: '95%',
            boxLabel:me.snippets.active.box,
            fieldLabel:me.snippets.active.field,
            xtype:'checkbox',
            value: true,
            labelWidth:155,
            uncheckedValue:false,
            inputValue:true
        },  me.passwordContainer, me.confirmField ];
    },

    /**
     * Creates the container for the password field and the generatePassword button.
     * @return [Ext.container.Container] - Contains the text field and the button
     */
    createPasswordContainer:function () {
        var me = this,
            pwRequired = false;

        if ( me.record.data.id === 0 ) {
            pwRequired = true;
        }

        //create the password generation button
        me.passwordButton = Ext.create('Ext.button.Button', {
            cls: Ext.baseCSSPrefix + 'password-button',
            iconCls:'sprite-license-key',
            action:'create-password',
            labelWidth: 155,
            tooltip:me.snippets.password.button,
            width: 25,
            /**
             * Add button handler to fire the generatePassword event which is handled
             * in the detail controller. The detail controller generates a password and set it into the password field
             */
            handler:function () {
                me.fireEvent('generatePassword', me.passwordField, me.confirmField);
            }
        });

        //create the password field to get access in the create password
        //button handler to pass the field to the generate password event
        me.passwordField = Ext.create('Ext.form.field.Text', {
            name:'newPassword',
            flex: 1,
            inputType:'password',
            labelWidth: 155,
            allowBlank:!pwRequired,
            required:pwRequired,
            fieldLabel:me.snippets.password.label,
            supportText:me.snippets.password.support,
            cls: Ext.baseCSSPrefix + 'password-field',
            validateOnBlur: true,
            validateOnChange: false,
            validator:function (value) {
                if ( Ext.String.trim(value) == Ext.String.trim(me.confirmField.getValue()) ) {
                    me.confirmField.clearInvalid();
                    return true;
                } else {
                    return me.snippets.password.message;
                }
            }
        });

        //create a container for the password field to append the generate password button
        return Ext.create('Ext.container.Container', {
            layout:'hbox',
            anchor: '95%',
            cls: Ext.baseCSSPrefix + 'password-container',
            height:70,
            items:[ me.passwordField, me.passwordButton ]
        });
    }

});
//{/block}
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
	Schema = mongoose.Schema;

/**
 * Project Schema
 */
var ProjectSchema = new Schema({
	name : String,
	description : String,
	children : [],
	deleted : Boolean,
	users : [],
	owner : String
});

mongoose.model('Project', ProjectSchema);<commit_msg>Readded user to projects schema<commit_after>'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
	Schema = mongoose.Schema;

/**
 * Project Schema
 */
var ProjectSchema = new Schema({
	name : String,
	description : String,
	children : [],
	deleted : Boolean,
	users : [],
	owner : String,
	user: Object
});

mongoose.model('Project', ProjectSchema);<|endoftext|>


<commit_before>ace.define("ace/mode/sh_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"], function(require, exports, module) {
"use strict";

var oop = require("../lib/oop");
var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;

var reservedKeywords = exports.reservedKeywords = (
        '!|{|}|case|do|done|elif|else|'+
        'esac|fi|for|if|in|then|until|while|'+
        '&|;|export|local|read|typeset|unset|'+
        'elif|select|set|function|declare|readonly'
    );

var languageConstructs = exports.languageConstructs = (
    '[|]|alias|bg|bind|break|builtin|'+
     'cd|command|compgen|complete|continue|'+
     'dirs|disown|echo|enable|eval|exec|'+
     'exit|fc|fg|getopts|hash|help|history|'+
     'jobs|kill|let|logout|popd|printf|pushd|'+
     'pwd|return|set|shift|shopt|source|'+
     'suspend|test|times|trap|type|ulimit|'+
     'umask|unalias|wait'
);

var ShHighlightRules = function() {
    var keywordMapper = this.createKeywordMapper({
        "keyword": reservedKeywords,
        "support.function.builtin": languageConstructs,
        "invalid.deprecated": "debugger"
    }, "identifier");

    var integer = "(?:(?:[1-9]\\d*)|(?:0))";

    var fraction = "(?:\\.\\d+)";
    var intPart = "(?:\\d+)";
    var pointFloat = "(?:(?:" + intPart + "?" + fraction + ")|(?:" + intPart + "\\.))";
    var exponentFloat = "(?:(?:" + pointFloat + "|" +  intPart + ")" + ")";
    var floatNumber = "(?:" + exponentFloat + "|" + pointFloat + ")";
    var fileDescriptor = "(?:&" + intPart + ")";

    var variableName = "[a-zA-Z_][a-zA-Z0-9_]*";
    var variable = "(?:" + variableName + "=)";

    var builtinVariable = "(?:\\$(?:SHLVL|\\$|\\!|\\?))";

    var func = "(?:" + variableName + "\\s*\\(\\))";

    this.$rules = {
        "start" : [{
            token : "constant",
            regex : /\\./
        }, {
            token : ["text", "comment"],
            regex : /(^|\s)(#.*)$/
        }, {
            token : "string",
            regex : '"',
            push : [{
                token : "constant.language.escape",
                regex : /\\(?:[$`"\\]|$)/
            }, {
                include : "variables"
            }, {
                token : "keyword.operator",
                regex : /`/ // TODO highlight `
            }, {
                token : "string",
                regex : '"',
                next: "pop"
            }, {
                defaultToken: "string"
            }]
        }, {
            token : "string",
            regex : "\\$'",
            push : [{
                token : "constant.language.escape",
                regex : /\\(?:[abeEfnrtv\\'"]|x[a-fA-F\d]{1,2}|u[a-fA-F\d]{4}([a-fA-F\d]{4})?|c.|\d{1,3})/
            }, {
                token : "string",
                regex : "'",
                next: "pop"
            }, {
                defaultToken: "string"
            }]
        }, {
            regex : "<<<",
            token : "keyword.operator"
        }, {
            stateName: "heredoc",
            regex : "(<<-?)(\\s*)(['\"`]?)([\\w\\-]+)(['\"`]?)",
            onMatch : function(value, currentState, stack) {
                var next = value[2] == '-' ? "indentedHeredoc" : "heredoc";
                var tokens = value.split(this.splitRegex);
                stack.push(next, tokens[4]);
                return [
                    {type:"constant", value: tokens[1]},
                    {type:"text", value: tokens[2]},
                    {type:"string", value: tokens[3]},
                    {type:"support.class", value: tokens[4]},
                    {type:"string", value: tokens[5]}
                ];
            },
            rules: {
                heredoc: [{
                    onMatch:  function(value, currentState, stack) {
                        if (value === stack[1]) {
                            stack.shift();
                            stack.shift();
                            this.next = stack[0] || "start";
                            return "support.class";
                        }
                        this.next = "";
                        return "string";
                    },
                    regex: ".*$",
                    next: "start"
                }],
                indentedHeredoc: [{
                    token: "string",
                    regex: "^\t+"
                }, {
                    onMatch:  function(value, currentState, stack) {
                        if (value === stack[1]) {
                            stack.shift();
                            stack.shift();
                            this.next = stack[0] || "start";
                            return "support.class";
                        }
                        this.next = "";
                        return "string";
                    },
                    regex: ".*$",
                    next: "start"
                }]
            }
        }, {
            regex : "$",
            token : "empty",
            next : function(currentState, stack) {
                if (stack[0] === "heredoc" || stack[0] === "indentedHeredoc")
                    return stack[0];
                return currentState;
            }
        }, {
            token : ["keyword", "text", "text", "text", "variable"],
            regex : /(declare|local|readonly)(\s+)(?:(-[fixar]+)(\s+))?([a-zA-Z_][a-zA-Z0-9_]*\b)/
        }, {
            token : "variable.language",
            regex : builtinVariable
        }, {
            token : "variable",
            regex : variable
        }, {
            include : "variables"
        }, {
            token : "support.function",
            regex : func
        }, {
            token : "support.function",
            regex : fileDescriptor
        }, {
            token : "string",           // ' string
            start : "'", end : "'"
        }, {
            token : "constant.numeric", // float
            regex : floatNumber
        }, {
            token : "constant.numeric", // integer
            regex : integer + "\\b"
        }, {
            token : keywordMapper,
            regex : "[a-zA-Z_][a-zA-Z0-9_]*\\b"
        }, {
            token : "keyword.operator",
            regex : "\\+|\\-|\\*|\\*\\*|\\/|\\/\\/|~|<|>|<=|=>|=|!=|[%&|`]"
        }, {
            token : "punctuation.operator",
            regex : ";"
        }, {
            token : "paren.lparen",
            regex : "[\\[\\(\\{]"
        }, {
            token : "paren.rparen",
            regex : "[\\]]"
        }, {
            token : "paren.rparen",
            regex : "[\\)\\}]",
            next : "pop"
        }],
        variables: [{
            token : "variable",
            regex : /(\$)(\w+)/
        }, {
            token : ["variable", "paren.lparen"],
            regex : /(\$)(\()/,
            push : "start"
        }, {
            token : ["variable", "paren.lparen", "keyword.operator", "variable", "keyword.operator"],
            regex : /(\$)(\{)([#!]?)(\w+|[*@#?\-$!0_])(:[?+\-=]?|##?|%%?|,,?\/|\^\^?)?/,
            push : "start"
        }, {
            token : "variable",
            regex : /\$[*@#?\-$!0_]/
        }, {
            token : ["variable", "paren.lparen"],
            regex : /(\$)(\{)/,
            push : "start"
        }]
    };

    this.normalizeRules();
};

oop.inherits(ShHighlightRules, TextHighlightRules);

exports.ShHighlightRules = ShHighlightRules;
});

ace.define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"], function(require, exports, module) {
"use strict";

var oop = require("../../lib/oop");
var Range = require("../../range").Range;
var BaseFoldMode = require("./fold_mode").FoldMode;

var FoldMode = exports.FoldMode = function(commentRegex) {
    if (commentRegex) {
        this.foldingStartMarker = new RegExp(
            this.foldingStartMarker.source.replace(/\|[^|]*?$/, "|" + commentRegex.start)
        );
        this.foldingStopMarker = new RegExp(
            this.foldingStopMarker.source.replace(/\|[^|]*?$/, "|" + commentRegex.end)
        );
    }
};
oop.inherits(FoldMode, BaseFoldMode);

(function() {

    this.foldingStartMarker = /(\{|\[)[^\}\]]*$|^\s*(\/\*)/;
    this.foldingStopMarker = /^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/;
    this.singleLineBlockCommentRe= /^\s*(\/\*).*\*\/\s*$/;
    this.tripleStarBlockCommentRe = /^\s*(\/\*\*\*).*\*\/\s*$/;
    this.startRegionRe = /^\s*(\/\*|\/\/)#?region\b/;
    this._getFoldWidgetBase = this.getFoldWidget;
    this.getFoldWidget = function(session, foldStyle, row) {
        var line = session.getLine(row);

        if (this.singleLineBlockCommentRe.test(line)) {
            if (!this.startRegionRe.test(line) && !this.tripleStarBlockCommentRe.test(line))
                return "";
        }

        var fw = this._getFoldWidgetBase(session, foldStyle, row);

        if (!fw && this.startRegionRe.test(line))
            return "start"; // lineCommentRegionStart

        return fw;
    };

    this.getFoldWidgetRange = function(session, foldStyle, row, forceMultiline) {
        var line = session.getLine(row);

        if (this.startRegionRe.test(line))
            return this.getCommentRegionBlock(session, line, row);

        var match = line.match(this.foldingStartMarker);
        if (match) {
            var i = match.index;

            if (match[1])
                return this.openingBracketBlock(session, match[1], row, i);

            var range = session.getCommentFoldRange(row, i + match[0].length, 1);

            if (range && !range.isMultiLine()) {
                if (forceMultiline) {
                    range = this.getSectionRange(session, row);
                } else if (foldStyle != "all")
                    range = null;
            }

            return range;
        }

        if (foldStyle === "markbegin")
            return;

        var match = line.match(this.foldingStopMarker);
        if (match) {
            var i = match.index + match[0].length;

            if (match[1])
                return this.closingBracketBlock(session, match[1], row, i);

            return session.getCommentFoldRange(row, i, -1);
        }
    };

    this.getSectionRange = function(session, row) {
        var line = session.getLine(row);
        var startIndent = line.search(/\S/);
        var startRow = row;
        var startColumn = line.length;
        row = row + 1;
        var endRow = row;
        var maxRow = session.getLength();
        while (++row < maxRow) {
            line = session.getLine(row);
            var indent = line.search(/\S/);
            if (indent === -1)
                continue;
            if  (startIndent > indent)
                break;
            var subRange = this.getFoldWidgetRange(session, "all", row);

            if (subRange) {
                if (subRange.start.row <= startRow) {
                    break;
                } else if (subRange.isMultiLine()) {
                    row = subRange.end.row;
                } else if (startIndent == indent) {
                    break;
                }
            }
            endRow = row;
        }

        return new Range(startRow, startColumn, endRow, session.getLine(endRow).length);
    };
    this.getCommentRegionBlock = function(session, line, row) {
        var startColumn = line.search(/\s*$/);
        var maxRow = session.getLength();
        var startRow = row;

        var re = /^\s*(?:\/\*|\/\/|--)#?(end)?region\b/;
        var depth = 1;
        while (++row < maxRow) {
            line = session.getLine(row);
            var m = re.exec(line);
            if (!m) continue;
            if (m[1]) depth--;
            else depth++;

            if (!depth) break;
        }

        var endRow = row;
        if (endRow > startRow) {
            return new Range(startRow, startColumn, endRow, line.length);
        }
    };

}).call(FoldMode.prototype);

});

ace.define("ace/mode/behaviour/cstyle",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/token_iterator","ace/lib/lang"], function(require, exports, module) {
"use strict";

var oop = require("../../lib/oop");
var Behaviour = require("../behaviour").Behaviour;
var TokenIterator = require("../../token_iterator").TokenIterator;
var lang = require("../../lib/lang");

var SAFE_INSERT_IN_TOKENS =
    ["text", "paren.rparen", "punctuation.operator"];
var SAFE_INSERT_BEFORE_TOKENS =
    ["text", "paren.rparen", "punctuation.operator", "comment"];

var context;
var contextCache = {};
var initContext = function(editor) {
    var id = -1;
    if (editor.multiSelect) {
        id = editor.selection.index;
        if (contextCache.rangeCount != editor.multiSelect.rangeCount)
            contextCache = {rangeCount: editor.multiSelect.rangeCount};
    }
    if (contextCache[id])
        return context = contextCache[id];
    context = contextCache[id] = {
        autoInsertedBrackets: 0,
        autoInsertedRow: -1,
        autoInsertedLineEnd: "",
        maybeInsertedBrackets: 0,
        maybeInsertedRow: -1,
        maybeInsertedLineStart: "",
        maybeInsertedLineEnd: ""
    };
};

var getWrapped = function(selection, selected, opening, closing) {
    var rowDiff = selection.end.row - selection.start.row;
    return {
        text: opening + selected + closing,
        selection: [
                0,
                selection.start.column + 1,
                rowDiff,
                selection.end.column + (rowDiff ? 0 : 1)
            ]
    };
};

var CstyleBehaviour = function() {
    this.add("braces", "insertion", function(state, action, editor, session, text) {
        var cursor = editor.getCursorPosition();
        var line = session.doc.getLine(cursor.row);
        if (text == '{') {
            initContext(editor);
            var selection = editor.getSelectionRange();
            var selected = session.doc.getTextRange(selection);
            if (selected !== "" && selected !== "{" && editor.getWrapBehavioursEnabled()) {
                return getWrapped(selection, selected, '{', '}');
            } else if (CstyleBehaviour.isSaneInsertion(editor, session)) {
                if (/[\]\}\)]/.test(line[cursor.column]) || editor.inMultiSelectMode) {
                    CstyleBehaviour.recordAutoInsert(editor, session, "}");
                    return {
                        text: '{}',
                        selection: [1, 1]
                    };
                } else {
                    CstyleBehaviour.recordMaybeInsert(editor, session, "{");
                    return {
                        text: '{',
                        selection: [1, 1]
                    };
                }
            }
        } else if (text == '}') {
            initContext(editor);
            var rightChar = line.substring(cursor.column, cursor.column + 1);
            if (rightChar == '}') {
                var matching = session.$findOpeningBracket('}', {column: cursor.column + 1, row: cursor.row});
                if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) {
                    CstyleBehaviour.popAutoInsertedClosing();
                    return {
                        text: '',
                        selection: [1, 1]
                    };
                }
            }
        } else if (text == "\n" || text == "\r\n") {
            initContext(editor);
            var closing = "";
            if (CstyleBehaviour.isMaybeInsertedClosing(cursor, line)) {
                closing = lang.stringRepeat("}", context.maybeInsertedBrackets);
                CstyleBehaviour.clearMaybeInsertedClosing();
            }
            var rightChar = line.substring(cursor.column, cursor.column + 1);
            if (rightChar === '}') {
                var openBracePos = session.findMatchingBracket({row: cursor.row, column: cursor.column+1}, '}');
                if (!openBracePos)
                     return null;
                var next_indent = this.$getIndent(session.getLine(openBracePos.row));
            } else if (closing) {
                var next_indent = this.$getIndent(line);
            } else {
                CstyleBehaviour.clearMaybeInsertedClosing();
                return;
            }
            var indent = next_indent + session.getTabString();

            return {
                text: '\n' + indent + '\n' + next_indent + closing,
                selection: [1, indent.length, 1, indent.length]
            };
        } else {
            CstyleBehaviour.clearMaybeInsertedClosing();
        }
    });

    this.add("braces", "deletion", function(state, action, editor, session, range) {
        var selected = session.doc.getTextRange(range);
        if (!range.isMultiLine() && selected == '{') {
            initContext(editor);
            var line = session.doc.getLine(range.start.row);
            var rightChar = line.substring(range.end.column, range.end.column + 1);
            if (rightChar == '}') {
                range.end.column++;
                return range;
            } else {
                context.maybeInsertedBrackets--;
            }
        }
    });

    this.add("parens", "insertion", function(state, action, editor, session, text) {
        if (text == '(') {
            initContext(editor);
            var selection = editor.getSelectionRange();
            var selected = session.doc.getTextRange(selection);
            if (selected !== "" && editor.getWrapBehavioursEnabled()) {
                return getWrapped(selection, selected, '(', ')');
            } else if (CstyleBehaviour.isSaneInsertion(editor, session)) {
                CstyleBehaviour.recordAutoInsert(editor, session, ")");
                return {
                    text: '()',
                    selection: [1, 1]
                };
            }
        } else if (text == ')') {
            initContext(editor);
            var cursor = editor.getCursorPosition();
            var line = session.doc.getLine(cursor.row);
            var rightChar = line.substring(cursor.column, cursor.column + 1);
            if (rightChar == ')') {
                var matching = session.$findOpeningBracket(')', {column: cursor.column + 1, row: cursor.row});
                if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) {
                    CstyleBehaviour.popAutoInsertedClosing();
                    return {
                        text: '',
                        selection: [1, 1]
                    };
                }
            }
        }
    });

    this.add("parens", "deletion", function(state, action, editor, session, range) {
        var selected = session.doc.getTextRange(range);
        if (!range.isMultiLine() && selected == '(') {
            initContext(editor);
            var line = session.doc.getLine(range.start.row);
            var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
            if (rightChar == ')') {
                range.end.column++;
                return range;
            }
        }
    });

    this.add("brackets", "insertion", function(state, action, editor, session, text) {
        if (text == '[') {
            initContext(editor);
            var selection = editor.getSelectionRange();
            var selected = session.doc.getTextRange(selection);
            if (selected !== "" && editor.getWrapBehavioursEnabled()) {
                return getWrapped(selection, selected, '[', ']');
            } else if (CstyleBehaviour.isSaneInsertion(editor, session)) {
                CstyleBehaviour.recordAutoInsert(editor, session, "]");
                return {
                    text: '[]',
                    selection: [1, 1]
                };
            }
        } else if (text == ']') {
            initContext(editor);
            var cursor = editor.getCursorPosition();
            var line = session.doc.getLine(cursor.row);
            var rightChar = line.substring(cursor.column, cursor.column + 1);
            if (rightChar == ']') {
                var matching = session.$findOpeningBracket(']', {column: cursor.column + 1, row: cursor.row});
                if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) {
                    CstyleBehaviour.popAutoInsertedClosing();
                    return {
                        text: '',
                        selection: [1, 1]
                    };
                }
            }
        }
    });

    this.add("brackets", "deletion", function(state, action, editor, session, range) {
        var selected = session.doc.getTextRange(range);
        if (!range.isMultiLine() && selected == '[') {
            initContext(editor);
            var line = session.doc.getLine(range.start.row);
            var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
            if (rightChar == ']') {
                range.end.column++;
                return range;
            }
        }
    });

    this.add("string_dquotes", "insertion", function(state, action, editor, session, text) {
        if (text == '"' || text == "'") {
            initContext(editor);
            var quote = text;
            var selection = editor.getSelectionRange();
            var selected = session.doc.getTextRange(selection);
            if (selected !== "" && selected !== "'" && selected != '"' && editor.getWrapBehavioursEnabled()) {
                return getWrapped(selection, selected, quote, quote);
            } else if (!selected) {
                var cursor = editor.getCursorPosition();
                var line = session.doc.getLine(cursor.row);
                var leftChar = line.substring(cursor.column-1, cursor.column);
                var rightChar = line.substring(cursor.column, cursor.column + 1);

                var token = session.getTokenAt(cursor.row, cursor.column);
                var rightToken = session.getTokenAt(cursor.row, cursor.column + 1);
                if (leftChar == "\\" && token && /escape/.test(token.type))
                    return null;

                var stringBefore = token && /string|escape/.test(token.type);
                var stringAfter = !rightToken || /string|escape/.test(rightToken.type);

                var pair;
                if (rightChar == quote) {
                    pair = stringBefore !== stringAfter;
                } else {
                    if (stringBefore && !stringAfter)
                        return null; // wrap string with different quote
                    if (stringBefore && stringAfter)
                        return null; // do not pair quotes inside strings
                    var wordRe = session.$mode.tokenRe;
                    wordRe.lastIndex = 0;
                    var isWordBefore = wordRe.test(leftChar);
                    wordRe.lastIndex = 0;
                    var isWordAfter = wordRe.test(leftChar);
                    if (isWordBefore || isWordAfter)
                        return null; // before or after alphanumeric
                    if (rightChar && !/[\s;,.})\]\\]/.test(rightChar))
                        return null; // there is rightChar and it isn't closing
                    pair = true;
                }
                return {
                    text: pair ? quote + quote : "",
                    selection: [1,1]
                };
            }
        }
    });

    this.add("string_dquotes", "deletion", function(state, action, editor, session, range) {
        var selected = session.doc.getTextRange(range);
        if (!range.isMultiLine() && (selected == '"' || selected == "'")) {
            initContext(editor);
            var line = session.doc.getLine(range.start.row);
            var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
            if (rightChar == selected) {
                range.end.column++;
                return range;
            }
        }
    });

};

CstyleBehaviour.isSaneInsertion = function(editor, session) {
    var cursor = editor.getCursorPosition();
    var iterator = new TokenIterator(session, cursor.row, cursor.column);
    if (!this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS)) {
        var iterator2 = new TokenIterator(session, cursor.row, cursor.column + 1);
        if (!this.$matchTokenType(iterator2.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS))
            return false;
    }
    iterator.stepForward();
    return iterator.getCurrentTokenRow() !== cursor.row ||
        this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_BEFORE_TOKENS);
};

CstyleBehaviour.$matchTokenType = function(token, types) {
    return types.indexOf(token.type || token) > -1;
};

CstyleBehaviour.recordAutoInsert = function(editor, session, bracket) {
    var cursor = editor.getCursorPosition();
    var line = session.doc.getLine(cursor.row);
    if (!this.isAutoInsertedClosing(cursor, line, context.autoInsertedLineEnd[0]))
        context.autoInsertedBrackets = 0;
    context.autoInsertedRow = cursor.row;
    context.autoInsertedLineEnd = bracket + line.substr(cursor.column);
    context.autoInsertedBrackets++;
};

CstyleBehaviour.recordMaybeInsert = function(editor, session, bracket) {
    var cursor = editor.getCursorPosition();
    var line = session.doc.getLine(cursor.row);
    if (!this.isMaybeInsertedClosing(cursor, line))
        context.maybeInsertedBrackets = 0;
    context.maybeInsertedRow = cursor.row;
    context.maybeInsertedLineStart = line.substr(0, cursor.column) + bracket;
    context.maybeInsertedLineEnd = line.substr(cursor.column);
    context.maybeInsertedBrackets++;
};

CstyleBehaviour.isAutoInsertedClosing = function(cursor, line, bracket) {
    return context.autoInsertedBrackets > 0 &&
        cursor.row === context.autoInsertedRow &&
        bracket === context.autoInsertedLineEnd[0] &&
        line.substr(cursor.column) === context.autoInsertedLineEnd;
};

CstyleBehaviour.isMaybeInsertedClosing = function(cursor, line) {
    return context.maybeInsertedBrackets > 0 &&
        cursor.row === context.maybeInsertedRow &&
        line.substr(cursor.column) === context.maybeInsertedLineEnd &&
        line.substr(0, cursor.column) == context.maybeInsertedLineStart;
};

CstyleBehaviour.popAutoInsertedClosing = function() {
    context.autoInsertedLineEnd = context.autoInsertedLineEnd.substr(1);
    context.autoInsertedBrackets--;
};

CstyleBehaviour.clearMaybeInsertedClosing = function() {
    if (context) {
        context.maybeInsertedBrackets = 0;
        context.maybeInsertedRow = -1;
    }
};

oop.inherits(CstyleBehaviour, Behaviour);

exports.CstyleBehaviour = CstyleBehaviour;
});

ace.define("ace/mode/sh",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/sh_highlight_rules","ace/range","ace/mode/folding/cstyle","ace/mode/behaviour/cstyle"], function(require, exports, module) {
"use strict";

var oop = require("../lib/oop");
var TextMode = require("./text").Mode;
var ShHighlightRules = require("./sh_highlight_rules").ShHighlightRules;
var Range = require("../range").Range;
var CStyleFoldMode = require("./folding/cstyle").FoldMode;
var CstyleBehaviour = require("./behaviour/cstyle").CstyleBehaviour;

var Mode = function() {
    this.HighlightRules = ShHighlightRules;
    this.foldingRules = new CStyleFoldMode();
    this.$behaviour = new CstyleBehaviour();
};
oop.inherits(Mode, TextMode);

(function() {

    this.lineCommentStart = "#";

    this.getNextLineIndent = function(state, line, tab) {
        var indent = this.$getIndent(line);

        var tokenizedLine = this.getTokenizer().getLineTokens(line, state);
        var tokens = tokenizedLine.tokens;

        if (tokens.length && tokens[tokens.length-1].type == "comment") {
            return indent;
        }

        if (state == "start") {
            var match = line.match(/^.*[\{\(\[\:]\s*$/);
            if (match) {
                indent += tab;
            }
        }

        return indent;
    };

    var outdents = {
        "pass": 1,
        "return": 1,
        "raise": 1,
        "break": 1,
        "continue": 1
    };

    this.checkOutdent = function(state, line, input) {
        if (input !== "\r\n" && input !== "\r" && input !== "\n")
            return false;

        var tokens = this.getTokenizer().getLineTokens(line.trim(), state).tokens;

        if (!tokens)
            return false;
        do {
            var last = tokens.pop();
        } while (last && (last.type == "comment" || (last.type == "text" && last.value.match(/^\s+$/))));

        if (!last)
            return false;

        return (last.type == "keyword" && outdents[last.value]);
    };

    this.autoOutdent = function(state, doc, row) {

        row += 1;
        var indent = this.$getIndent(doc.getLine(row));
        var tab = doc.getTabString();
        if (indent.slice(-tab.length) == tab)
            doc.remove(new Range(row, indent.length-tab.length, row, indent.length));
    };

    this.$id = "ace/mode/sh";
}).call(Mode.prototype);

exports.Mode = Mode;
});
<commit_msg>Update tmp/bower_components/ace-builds/src-noconflict/mode-sh.js<commit_after><|endoftext|>