From e0bfae6f4e74a87c5c48054fc371631680c45857 Mon Sep 17 00:00:00 2001 From: Gabriel Csapo Date: Tue, 5 Mar 2019 21:10:15 -0800 Subject: [PATCH] [feature] adds the ability to introspect on the header for the authenticate function (#52) --- CHANGELOG.md | 5 + docs/code/Git.html | 81 +- docs/code/Service.html | 6 +- docs/code/git.js.html | 20 +- docs/code/index.html | 75 +- docs/code/module-lib_util.html | 6 +- docs/code/service.js.html | 65 +- docs/code/util.js.html | 11 +- docs/index.html | 4225 +------------------------------- example/index.js | 4 +- lib/git.js | 13 +- package.json | 2 +- test/git.js | 76 +- 13 files changed, 336 insertions(+), 4253 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92624bc..e19b933 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.6.0 (03/03/2019) + +- Augments the authenticate function declaration to accept an object as the first argument and a callback for the second. This allows us to make changes without having to cause breaking changes. + - Adds the ability to introspect on the header (fixes #49) + # 0.5.1 (03/03/2019) - bump dependencies diff --git a/docs/code/Git.html b/docs/code/Git.html index 3e058ee..ec8f958 100644 --- a/docs/code/Git.html +++ b/docs/code/Git.html @@ -271,13 +271,13 @@
Properties
-

a function that has the following arguments (repo, username, password, next) and will be called when a request comes through if set

-
 authenticate: (type, repo, username, password, next) => {
+            

a function that has the following arguments ({ type, repo, username, password, headers }, next) and will be called when a request comes through if set

+
 authenticate: ({ type, repo, username, password, headers }, next) => {
    console.log(type, repo, username, password);
    next();
  }
  // alternatively you can also pass authenticate a promise
- authenticate: (type, repo, username, password, next) => {
+ authenticate: ({ type, repo, username, password, headers }, next) => {
    console.log(type, repo, username, password);
    return new Promise((resolve, reject) => {
     if(username === 'foo') {
@@ -393,7 +393,7 @@ 

(static) close<
Source:
@@ -444,6 +444,51 @@

(static) close< + + +

Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDescription
+ + +Promise + + + +

will resolve or reject when the server closes or fails to close.

@@ -1106,7 +1151,7 @@
Parameters:
-

(static) listen(port, optionsopt, callback)

+

(static) listen(port, optionsopt, callback) → {Git}

@@ -1118,7 +1163,7 @@

(static) liste
Source:
@@ -1435,6 +1480,30 @@

Properties
+
Returns:
+ + +
+
    +
  • the Git instance, useful for chaining
  • +
+
+ + + +
+
+ Type +
+
+ +Git + + +
+
+ + diff --git a/docs/code/Service.html b/docs/code/Service.html index 2c2603e..4a74a7b 100644 --- a/docs/code/Service.html +++ b/docs/code/Service.html @@ -65,7 +65,7 @@

new ServiceSource:
@@ -1253,7 +1253,7 @@

(static) accep
Source:
@@ -1337,7 +1337,7 @@

(static) rejec
Source:
diff --git a/docs/code/git.js.html b/docs/code/git.js.html index 8435a5f..2229a3a 100644 --- a/docs/code/git.js.html +++ b/docs/code/git.js.html @@ -157,14 +157,14 @@

git.js

* @param {Object} options - options that can be applied on the new instance being created * @param {Boolean=} options.autoCreate - By default, repository targets will be created if they don't exist. You can disable that behavior with `options.autoCreate = true` - * @param {Function} options.authenticate - a function that has the following arguments (repo, username, password, next) and will be called when a request comes through if set + * @param {Function} options.authenticate - a function that has the following arguments ({ type, repo, username, password, headers }, next) and will be called when a request comes through if set * - authenticate: (type, repo, username, password, next) => { + authenticate: ({ type, repo, username, password, headers }, next) => { console.log(type, repo, username, password); next(); } // alternatively you can also pass authenticate a promise - authenticate: (type, repo, username, password, next) => { + authenticate: ({ type, repo, username, password, headers }, next) => { console.log(type, repo, username, password); return new Promise((resolve, reject) => { if(username === 'foo') { @@ -341,9 +341,12 @@

git.js

// check if the repo is authenticated if(this.authenticate) { const type = this.getType(service); - const promise = this.authenticate(type, repoName, basicAuth.bind(null, req, res), (error) => { + const headers = req.headers; + const user = basicAuth.bind(null, req, res); + const promise = this.authenticate({ type, repo: repoName, user, headers }, (error) => { return next(error); }); + if(promise instanceof Promise) { return promise .then(next) @@ -472,6 +475,7 @@

git.js

* @param {Buffer|String=} options.key - the key file for the https server * @param {Buffer|String=} options.cert - the cert file for the https server * @param {Function} callback - the function to call when server is started or error has occured + * @return {Git} - the Git instance, useful for chaining */ listen(port, options, callback) { const self = this; @@ -486,14 +490,20 @@

git.js

}); this.server.listen(port, callback); + return this; } /** * closes the server instance * @method close * @memberof Git + * @param {Promise} - will resolve or reject when the server closes or fails to close. */ close() { - this.server.close(); + return new Promise((resolve, reject) => { + this.server.close((err) => { + err ? reject(err) : resolve(); + }); + }); } } diff --git a/docs/code/index.html b/docs/code/index.html index d1181b8..06af68b 100644 --- a/docs/code/index.html +++ b/docs/code/index.html @@ -48,6 +48,8 @@

Home

Classes

  • node-git-server

    🎡 A configurable git server written in Node.js

    +
    +

    there be 🐲 here! The API's and functionality are still be cemented, anything before a 1.0.0 release will be subject to change.

    @@ -59,7 +61,76 @@

    Home

    Classes

    • devDependency Status npm npm

      -

      Install

      npm install node-git-server

      Usage

      const path = require('path');
      +

      Install

      npm install node-git-server

      Usage

      Simple

      const path = require('path');
      +const Server = require('node-git-server');
      +
      +const repos = new Server(path.resolve(__dirname, 'tmp'), {
      +    autoCreate: true
      +});
      +const port = process.env.PORT || 7005;
      +
      +repos.on('push', (push) => {
      +    console.log(`push ${push.repo}/${push.commit} (${push.branch})`);
      +    push.accept();
      +});
      +
      +repos.on('fetch', (fetch) => {
      +    console.log(`fetch ${fetch.commit}`);
      +    fetch.accept();
      +});
      +
      +repos.listen(port, () => {
      +    console.log(`node-git-server running at http://localhost:${port}`)
      +});

      then start up the node-git-server server...

      +
      $ node example/index.js

      meanwhile...

      +
      $ git push http://localhost:7005/beep master
      +Counting objects: 356, done.
      +Delta compression using up to 2 threads.
      +Compressing objects: 100% (133/133), done.
      +Writing objects: 100% (356/356), 46.20 KiB, done.
      +Total 356 (delta 210), reused 355 (delta 210)
      +To http://localhost:7005/beep
      + * [new branch]      master -> master

      Sending logs

      const path = require('path');
      +const Server = require('node-git-server');
      +
      +const repos = new Server(path.resolve(__dirname, 'tmp'), {
      +    autoCreate: true
      +});
      +const port = process.env.PORT || 7005;
      +
      +repos.on('push', (push) => {
      +    console.log(`push ${push.repo}/${push.commit} (${push.branch})`);
      +
      +    repos.list((err, results) => {
      +        push.log(' ');
      +        push.log('Hey!');
      +        push.log('Checkout these other repos:');
      +        for(const repo of results) {
      +          push.log(`- ${repo}`);
      +        }
      +        push.log(' ');
      +    });
      +
      +    push.accept();
      +});
      +
      +repos.listen(port, () => {
      +    console.log(`node-git-server running at http://localhost:${port}`)
      +});

      then start up the node-git-server server...

      +
      $ node example/index.js

      meanwhile...

      +
      $ git push http://localhost:7005/beep master
      +Counting objects: 356, done.
      +Delta compression using up to 2 threads.
      +Compressing objects: 100% (133/133), done.
      +Writing objects: 100% (356/356), 46.20 KiB, done.
      +Total 356 (delta 210), reused 355 (delta 210)
      +remote:  
      +remote: Hey!
      +remote: Checkout these other repos:
      +remote: - test.git
      +remote:  
      +To http://localhost:7005/test
      +   77bb26e..22918d5  master -> master

      Authentication

      const path = require('path');
       const Server = require('node-git-server');
       
       const repos = new Server(path.resolve(__dirname, 'tmp'), {
      @@ -98,7 +169,7 @@ 

      Install

      npm install node-git-serve
       Writing objects: 100% (356/356), 46.20 KiB, done.
       Total 356 (delta 210), reused 355 (delta 210)
       To http://localhost:7005/beep
      - * [new branch]      master -> master

      Example

      Running the following command will start up a simple http server:

      + * [new branch] master -> master

      Example

      Running the following command will start up a simple http server:

      node example/index.js

      If you want to try using https run the following

      node example/index.js --https

      When running https with self-signed certs there are two ways to override the git-clients behavior using git config http.sslVerify false or git config --global http.sslCAInfo /path/to/cert.pem

      diff --git a/docs/code/module-lib_util.html b/docs/code/module-lib_util.html index 19d87f7..332fccc 100644 --- a/docs/code/module-lib_util.html +++ b/docs/code/module-lib_util.html @@ -263,7 +263,7 @@

      (inner)
      Source:
      @@ -460,7 +460,7 @@

      (inner)
      Source:
      @@ -974,7 +974,7 @@

      (inner)
      Source:
      diff --git a/docs/code/service.js.html b/docs/code/service.js.html index f6073fd..c53319c 100644 --- a/docs/code/service.js.html +++ b/docs/code/service.js.html @@ -38,16 +38,23 @@

      service.js

      const through = require('through');
      -const HttpDuplex = require('./http-duplex');
       const zlib = require('zlib');
      -
      +const util = require('util');
      +const os = require('os');
       const { spawn } = require('child_process');
       
      +const HttpDuplex = require('./http-duplex');
      +
       const headerRE = {
         'receive-pack': '([0-9a-fA-F]+) ([0-9a-fA-F]+) refs\/(heads|tags)\/(.*?)( |00|\u0000)|^(0000)$', // eslint-disable-line
         'upload-pack': '^\\S+ ([0-9a-fA-F]+)'
       };
       
      +const packSideband = s => {
      +  const n = (4 + s.length).toString(16);
      +  return Array(4 - n.length + 1).join('0') + n + s;
      +};
      +
       class Service extends HttpDuplex {
         /**
          * Handles invoking the git-*-pack binaries
      @@ -67,6 +74,7 @@ 

      service.js

      this.repo = opts.repo; this.service = opts.service; this.cwd = opts.cwd; + this.logs = []; var buffered = through().pause(); @@ -89,7 +97,7 @@

      service.js

      if(req.headers["authorization"]) { const tokens = req.headers["authorization"].split(" "); if (tokens[0] === "Basic") { - const splitHash = new Buffer(tokens[1], 'base64').toString('utf8').split(":"); + const splitHash = Buffer.from(tokens[1], 'base64').toString('utf8').split(":"); this.username = splitHash.shift(); } } @@ -135,35 +143,63 @@

      service.js

      self.once('accept', function onAccept() { process.nextTick(function() { - var cmd = ['git-' + opts.service, '--stateless-rpc', opts.cwd]; - var ps = spawn(cmd[0], cmd.slice(1)); + const cmd = os.platform() == 'win32' ? + ['git', opts.service, '--stateless-rpc', opts.cwd] + : + ['git-' + opts.service, '--stateless-rpc', opts.cwd]; + + const ps = spawn(cmd[0], cmd.slice(1)); + ps.on('error', function(err) { self.emit('error', new Error(`${err.message} running command ${cmd.join(' ')}`)); }); self.emit('service', ps); - var respStream = through(function(c) { - if (self.listeners('response').length === 0) return this.queue(c); + var respStream = through(function write(c) { + if (self.listeners('response').length === 0) { + if(self.logs.length > 0) { + while(self.logs.length > 0) { + this.queue(self.logs.pop()); + } + } + + return this.queue(c); + } // prevent git from sending the close signal if (c.length === 4 && c.toString() === '0000') return; this.queue(c); - }, function() { + }, function end() { if (self.listeners('response').length > 0) return; + this.queue(null); }); + respStream.log = function() { + self.log(...arguments); + }; self.emit('response', respStream, function endResponse() { - res.queue(new Buffer('0000')); + res.queue(Buffer.from('0000')); res.queue(null); }); + ps.stdout.pipe(respStream).pipe(res); buffered.pipe(ps.stdin); buffered.resume(); - ps.on('exit', self.emit.bind(self, 'exit')); + ps.on('exit', () => { + if(self.logs.length > 0) { + while(self.logs.length > 0) { + respStream.queue(self.logs.pop()); + } + respStream.queue(Buffer.from('0000')); + respStream.queue(null); + } + + self.emit.bind(self, 'exit'); + }); }); }); @@ -172,6 +208,15 @@

      service.js

      res.end(msg); }); } + + log() { + const _log = util.format(...arguments); + const SIDEBAND = String.fromCharCode(2); // PROGRESS + const message = `${SIDEBAND}${_log}\n`; + const formattedMessage = Buffer.from(packSideband(message)); + + this.logs.unshift(formattedMessage); + } /** * reject request in flight * @method reject diff --git a/docs/code/util.js.html b/docs/code/util.js.html index 458281a..c874c4f 100644 --- a/docs/code/util.js.html +++ b/docs/code/util.js.html @@ -73,7 +73,7 @@

      util.js

      } else { const tokens = req.headers["authorization"].split(" "); if (tokens[0] === "Basic") { - const splitHash = new Buffer(tokens[1], 'base64').toString('utf8').split(":"); + const splitHash = Buffer.from(tokens[1], 'base64').toString('utf8').split(":"); const username = splitHash.shift(); const password = splitHash.join(":"); callback(username, password, null); @@ -123,7 +123,14 @@

      util.js

      res.write(pack('# service=git-' + service + '\n')); res.write('0000'); - const cmd = ['git-' + service, '--stateless-rpc', '--advertise-refs', repoLocation]; + var cmd = null; + var isWin = /^win/.test(process.platform); + if (isWin) { + cmd = ['git', service, '--stateless-rpc', '--advertise-refs', repoLocation]; + } else { + cmd = ['git-' + service, '--stateless-rpc', '--advertise-refs', repoLocation]; + } + const ps = spawn(cmd[0], cmd.slice(1)); ps.on('error', (err) => { dup.emit('error', new Error(`${err.message} running command ${cmd.join(' ')}`)); diff --git a/docs/index.html b/docs/index.html index be82179..23abc03 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,11 +1,4 @@ - - - - - - - - - -
      -
      - +Object.defineProperty(t,"__esModule",{value:!0});var n=null,r=!1,o=3,i=-1,a=-1,l=!1,u=!1;function c(){if(!l){var e=n.expirationTime;u?_():u=!0,w(f,e)}}function s(){var e=n,t=n.next;if(n===t)n=null;else{var r=n.previous;n=r.next=t,t.previous=r}e.next=e.previous=null,r=e.callback,t=e.expirationTime,e=e.priorityLevel;var i=o,l=a;o=e,a=t;try{var u=r()}finally{o=i,a=l}if("function"==typeof u)if(u={callback:u,priorityLevel:e,expirationTime:t,next:null,previous:null},null===n)n=u.next=u.previous=u;else{r=null,e=n;do{if(e.expirationTime>=t){r=e;break}e=e.next}while(e!==n);null===r?r=n:r===n&&(n=u,c()),(t=r.previous).next=r.previous=u,u.next=r,u.previous=t}}function d(){if(-1===i&&null!==n&&1===n.priorityLevel){l=!0;try{do{s()}while(null!==n&&1===n.priorityLevel)}finally{l=!1,null!==n?c():u=!1}}}function f(e){l=!0;var o=r;r=e;try{if(e)for(;null!==n;){var i=t.unstable_now();if(!(n.expirationTime<=i))break;do{s()}while(null!==n&&n.expirationTime<=i)}else if(null!==n)do{s()}while(null!==n&&!T())}finally{l=!1,r=o,null!==n?c():u=!1,d()}}var p,h,g=Date,m="function"==typeof setTimeout?setTimeout:void 0,b="function"==typeof clearTimeout?clearTimeout:void 0,v="function"==typeof requestAnimationFrame?requestAnimationFrame:void 0,y="function"==typeof cancelAnimationFrame?cancelAnimationFrame:void 0;function x(e){p=v(function(t){b(h),e(t)}),h=m(function(){y(p),e(t.unstable_now())},100)}if("object"==typeof performance&&"function"==typeof performance.now){var k=performance;t.unstable_now=function(){return k.now()}}else t.unstable_now=function(){return g.now()};var w,_,T,S=null;if("undefined"!=typeof window?S=window:void 0!==e&&(S=e),S&&S._schedMock){var E=S._schedMock;w=E[0],_=E[1],T=E[2],t.unstable_now=E[3]}else if("undefined"==typeof window||"function"!=typeof MessageChannel){var C=null,P=function(e){if(null!==C)try{C(e)}finally{C=null}};w=function(e){null!==C?setTimeout(w,0,e):(C=e,setTimeout(P,0,!1))},_=function(){C=null},T=function(){return!1}}else{"undefined"!=typeof console&&("function"!=typeof v&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills"),"function"!=typeof y&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills"));var N=null,z=!1,R=-1,O=!1,M=!1,I=0,L=33,U=33;T=function(){return I<=t.unstable_now()};var A=new MessageChannel,D=A.port2;A.port1.onmessage=function(){z=!1;var e=N,n=R;N=null,R=-1;var r=t.unstable_now(),o=!1;if(0>=I-r){if(!(-1!==n&&n<=r))return O||(O=!0,x(F)),N=e,void(R=n);o=!0}if(null!==e){M=!0;try{e(o)}finally{M=!1}}};var F=function(e){if(null!==N){x(F);var t=e-I+U;tt&&(t=8),U=tt?D.postMessage(void 0):O||(O=!0,x(F))},_=function(){N=null,z=!1,R=-1}}t.unstable_ImmediatePriority=1,t.unstable_UserBlockingPriority=2,t.unstable_NormalPriority=3,t.unstable_IdlePriority=5,t.unstable_LowPriority=4,t.unstable_runWithPriority=function(e,n){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var r=o,a=i;o=e,i=t.unstable_now();try{return n()}finally{o=r,i=a,d()}},t.unstable_next=function(e){switch(o){case 1:case 2:case 3:var n=3;break;default:n=o}var r=o,a=i;o=n,i=t.unstable_now();try{return e()}finally{o=r,i=a,d()}},t.unstable_scheduleCallback=function(e,r){var a=-1!==i?i:t.unstable_now();if("object"==typeof r&&null!==r&&"number"==typeof r.timeout)r=a+r.timeout;else switch(o){case 1:r=a+-1;break;case 2:r=a+250;break;case 5:r=a+1073741823;break;case 4:r=a+1e4;break;default:r=a+5e3}if(e={callback:e,priorityLevel:o,expirationTime:r,next:null,previous:null},null===n)n=e.next=e.previous=e,c();else{a=null;var l=n;do{if(l.expirationTime>r){a=l;break}l=l.next}while(l!==n);null===a?a=n:a===n&&(n=e,c()),(r=a.previous).next=a.previous=e,e.next=a,e.previous=r}return e},t.unstable_cancelCallback=function(e){var t=e.next;if(null!==t){if(t===e)n=null;else{e===n&&(n=t);var r=e.previous;r.next=t,t.previous=r}e.next=e.previous=null}},t.unstable_wrapCallback=function(e){var n=o;return function(){var r=o,a=i;o=n,i=t.unstable_now();try{return e.apply(this,arguments)}finally{o=r,i=a,d()}}},t.unstable_getCurrentPriorityLevel=function(){return o},t.unstable_shouldYield=function(){return!r&&(null!==n&&n.expirationTime1)for(var n=1;n ?(paragraph|[^\n]*)(?:\n|$))+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:"^ {0,3}(?:<(script|pre|style)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?\\?>\\n*|\\n*|\\n*|)[\\s\\S]*?(?:\\n{2,}|$)|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)|(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$))",def:/^ {0,3}\[(label)\]: *\n? *]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,table:g,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,paragraph:/^([^\n]+(?:\n(?!hr|heading|lheading| {0,3}>|<\/?(?:tag)(?: +|\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)*)/,text:/^[^\n]+/};function r(e){this.tokens=[],this.tokens.links=Object.create(null),this.options=e||y.defaults,this.rules=n.normal,this.options.pedantic?this.rules=n.pedantic:this.options.gfm&&(this.options.tables?this.rules=n.tables:this.rules=n.gfm)}n._label=/(?!\s*\])(?:\\[\[\]]|[^\[\]])+/,n._title=/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/,n.def=d(n.def).replace("label",n._label).replace("title",n._title).getRegex(),n.bullet=/(?:[*+-]|\d+\.)/,n.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/,n.item=d(n.item,"gm").replace(/bull/g,n.bullet).getRegex(),n.list=d(n.list).replace(/bull/g,n.bullet).replace("hr","\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))").replace("def","\\n+(?="+n.def.source+")").getRegex(),n._tag="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",n._comment=//,n.html=d(n.html,"i").replace("comment",n._comment).replace("tag",n._tag).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),n.paragraph=d(n.paragraph).replace("hr",n.hr).replace("heading",n.heading).replace("lheading",n.lheading).replace("tag",n._tag).getRegex(),n.blockquote=d(n.blockquote).replace("paragraph",n.paragraph).getRegex(),n.normal=m({},n),n.gfm=m({},n.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/}),n.gfm.paragraph=d(n.paragraph).replace("(?!","(?!"+n.gfm.fences.source.replace("\\1","\\2")+"|"+n.list.source.replace("\\1","\\3")+"|").getRegex(),n.tables=m({},n.gfm,{nptable:/^ *([^|\n ].*\|.*)\n *([-:]+ *\|[-| :]*)(?:\n((?:.*[^>\n ].*(?:\n|$))*)\n*|$)/,table:/^ *\|(.+)\n *\|?( *[-:]+[-| :]*)(?:\n((?: *[^>\n ].*(?:\n|$))*)\n*|$)/}),n.pedantic=m({},n.normal,{html:d("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",n._comment).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/}),r.rules=n,r.lex=function(e,t){return new r(t).lex(e)},r.prototype.lex=function(e){return e=e.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n"),this.token(e,!0)},r.prototype.token=function(e,t){var r,o,i,a,l,u,c,s,d,f,p,h,g,m,y,x;for(e=e.replace(/^ +$/gm,"");e;)if((i=this.rules.newline.exec(e))&&(e=e.substring(i[0].length),i[0].length>1&&this.tokens.push({type:"space"})),i=this.rules.code.exec(e))e=e.substring(i[0].length),i=i[0].replace(/^ {4}/gm,""),this.tokens.push({type:"code",text:this.options.pedantic?i:v(i,"\n")});else if(i=this.rules.fences.exec(e))e=e.substring(i[0].length),this.tokens.push({type:"code",lang:i[2],text:i[3]||""});else if(i=this.rules.heading.exec(e))e=e.substring(i[0].length),this.tokens.push({type:"heading",depth:i[1].length,text:i[2]});else if(t&&(i=this.rules.nptable.exec(e))&&(u={type:"table",header:b(i[1].replace(/^ *| *\| *$/g,"")),align:i[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:i[3]?i[3].replace(/\n$/,"").split("\n"):[]}).header.length===u.align.length){for(e=e.substring(i[0].length),p=0;p ?/gm,""),this.token(i,t),this.tokens.push({type:"blockquote_end"});else if(i=this.rules.list.exec(e)){for(e=e.substring(i[0].length),c={type:"list_start",ordered:m=(a=i[2]).length>1,start:m?+a:"",loose:!1},this.tokens.push(c),s=[],r=!1,g=(i=i[0].match(this.rules.item)).length,p=0;p1&&l.length>1||(e=i.slice(p+1).join("\n")+e,p=g-1)),o=r||/\n\n(?!\s*$)/.test(u),p!==g-1&&(r="\n"===u.charAt(u.length-1),o||(o=r)),o&&(c.loose=!0),x=void 0,(y=/^\[[ xX]\] /.test(u))&&(x=" "!==u[1],u=u.replace(/^\[[ xX]\] +/,"")),d={type:"list_item_start",task:y,checked:x,loose:o},s.push(d),this.tokens.push(d),this.token(u,!1),this.tokens.push({type:"list_item_end"});if(c.loose)for(g=s.length,p=0;p?@\[\]\\^_`{|}~])/,autolink:/^<(scheme:[^\s\x00-\x1f<>]*|email)>/,url:g,tag:"^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^",link:/^!?\[(label)\]\(href(?:\s+(title))?\s*\)/,reflink:/^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,nolink:/^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,strong:/^__([^\s])__(?!_)|^\*\*([^\s])\*\*(?!\*)|^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)/,em:/^_([^\s_])_(?!_)|^\*([^\s*"<\[])\*(?!\*)|^_([^\s][\s\S]*?[^\s_])_(?!_|[^\s.])|^_([^\s_][\s\S]*?[^\s])_(?!_|[^\s.])|^\*([^\s"<\[][\s\S]*?[^\s*])\*(?!\*)|^\*([^\s*"<\[][\s\S]*?[^\s])\*(?!\*)/,code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,br:/^( {2,}|\\)\n(?!\s*$)/,del:g,text:/^(`+|[^`])[\s\S]*?(?=[\\?@\[\]\\^_`{|}~])/g,o._scheme=/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/,o._email=/[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/,o.autolink=d(o.autolink).replace("scheme",o._scheme).replace("email",o._email).getRegex(),o._attribute=/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/,o.tag=d(o.tag).replace("comment",n._comment).replace("attribute",o._attribute).getRegex(),o._label=/(?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?/,o._href=/\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f\\]*\)|[^\s\x00-\x1f()\\])*?)/,o._title=/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/,o.link=d(o.link).replace("label",o._label).replace("href",o._href).replace("title",o._title).getRegex(),o.reflink=d(o.reflink).replace("label",o._label).getRegex(),o.normal=m({},o),o.pedantic=m({},o.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/,link:d(/^!?\[(label)\]\((.*?)\)/).replace("label",o._label).getRegex(),reflink:d(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",o._label).getRegex()}),o.gfm=m({},o.normal,{escape:d(o.escape).replace("])","~|])").getRegex(),_extended_email:/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,url:/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,_backpedal:/(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,del:/^~+(?=\S)([\s\S]*?\S)~+/,text:d(o.text).replace("]|","~]|").replace("|$","|https?://|ftp://|www\\.|[a-zA-Z0-9.!#$%&'*+/=?^_`{\\|}~-]+@|$").getRegex()}),o.gfm.url=d(o.gfm.url).replace("email",o.gfm._extended_email).getRegex(),o.breaks=m({},o.gfm,{br:d(o.br).replace("{2,}","*").getRegex(),text:d(o.gfm.text).replace("{2,}","*").getRegex()}),i.rules=o,i.output=function(e,t,n){return new i(t,n).output(e)},i.prototype.output=function(e){for(var t,n,r,o,a,l,u="";e;)if(a=this.rules.escape.exec(e))e=e.substring(a[0].length),u+=a[1];else if(a=this.rules.autolink.exec(e))e=e.substring(a[0].length),r="@"===a[2]?"mailto:"+(n=c(this.mangle(a[1]))):n=c(a[1]),u+=this.renderer.link(r,null,n);else if(this.inLink||!(a=this.rules.url.exec(e))){if(a=this.rules.tag.exec(e))!this.inLink&&/^/i.test(a[0])&&(this.inLink=!1),!this.inRawBlock&&/^<(pre|code|kbd|script)(\s|>)/i.test(a[0])?this.inRawBlock=!0:this.inRawBlock&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(a[0])&&(this.inRawBlock=!1),e=e.substring(a[0].length),u+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(a[0]):c(a[0]):a[0];else if(a=this.rules.link.exec(e))e=e.substring(a[0].length),this.inLink=!0,r=a[2],this.options.pedantic?(t=/^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(r))?(r=t[1],o=t[3]):o="":o=a[3]?a[3].slice(1,-1):"",r=r.trim().replace(/^<([\s\S]*)>$/,"$1"),u+=this.outputLink(a,{href:i.escapes(r),title:i.escapes(o)}),this.inLink=!1;else if((a=this.rules.reflink.exec(e))||(a=this.rules.nolink.exec(e))){if(e=e.substring(a[0].length),t=(a[2]||a[1]).replace(/\s+/g," "),!(t=this.links[t.toLowerCase()])||!t.href){u+=a[0].charAt(0),e=a[0].substring(1)+e;continue}this.inLink=!0,u+=this.outputLink(a,t),this.inLink=!1}else if(a=this.rules.strong.exec(e))e=e.substring(a[0].length),u+=this.renderer.strong(this.output(a[4]||a[3]||a[2]||a[1]));else if(a=this.rules.em.exec(e))e=e.substring(a[0].length),u+=this.renderer.em(this.output(a[6]||a[5]||a[4]||a[3]||a[2]||a[1]));else if(a=this.rules.code.exec(e))e=e.substring(a[0].length),u+=this.renderer.codespan(c(a[2].trim(),!0));else if(a=this.rules.br.exec(e))e=e.substring(a[0].length),u+=this.renderer.br();else if(a=this.rules.del.exec(e))e=e.substring(a[0].length),u+=this.renderer.del(this.output(a[1]));else if(a=this.rules.text.exec(e))e=e.substring(a[0].length),this.inRawBlock?u+=this.renderer.text(a[0]):u+=this.renderer.text(c(this.smartypants(a[0])));else if(e)throw new Error("Infinite loop on byte: "+e.charCodeAt(0))}else{if("@"===a[2])r="mailto:"+(n=c(a[0]));else{do{l=a[0],a[0]=this.rules._backpedal.exec(a[0])[0]}while(l!==a[0]);n=c(a[0]),r="www."===a[1]?"http://"+n:n}e=e.substring(a[0].length),u+=this.renderer.link(r,null,n)}return u},i.escapes=function(e){return e?e.replace(i.rules._escapes,"$1"):e},i.prototype.outputLink=function(e,t){var n=t.href,r=t.title?c(t.title):null;return"!"!==e[0].charAt(0)?this.renderer.link(n,r,this.output(e[1])):this.renderer.image(n,r,c(e[1]))},i.prototype.smartypants=function(e){return this.options.smartypants?e.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014\/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014\/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…"):e},i.prototype.mangle=function(e){if(!this.options.mangle)return e;for(var t,n="",r=e.length,o=0;o.5&&(t="x"+t.toString(16)),n+="&#"+t+";";return n},a.prototype.code=function(e,t,n){if(this.options.highlight){var r=this.options.highlight(e,t);null!=r&&r!==e&&(n=!0,e=r)}return t?'
      '+(n?e:c(e,!0))+"
      \n":"
      "+(n?e:c(e,!0))+"
      "},a.prototype.blockquote=function(e){return"
      \n"+e+"
      \n"},a.prototype.html=function(e){return e},a.prototype.heading=function(e,t,n){return this.options.headerIds?"'+e+"\n":""+e+"\n"},a.prototype.hr=function(){return this.options.xhtml?"
      \n":"
      \n"},a.prototype.list=function(e,t,n){var r=t?"ol":"ul";return"<"+r+(t&&1!==n?' start="'+n+'"':"")+">\n"+e+"\n"},a.prototype.listitem=function(e){return"
    • "+e+"
    • \n"},a.prototype.checkbox=function(e){return" "},a.prototype.paragraph=function(e){return"

      "+e+"

      \n"},a.prototype.table=function(e,t){return t&&(t=""+t+""),"\n\n"+e+"\n"+t+"
      \n"},a.prototype.tablerow=function(e){return"\n"+e+"\n"},a.prototype.tablecell=function(e,t){var n=t.header?"th":"td";return(t.align?"<"+n+' align="'+t.align+'">':"<"+n+">")+e+"\n"},a.prototype.strong=function(e){return""+e+""},a.prototype.em=function(e){return""+e+""},a.prototype.codespan=function(e){return""+e+""},a.prototype.br=function(){return this.options.xhtml?"
      ":"
      "},a.prototype.del=function(e){return""+e+""},a.prototype.link=function(e,t,n){if(null===(e=f(this.options.sanitize,this.options.baseUrl,e)))return n;var r='
      "},a.prototype.image=function(e,t,n){if(null===(e=f(this.options.sanitize,this.options.baseUrl,e)))return n;var r=''+n+'":">"},a.prototype.text=function(e){return e},l.prototype.strong=l.prototype.em=l.prototype.codespan=l.prototype.del=l.prototype.text=function(e){return e},l.prototype.link=l.prototype.image=function(e,t,n){return""+n},l.prototype.br=function(){return""},u.parse=function(e,t){return new u(t).parse(e)},u.prototype.parse=function(e){this.inline=new i(e.links,this.options),this.inlineText=new i(e.links,m({},this.options,{renderer:new l})),this.tokens=e.reverse();for(var t="";this.next();)t+=this.tok();return t},u.prototype.next=function(){return this.token=this.tokens.pop()},u.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0},u.prototype.parseText=function(){for(var e=this.token.text;"text"===this.peek().type;)e+="\n"+this.next().text;return this.inline.output(e)},u.prototype.tok=function(){switch(this.token.type){case"space":return"";case"hr":return this.renderer.hr();case"heading":return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,s(this.inlineText.output(this.token.text)));case"code":return this.renderer.code(this.token.text,this.token.lang,this.token.escaped);case"table":var e,t,n,r,o="",i="";for(n="",e=0;e"']/,c.escapeReplace=/[&<>"']/g,c.replacements={"&":"&","<":"<",">":">",'"':""","'":"'"},c.escapeTestNoEncode=/[<>"']|&(?!#?\w+;)/,c.escapeReplaceNoEncode=/[<>"']|&(?!#?\w+;)/g;var p={},h=/^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;function g(){}function m(e){for(var t,n,r=1;r=0&&"\\"===n[o];)r=!r;return r?"|":" |"}).split(/ \|/),r=0;if(n.length>t)n.splice(t);else for(;n.lengthAn error occurred:

      "+c(e.message+"",!0)+"
      ";throw e}}g.exec=g,y.options=y.setOptions=function(e){return m(y.defaults,e),y},y.getDefaults=function(){return{baseUrl:null,breaks:!1,gfm:!0,headerIds:!0,headerPrefix:"",highlight:null,langPrefix:"language-",mangle:!0,pedantic:!1,renderer:new a,sanitize:!1,sanitizer:null,silent:!1,smartLists:!1,smartypants:!1,tables:!0,xhtml:!1}},y.defaults=y.getDefaults(),y.Parser=u,y.parser=u.parse,y.Renderer=a,y.TextRenderer=l,y.Lexer=r,y.lexer=r.lex,y.InlineLexer=i,y.inlineLexer=i.output,y.parse=y,e.exports=y}(this||"undefined"!=typeof window&&window)}).call(this,n(6))},function(module,exports,__webpack_require__){"use strict";__webpack_require__(27),__webpack_require__(29);var _krayon2=_interopRequireDefault(__webpack_require__(31)),_react=_interopRequireDefault(__webpack_require__(0)),_propTypes=_interopRequireDefault(__webpack_require__(1)),_textarea=_interopRequireDefault(__webpack_require__(33)),_util=__webpack_require__(4);function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}class Editor extends _react.default.Component{constructor(e){super(e),this.state={value:(0,_util.cleanString)(e.value),output:{},duration:0}}run(){const self=this,{value:value}=this.state;return function(){var c={cons:[],log:function(){c.cons.push({type:"text",value:Array.prototype.join.call(arguments," ")}),self.setState({output:c})},html:function(e){c.cons.push({type:"html",value:e}),self.setState({output:c})}};const start=Date.now();try{!function(console){c.val=eval(value)}(c)}catch(e){c.log("Error:",e)}self.setState({output:c,duration:Date.now()-start})}(),this}onChange(e){this.setState({value:e})}render(){const{title:e,subtitle:t}=this.props,{value:n,output:r,duration:o}=this.state,{cons:i,val:a}=r;let l=(0,_krayon2.default)(n.replace(//g,">"));return _react.default.createElement("div",{className:"editor"},_react.default.createElement("div",{className:"text-left text-black"},(0,_util.cleanString)(e),t?_react.default.createElement("div",null,_react.default.createElement("small",null," ",(0,_util.cleanString)(t)," ")):""),_react.default.createElement("br",null),_react.default.createElement("div",{style:{position:"relative"}},_react.default.createElement(_textarea.default,{onChange:this.onChange.bind(this),value:n}),_react.default.createElement("pre",{className:"textarea-overlay",style:{position:"absolute",top:0,backgroundColor:"rgba(255, 255, 255, 0)",whiteSpace:"pre-wrap"},dangerouslySetInnerHTML:{__html:l}})),_react.default.createElement("div",{style:{overflow:"auto",border:"1px solid #cfcfc4",padding:"0",borderBottomLeftRadius:"5px",borderBottomRightRadius:"5px"}},_react.default.createElement("div",{className:"time"},"Run took ",o,"ms"),_react.default.createElement("div",{className:"console"},_react.default.createElement("span",{className:"output"},a||i?"":_react.default.createElement("div",null,_react.default.createElement("pre",{style:{whiteSpace:"pre-wrap",margin:0,borderRadius:0}},"Output from the example appears here")),a?_react.default.createElement("div",null,_react.default.createElement("pre",{style:{whiteSpace:"pre-wrap",margin:0,borderRadius:0}},a.toString())):"",i&&i.length>0?i.map((e,t)=>{const{type:n,value:r}=e;return"html"===n?_react.default.createElement("pre",{key:`${t}/${Date.now()}`,style:{margin:"10px",border:"1px solid #f5f5f5",padding:"5px",position:"relative"}}," ",_react.default.createElement("div",{dangerouslySetInnerHTML:{__html:r.toString()}})," "):_react.default.createElement("pre",{key:`${t}/${Date.now()}`,style:{margin:"10px",border:"1px solid #f5f5f5",padding:"5px",position:"relative"}},r)}):""),_react.default.createElement("button",{className:"run",type:"button",onClick:this.run.bind(this)},"Run"))))}}Editor.propTypes={title:_propTypes.default.string,subtitle:_propTypes.default.string,value:_propTypes.default.string},module.exports=Editor},function(e,t,n){var r=n(28);"string"==typeof r&&(r=[[e.i,r,""]]);var o={hmr:!0,transform:void 0,insertInto:void 0};n(3)(r,o);r.locals&&(e.exports=r.locals)},function(e,t,n){(e.exports=n(2)(!1)).push([e.i,".panel, .panel-body, .ace_editor, .textarea, .textarea-container {\n border-top-left-radius: 5px;\n border-top-right-radius: 5px;\n}\n\n.panel, .panel-body, .panel-footer, .panel-header {\n padding: 0 !important;\n}\n\n.ace_editor {\n font-size: 1em !important;\n}\n\n.description {\n text-align: center;\n font-weight: 100;\n font-size: 18px;\n}\n\n.console {\n position: relative;\n border-top: 1px solid #CCC;\n font-family: monospace;\n line-height: 28px;\n min-height: 37px;\n}\n\n.console .output {\n color: #666;\n width: 100%;\n display: inline-block;\n}\n\n.console .output > pre {\n padding: 0;\n white-space: pre-wrap;\n background-color: transparent;\n}\n\n.time {\n color: #666;\n font-family: monospace;\n line-height: 13px;\n padding: 8px;\n font-size: 13px;\n}\n\n.run {\n padding: 12px 18px;\n margin: 10px;\n cursor: pointer;\n display: inline-block;\n text-align: center;\n border: 1px solid #cfcfc4;\n margin: 4px;\n position: absolute;\n top: 0;\n right: 0;\n background-color: rgb(81, 163, 81);\n color: white;\n border-radius: 0px;\n padding: 0 10px;\n line-height: 28px;\n}\n\n.textarea-container {\n position: relative;\n border: 1px solid #cfcfc4;\n border-bottom: 0;\n overflow: hidden;\n z-index: 100;\n}\n\n.textarea {\n resize: none;\n width: 100%;\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n color: transparent;\n background: transparent;\n color: black;\n text-shadow: 0px 0px 0px transparent;\n -webkit-text-fill-color: transparent;\n margin: 0;\n padding: 10px;\n outline: none;\n border: none;\n min-height: 40px;\n font: 13px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;\n box-shadow: none;\n display: block;\n overflow: hidden;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.textarea-overlay {\n resize: none;\n width: 100%;\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n margin: 0;\n padding: 10px;\n outline: none;\n border: none;\n min-height: 40px;\n font: 13px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;\n box-shadow: none;\n display: block;\n overflow: hidden;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.textarea--hidden {\n display: block;\n white-space: pre-wrap;\n word-wrap: break-word;\n visibility: hidden;\n position: absolute;\n top: 0;\n}\n",""])},function(e,t,n){var r=n(30);"string"==typeof r&&(r=[[e.i,r,""]]);var o={hmr:!0,transform:void 0,insertInto:void 0};n(3)(r,o);r.locals&&(e.exports=r.locals)},function(e,t,n){(e.exports=n(2)(!1)).push([e.i,".string {\n color: #032f62\n}\n.keyword {\n color: #d73a49\n}\n.operator {\n color: #d73a49\n}\n.function {\n color: #005cc5\n}\n.class {\n color: #6f42c1\n}\n.comment {\n color: #6a737d\n}\n",""])},function(e,t,n){const{decode:r,parse:o}=n(32);e.exports=function(e){return o(e).replace(/\{#([a-z]+)#(.*?)#\}/g,(e,t,n)=>{let o=r(n);return"\0"!==o?`${o}`:""})}},function(e,t){const n={string:/("[^"]*"|'[^']*'|`[^`]*`)/g,comment:/(\/\*.*?\*\/|\/\/.*)/g,class:/\b([A-Z][a-z]+)\b/g,number:/\b([0-9]+(?:\.[0-9]+)?)\b/g,keyword:new RegExp("\\b("+["const","console","process","let","var","function","if","else","for","while","break","switch","case","do","new","continue","delete","return","this","true","false","throw","catch","typeof"].join("|")+")\\b","g"),function:/([\w+]*)\(.*\);?/g,operator:/([+|=|-|||!|<|>|%|*|~])/g};function r(e){return e.split("").map(e=>e.charCodeAt(0)>127?e:String.fromCharCode(e.charCodeAt(0)+10240)).join(" ")}e.exports={decode:function(e){return e.trim().split(" ").map(e=>e.charCodeAt(0)-10240>127?e:String.fromCharCode(e.charCodeAt(0)-10240)).join("")},encode:r,parse:function e(t){return Object.keys(n).forEach(o=>{t=t.replace(n[o],(t,n)=>{let i=t.replace(n,""),a=`{#${o}#${r(n)}#}`;if("function"==o&&i){let t=i.indexOf("("),n=i.lastIndexOf(")"),r=i.lastIndexOf(";"),o=i.replace(/./g,(e,o)=>{switch(o){case t:case n:case r:return"";default:return e}});i=`(${e(o)})${r>-1?";":""}`}return a+i})}),t}}},function(e,t,n){"use strict";var r=i(n(0)),o=i(n(1));function i(e){return e&&e.__esModule?e:{default:e}}const a=20;class l extends r.default.Component{constructor(e){super(e),this.state={height:a,value:e.value||""},this.setValue=this.setValue.bind(this),this.setFilledTextareaHeight=this.setFilledTextareaHeight.bind(this)}componentDidMount(){this.mounted=!0,this.setFilledTextareaHeight()}setFilledTextareaHeight(){this.mounted&&setTimeout(()=>{const e=this.hidden;this.setState({height:e.clientHeight})},5)}setValue(e){const{onChange:t}=this.props,{value:n}=e.target;this.setState({value:n}),t&&t(n)}getExpandableField(){const{height:e,value:t}=this.state,n=e<=a;return r.default.createElement("textarea",{className:"textarea",name:"textarea",id:"textarea",autoFocus:!0,defaultValue:t,style:{height:e,resize:n?"none":null},onChange:this.setValue,onKeyUp:this.setFilledTextareaHeight})}getGhostField(){return r.default.createElement("div",{className:"textarea textarea--hidden",ref:e=>{this.hidden=e},"aria-hidden":"true"},this.state.value)}render(){return r.default.createElement("div",{className:"textarea-container"},this.getExpandableField(),this.getGhostField())}}l.propTypes={value:o.default.string,onChange:o.default.function},e.exports=l}]); \ No newline at end of file diff --git a/example/index.js b/example/index.js index 1e856d8..9a6c3bb 100644 --- a/example/index.js +++ b/example/index.js @@ -24,8 +24,8 @@ const port = process.env.PORT || 7005; const repos = new Server(path.normalize(path.resolve(__dirname, 'tmp')), { autoCreate: true, - authenticate: (type, repo, user, next) => { - console.log(type, repo); // eslint-disable-line + authenticate: ({ type, repo, user, headers }, next) => { + console.log(type, repo, headers); // eslint-disable-line if(type == 'push') { user((username, password) => { console.log(username, password); // eslint-disable-line diff --git a/lib/git.js b/lib/git.js index 9f9f129..7a49585 100644 --- a/lib/git.js +++ b/lib/git.js @@ -118,14 +118,14 @@ class Git extends EventEmitter { * @param {Object} options - options that can be applied on the new instance being created * @param {Boolean=} options.autoCreate - By default, repository targets will be created if they don't exist. You can disable that behavior with `options.autoCreate = true` - * @param {Function} options.authenticate - a function that has the following arguments (repo, username, password, next) and will be called when a request comes through if set + * @param {Function} options.authenticate - a function that has the following arguments ({ type, repo, username, password, headers }, next) and will be called when a request comes through if set * - authenticate: (type, repo, username, password, next) => { + authenticate: ({ type, repo, username, password, headers }, next) => { console.log(type, repo, username, password); next(); } // alternatively you can also pass authenticate a promise - authenticate: (type, repo, username, password, next) => { + authenticate: ({ type, repo, username, password, headers }, next) => { console.log(type, repo, username, password); return new Promise((resolve, reject) => { if(username === 'foo') { @@ -302,9 +302,12 @@ class Git extends EventEmitter { // check if the repo is authenticated if(this.authenticate) { const type = this.getType(service); - const promise = this.authenticate(type, repoName, basicAuth.bind(null, req, res), (error) => { + const headers = req.headers; + const user = basicAuth.bind(null, req, res); + const promise = this.authenticate({ type, repo: repoName, user, headers }, (error) => { return next(error); }); + if(promise instanceof Promise) { return promise .then(next) @@ -456,7 +459,7 @@ class Git extends EventEmitter { * @memberof Git * @param {Promise} - will resolve or reject when the server closes or fails to close. */ - close(callback) { + close() { return new Promise((resolve, reject) => { this.server.close((err) => { err ? reject(err) : resolve(); diff --git a/package.json b/package.json index 6c5e249..c442583 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-git-server", - "version": "0.5.1", + "version": "0.6.0", "description": "🎡 A configurable git server written in Node.js", "author": "Gabriel J. Csapo ", "contributors": [ diff --git a/test/git.js b/test/git.js index a56330e..588d586 100644 --- a/test/git.js +++ b/test/git.js @@ -10,7 +10,7 @@ const async = require('async'); const GitServer = require('../'); test('git', (t) => { - t.plan(9); + t.plan(10); t.test('create, push to, and clone a repo', (t) => { var lastCommit; @@ -648,7 +648,7 @@ test('git', (t) => { const repos = new GitServer(repoDir, { autoCreate: true, - authenticate: (type, repo, user, next) => { + authenticate: ({ type, repo, user }, next) => { if (type == 'download', repo == 'doom') { user((username, password) => { @@ -700,6 +700,76 @@ test('git', (t) => { }); + t.test('should be able to access headers in authenticate', (t) => { + t.plan(14); + + const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + const dstDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; + + fs.mkdirSync(repoDir, 0700); + fs.mkdirSync(srcDir, 0700); + fs.mkdirSync(dstDir, 0700); + + const repos = new GitServer(repoDir, { + autoCreate: true, + authenticate: ({ type, repo, user, headers }, next) => { + if (type == 'download', repo == 'doom') { + t.ok(headers['host']); + t.ok(headers['user-agent']); + t.ok(headers['accept']); + t.ok(headers['pragma']); + t.ok(headers['accept-encoding']); + + user((username, password) => { + if (username == 'root' && password == 'root') { + next(); + } else { + next('that is not the correct password'); + } + }); + } else { + next('that is not the correct password'); + } + } + }); + const port = Math.floor(Math.random() * ((1 << 16) - 1e4)) + 1e4; + repos.listen(port); + + process.chdir(srcDir); + async.waterfall([ + (callback) => { + process.chdir(dstDir); + const clone = spawn('git', ['clone', `http://root:root@localhost:${port}/doom.git`]); + + clone.on('close', function(code) { + t.equal(code, 0); + callback(); + }); + }, + (callback) => { + process.chdir(dstDir); + const clone = spawn('git', ['clone', `http://root:world@localhost:${port}/doom.git doom1`]); + let error = ''; + + clone.stderr.on('data', (d) => { + error += d.toString('utf8'); + }); + + clone.on('close', function(code) { + t.equal(error, `Cloning into 'doom.git doom1'...\nfatal: unable to access 'http://root:world@localhost:${port}/doom.git doom1/': Empty reply from server\n`); + t.equal(code, 128); + callback(); + }); + } + ], (err) => { + t.ok(!err, 'no errors'); + repos.close(); + t.end(); + }); + + }); + t.test('should be able to protect certain routes with a promised authenticate', (t) => { const repoDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; const srcDir = `/tmp/${Math.floor(Math.random() * (1 << 30)).toString(16)}`; @@ -711,7 +781,7 @@ test('git', (t) => { const repos = new GitServer(repoDir, { autoCreate: true, - authenticate: (type, repo, user) => { + authenticate: ({ type, repo, user }) => { return new Promise(function(resolve, reject) { if (type == 'download', repo == 'doom') { user((username, password) => {