[
  {
    "path": ".gitignore",
    "content": "*.swp\nbin/\npkg/\nsrc/code.google.com\nsrc/github.com\nsrc/bitbucket.org\nsrc/launchpad.net\nsrc/gopkg.in\nsrc/ngrok/client/assets/\nsrc/ngrok/server/assets/\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: false\nlanguage: go\nscript: make release-all\ninstall: true\ngo: \n    - 1.4\n    - 1.5\n    - 1.6\n    - tip\n\nmatrix:\n    allow_failures:\n        - go: tip\n"
  },
  {
    "path": "CONTRIBUTORS",
    "content": "Contributors to ngrok, both large and small:\n\n- Alan Shreve \n- Brandon Philips \n- Caleb Spare \n- Jay Hayes \n- Kevin Burke \n- Kyle Conroy \n- Nick Presta \n- Stephen Huenneke \n- inconshreveable \n- jzs \n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2013 Alan Shreve\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: default server client deps fmt clean all release-all assets client-assets server-assets contributors\nexport GOPATH:=$(shell pwd)\n\nBUILDTAGS=debug\ndefault: all\n\ndeps: assets\n\tgo get -tags '$(BUILDTAGS)' -d -v ngrok/...\n\nserver: deps\n\tgo install -tags '$(BUILDTAGS)' ngrok/main/ngrokd\n\nfmt:\n\tgo fmt ngrok/...\n\nclient: deps\n\tgo install -tags '$(BUILDTAGS)' ngrok/main/ngrok\n\nassets: client-assets server-assets\n\nbin/go-bindata:\n\tGOOS=\"\" GOARCH=\"\" go get github.com/jteeuwen/go-bindata/go-bindata\n\nclient-assets: bin/go-bindata\n\tbin/go-bindata -nomemcopy -pkg=assets -tags=$(BUILDTAGS) \\\n\t\t-debug=$(if $(findstring debug,$(BUILDTAGS)),true,false) \\\n\t\t-o=src/ngrok/client/assets/assets_$(BUILDTAGS).go \\\n\t\tassets/client/...\n\nserver-assets: bin/go-bindata\n\tbin/go-bindata -nomemcopy -pkg=assets -tags=$(BUILDTAGS) \\\n\t\t-debug=$(if $(findstring debug,$(BUILDTAGS)),true,false) \\\n\t\t-o=src/ngrok/server/assets/assets_$(BUILDTAGS).go \\\n\t\tassets/server/...\n\nrelease-client: BUILDTAGS=release\nrelease-client: client\n\nrelease-server: BUILDTAGS=release\nrelease-server: server\n\nrelease-all: fmt release-client release-server\n\nall: fmt client server\n\nclean:\n\tgo clean -i -r ngrok/...\n\trm -rf src/ngrok/client/assets/ src/ngrok/server/assets/\n\ncontributors:\n\techo \"Contributors to ngrok, both large and small:\\n\" > CONTRIBUTORS\n\tgit log --raw | grep \"^Author: \" | sort | uniq | cut -d ' ' -f2- | sed 's/^/- /' | cut -d '<' -f1 >> CONTRIBUTORS\n"
  },
  {
    "path": "README.md",
    "content": "# ngrok - Unified Ingress for Developers\n\n[https://ngrok.com](https://ngrok.com)\n\n## ngrok Community on GitHub\n\nIf you are having an issue with the ngrok cloud service please open an issue on the [ngrok community on GitHub](https://github.com/ngrok/ngrok)\n\n## This repository is archived\n\nThis is the GitHub repository for the old v1 version of ngrok which was actively developed from 2013-2016.\n\n**This repository is archived: ngrok v1 is no longer developed, supported or maintained.**\n\nThank you to everyone who contributed to ngrok v1 it in its early days with PRs, issues and feedback. If you wish to continue development on this codebase, please fork it.\n\nngrok's cloud service continues to operate and you can sign up for it here: [https://ngrok.com/signup](https://ngrok.com/signup)\n\n## What is ngrok?\n\nngrok is a globally distributed reverse proxy that secures, protects and accelerates your applications and network services, no matter where you run them. You can think of ngrok as the front door to your applications. ngrok combines your reverse proxy, firewall, API gateway, and global load balancing into one. ngrok can capture and analyze all traffic to your web service for later inspection and replay.\n\nTo use ngrok, sign up at [https://ngrok.com/signup](https://ngrok.com/signup)\n\n## ngrok open-source development\nngrok continues to contribute to the open source ecosystem at [https://github.com/ngrok](https://github.com/ngrok) with:\n- [The ngrok kubernetes operator](https://github.com/ngrok/kubernetes-ingress-controller)\n- [The ngrok agent SDKs](https://ngrok.com/docs/agent-sdks/) for [Python](https://github.com/ngrok/ngrok-python), [JavaScript](https://github.com/ngrok/ngrok-javascript), [Go](https://github.com/ngrok/ngrok-go), [Rust](https://github.com/ngrok/ngrok-rust) and [Java](https://github.com/ngrok/ngrok-java)\n\n\n## What is ngrok for?\n\n[What can you do with ngrok?](https://ngrok.com/docs/what-is-ngrok/#what-can-you-do-with-ngrok)\n\n- Site-to-site Connectivity: Connect securely to APIs and databases in your customers' networks without complex network configuration.\n- Developer Previews: Demoing an app from your local machine without deploying it\n- Webhook Testing: Developing any services which consume webhooks (HTTP callbacks) by allowing you to replay those requests\n- API Gateway: An global gateway-as-a-service that works for API running anywhere with simple CEL-based traffic policy for rate limiting, jwt authentication and more.\n- Device Gateway: Run ngrok on your IoT devices to control device APIs from your cloud \n- Debug and understand any web service by inspecting the HTTP traffic to it\n"
  },
  {
    "path": "assets/client/page.html",
    "content": "<html>\n    <head>\n        <title>ngrok</title>\n        <link href=\"/static/css/highlight.min.css\" rel=\"stylesheet\">\n        <link href=\"/static/css/bootstrap.min.css\" rel=\"stylesheet\">\n        <script src=\"/static/js/highlight.min.js\"></script>\n        <script src=\"/static/js/vkbeautify.js\"></script>\n        <script src=\"/static/js/jquery-1.9.1.min.js\"></script>\n        <script src=\"/static/js/jquery.timeago.js\"></script>\n        <script src=\"/static/js/angular.js\"></script>\n        <script src=\"/static/js/angular-sanitize.min.js\"></script>\n        <script src=\"/static/js/base64.js\"></script>\n        <script src=\"/static/js/ngrok.js\"></script>\n        <script type=\"text/javascript\">\n            window.data = JSON.parse({% . %});\n        </script>\n        <style type=\"text/css\">\n            body { margin-top: 50px; }\n            table.params { font-size: 12px; font-family: Courier, monospace; }\n            .txn-selector tr { cursor: pointer; }\n            .txn-selector tr:hover { background-color: #ddd; }\n            tr.selected, tr.selected:hover {\n                background-color: #ff9999;\n                background-color: #000000;\n                color:white;\n            }\n            .path {\n              width: 100%;\n            }\n            .wrapped {\n              word-wrap: break-word;\n              word-break: break-word;\n              overflow: hidden;\n            }\n        </style>\n    </head>\n\n    <body ng-app=\"ngrok\">\n        <div class=\"container\" ng-controller=\"HttpTxns\">\n            <div class=\"navbar navbar-inverse navbar-fixed-top\">\n                <div class=\"navbar-inner\">\n                    <div class=\"container\">\n                        <a class=\"brand\" href=\"#\">ngrok</a>\n                        <ul class=\"nav\">\n                            <li class=\"active\"><a href=\"#\">Inbound Requests</a></li>\n                            <!--\n                            <li><a href=\"#\">Outbound Requests</a></li>\n                            <li><a href=\"#\">Configuration</a></li>\n                            -->\n                        </ul>\n                    </div>\n                </div>\n            </div>\n            <div ng-show=\"txns.length==0\" class=\"row\">\n                <div class=\"span6 offset3\">\n                    <div class=\"well\" style=\"padding: 20px 50px;\">\n                        <h4>No requests to display yet</h4>\n\t\t\t<hr />\n                        <h5>To get started, make a request to one of your tunnel URLs:</h5>\n                            <ul>\n                                <li ng-repeat=\"t in tunnels\"><p class=\"lead\"><a target=\"_blank\" href=\"{{ t.PublicUrl }}\">{{ t.PublicUrl }}</a></p></li>\n                            </ul>\n                        </p>\n                    </div>\n                </div>\n            </div>\n            <div ng-show=\"txns.length>0\" class=\"row\">\n                <div class=\"span6\">\n                    <h4>All Requests</h4>\n                    <table class=\"table txn-selector\">\n                        <tr ng-controller=\"TxnNavItem\" ng-class=\"{'selected':isActive()}\" ng-repeat=\"txn in txns\" ng-click=\"makeActive()\">\n                            <td class=\"wrapped\"><div class=\"path\">{{ txn.Req.MethodPath }}</div></td>\n                            <td>{{ txn.Resp.Status }}</td>\n                            <td><span class=\"pull-right\">{{ txn.Duration }}</span></td>\n                        </tr>\n                    </table>\n                </div>\n                <div class=\"span6\" ng-controller=\"HttpTxn\" ng-show=\"!!Txn\">\n                    <div class=\"row-fluid\">\n                        <div class=\"span4\">\n                            <span title=\"{{ISO8601(Txn.Start)}}\" class=\"muted\">\n                                {{TimeFormat(Txn.Start)}}\n                            </span>\n                        </div>\n                        <div class=\"span4\">\n                            <i class=\"icon-time\"></i> Duration\n                            <span style=\"margin-left: 8px;\" class=\"muted\">{{Txn.Duration}}</span>\n                        </div>\n                        <div  class=\"span4\">\n                            <i class=\"icon-user\"></i> IP\n                            <span style=\"margin-left: 8px;\" class=\"muted\">{{Txn.ConnCtx.ClientAddr.split(\":\")[0]}}</span>\n                        </div>\n                    </div>\n                    <hr />\n                    <div ng-show=\"!!Req\" ng-controller=\"HttpRequest\">\n                        <h3 class=\"wrapped\">{{ Req.MethodPath }}</h3>\n                        <div onbtnclick=\"replay()\" btn=\"Replay\" tabs=\"Summary,Headers,Raw,Binary\">\n                        </div>\n\n                        <div ng-show=\"isTab('Summary')\">\n                            <keyval title=\"Query Params\" tuples=\"Req.Params\"></keyval>\n                            <div body=\"Req.Body\" binary=\"Req.Binary\"></div>\n                        </div>\n\n                        <div ng-show=\"isTab('Headers')\">\n                            <keyval title=\"Headers\" tuples=\"Req.Header\"></keyval>\n                        </div>\n\n                        <div ng-show=\"isTab('Raw')\">\n                            <pre><code class=\"http\">{{ Req.RawText }}</code></pre>\n                        </div>\n\n                        <div ng-show=\"isTab('Binary')\">\n                            <pre><code>{{ Req.RawBytes }}</code></pre>\n                        </div>\n\n                    </div>\n\n                    <hr style=\"margin: 40px 0 20px\" />\n\n                    <div ng-show=\"!!Resp\" ng-controller=\"HttpResponse\">\n                        <h3 ng-class=\"Resp.statusClass\">{{ Resp.Status }}</h3>\n\n                        <div tabs=\"Summary,Headers,Raw,Binary\"></div>\n                        <div ng-show=\"isTab('Summary')\">\n                            <div body=\"Resp.Body\" binary=\"Resp.Binary\"></div>\n                        </div>\n\n                        <div ng-show=\"isTab('Headers')\">\n                            <keyval title=\"Headers\" tuples=\"Resp.Header\"></keyval>\n                        </div>\n\n                        <div ng-show=\"isTab('Raw')\">\n                            <pre><code class=\"http\">{{ Resp.RawText }}</code></pre>\n                        </div>\n\n                        <div ng-show=\"isTab('Binary')\">\n                            <pre><code>{{ Resp.RawBytes }}</code></pre>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n        <!-- UserVoice JavaScript SDK (only needed once on a page) -->\n        <script>(function(){var uv=document.createElement('script');uv.type='text/javascript';uv.async=true;uv.src='//widget.uservoice.com/4KUmdF6WZd302MfwoayMw.js';var s=document.getElementsByTagName('script')[0];s.parentNode.insertBefore(uv,s)})()</script>\n\n        <!-- A tab to launch the Classic Widget -->\n        <script>\n        UserVoice = window.UserVoice || [];\n        UserVoice.push(['showTab', 'classic_widget', {\n          mode: 'feedback',\n          primary_color: '#cc6d00',\n          link_color: '#007dbf',\n          forum_id: 211925,\n          tab_label: 'Feedback',\n          tab_color: '#cc6d00',\n          tab_position: 'middle-left',\n          tab_inverted: false\n        }]);\n        </script>\n    </body>\n</html>\n"
  },
  {
    "path": "assets/client/static/js/angular.js",
    "content": "/*\n AngularJS v1.1.5\n (c) 2010-2012 Google, Inc. http://angularjs.org\n License: MIT\n*/\n(function(M,T,p){'use strict';function lc(){var b=M.angular;M.angular=mc;return b}function Xa(b){return!b||typeof b.length!==\"number\"?!1:typeof b.hasOwnProperty!=\"function\"&&typeof b.constructor!=\"function\"?!0:b instanceof R||ga&&b instanceof ga||Ea.call(b)!==\"[object Object]\"||typeof b.callee===\"function\"}function n(b,a,c){var d;if(b)if(H(b))for(d in b)d!=\"prototype\"&&d!=\"length\"&&d!=\"name\"&&b.hasOwnProperty(d)&&a.call(c,b[d],d);else if(b.forEach&&b.forEach!==n)b.forEach(a,c);else if(Xa(b))for(d=\n0;d<b.length;d++)a.call(c,b[d],d);else for(d in b)b.hasOwnProperty(d)&&a.call(c,b[d],d);return b}function qb(b){var a=[],c;for(c in b)b.hasOwnProperty(c)&&a.push(c);return a.sort()}function nc(b,a,c){for(var d=qb(b),e=0;e<d.length;e++)a.call(c,b[d[e]],d[e]);return d}function rb(b){return function(a,c){b(c,a)}}function Fa(){for(var b=ba.length,a;b;){b--;a=ba[b].charCodeAt(0);if(a==57)return ba[b]=\"A\",ba.join(\"\");if(a==90)ba[b]=\"0\";else return ba[b]=String.fromCharCode(a+1),ba.join(\"\")}ba.unshift(\"0\");\nreturn ba.join(\"\")}function sb(b,a){a?b.$$hashKey=a:delete b.$$hashKey}function t(b){var a=b.$$hashKey;n(arguments,function(a){a!==b&&n(a,function(a,c){b[c]=a})});sb(b,a);return b}function N(b){return parseInt(b,10)}function tb(b,a){return t(new (t(function(){},{prototype:b})),a)}function q(){}function qa(b){return b}function S(b){return function(){return b}}function C(b){return typeof b==\"undefined\"}function B(b){return typeof b!=\"undefined\"}function L(b){return b!=null&&typeof b==\"object\"}function E(b){return typeof b==\n\"string\"}function Ya(b){return typeof b==\"number\"}function ra(b){return Ea.apply(b)==\"[object Date]\"}function F(b){return Ea.apply(b)==\"[object Array]\"}function H(b){return typeof b==\"function\"}function sa(b){return b&&b.document&&b.location&&b.alert&&b.setInterval}function U(b){return E(b)?b.replace(/^\\s*/,\"\").replace(/\\s*$/,\"\"):b}function oc(b){return b&&(b.nodeName||b.bind&&b.find)}function Za(b,a,c){var d=[];n(b,function(b,g,i){d.push(a.call(c,b,g,i))});return d}function Ga(b,a){if(b.indexOf)return b.indexOf(a);\nfor(var c=0;c<b.length;c++)if(a===b[c])return c;return-1}function ta(b,a){var c=Ga(b,a);c>=0&&b.splice(c,1);return a}function V(b,a){if(sa(b)||b&&b.$evalAsync&&b.$watch)throw Error(\"Can't copy Window or Scope\");if(a){if(b===a)throw Error(\"Can't copy equivalent objects or arrays\");if(F(b))for(var c=a.length=0;c<b.length;c++)a.push(V(b[c]));else{c=a.$$hashKey;n(a,function(b,c){delete a[c]});for(var d in b)a[d]=V(b[d]);sb(a,c)}}else(a=b)&&(F(b)?a=V(b,[]):ra(b)?a=new Date(b.getTime()):L(b)&&(a=V(b,{})));\nreturn a}function pc(b,a){var a=a||{},c;for(c in b)b.hasOwnProperty(c)&&c.substr(0,2)!==\"$$\"&&(a[c]=b[c]);return a}function ia(b,a){if(b===a)return!0;if(b===null||a===null)return!1;if(b!==b&&a!==a)return!0;var c=typeof b,d;if(c==typeof a&&c==\"object\")if(F(b)){if((c=b.length)==a.length){for(d=0;d<c;d++)if(!ia(b[d],a[d]))return!1;return!0}}else if(ra(b))return ra(a)&&b.getTime()==a.getTime();else{if(b&&b.$evalAsync&&b.$watch||a&&a.$evalAsync&&a.$watch||sa(b)||sa(a))return!1;c={};for(d in b)if(!(d.charAt(0)===\n\"$\"||H(b[d]))){if(!ia(b[d],a[d]))return!1;c[d]=!0}for(d in a)if(!c[d]&&d.charAt(0)!==\"$\"&&a[d]!==p&&!H(a[d]))return!1;return!0}return!1}function $a(b,a){var c=arguments.length>2?ka.call(arguments,2):[];return H(a)&&!(a instanceof RegExp)?c.length?function(){return arguments.length?a.apply(b,c.concat(ka.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}:a}function qc(b,a){var c=a;/^\\$+/.test(b)?c=p:sa(a)?c=\"$WINDOW\":a&&T===a?c=\"$DOCUMENT\":a&&a.$evalAsync&&\na.$watch&&(c=\"$SCOPE\");return c}function ha(b,a){return JSON.stringify(b,qc,a?\"  \":null)}function ub(b){return E(b)?JSON.parse(b):b}function ua(b){b&&b.length!==0?(b=I(\"\"+b),b=!(b==\"f\"||b==\"0\"||b==\"false\"||b==\"no\"||b==\"n\"||b==\"[]\")):b=!1;return b}function va(b){b=w(b).clone();try{b.html(\"\")}catch(a){}var c=w(\"<div>\").append(b).html();try{return b[0].nodeType===3?I(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\\w\\-]+)/,function(a,b){return\"<\"+I(b)})}catch(d){return I(c)}}function vb(b){var a={},c,d;n((b||\n\"\").split(\"&\"),function(b){b&&(c=b.split(\"=\"),d=decodeURIComponent(c[0]),a[d]=B(c[1])?decodeURIComponent(c[1]):!0)});return a}function wb(b){var a=[];n(b,function(b,d){a.push(wa(d,!0)+(b===!0?\"\":\"=\"+wa(b,!0)))});return a.length?a.join(\"&\"):\"\"}function ab(b){return wa(b,!0).replace(/%26/gi,\"&\").replace(/%3D/gi,\"=\").replace(/%2B/gi,\"+\")}function wa(b,a){return encodeURIComponent(b).replace(/%40/gi,\"@\").replace(/%3A/gi,\":\").replace(/%24/g,\"$\").replace(/%2C/gi,\",\").replace(/%20/g,a?\"%20\":\"+\")}function rc(b,\na){function c(a){a&&d.push(a)}var d=[b],e,g,i=[\"ng:app\",\"ng-app\",\"x-ng-app\",\"data-ng-app\"],f=/\\sng[:\\-]app(:\\s*([\\w\\d_]+);?)?\\s/;n(i,function(a){i[a]=!0;c(T.getElementById(a));a=a.replace(\":\",\"\\\\:\");b.querySelectorAll&&(n(b.querySelectorAll(\".\"+a),c),n(b.querySelectorAll(\".\"+a+\"\\\\:\"),c),n(b.querySelectorAll(\"[\"+a+\"]\"),c))});n(d,function(a){if(!e){var b=f.exec(\" \"+a.className+\" \");b?(e=a,g=(b[2]||\"\").replace(/\\s+/g,\",\")):n(a.attributes,function(b){if(!e&&i[b.name])e=a,g=b.value})}});e&&a(e,g?[g]:[])}\nfunction xb(b,a){var c=function(){b=w(b);a=a||[];a.unshift([\"$provide\",function(a){a.value(\"$rootElement\",b)}]);a.unshift(\"ng\");var c=yb(a);c.invoke([\"$rootScope\",\"$rootElement\",\"$compile\",\"$injector\",\"$animator\",function(a,b,c,d,e){a.$apply(function(){b.data(\"$injector\",d);c(b)(a)});e.enabled(!0)}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;if(M&&!d.test(M.name))return c();M.name=M.name.replace(d,\"\");Ha.resumeBootstrap=function(b){n(b,function(b){a.push(b)});c()}}function bb(b,a){a=a||\"_\";return b.replace(sc,\nfunction(b,d){return(d?a:\"\")+b.toLowerCase()})}function cb(b,a,c){if(!b)throw Error(\"Argument '\"+(a||\"?\")+\"' is \"+(c||\"required\"));return b}function xa(b,a,c){c&&F(b)&&(b=b[b.length-1]);cb(H(b),a,\"not a function, got \"+(b&&typeof b==\"object\"?b.constructor.name||\"Object\":typeof b));return b}function tc(b){function a(a,b,e){return a[b]||(a[b]=e())}return a(a(b,\"angular\",Object),\"module\",function(){var b={};return function(d,e,g){e&&b.hasOwnProperty(d)&&(b[d]=null);return a(b,d,function(){function a(c,\nd,e){return function(){b[e||\"push\"]([c,d,arguments]);return m}}if(!e)throw Error(\"No module: \"+d);var b=[],c=[],j=a(\"$injector\",\"invoke\"),m={_invokeQueue:b,_runBlocks:c,requires:e,name:d,provider:a(\"$provide\",\"provider\"),factory:a(\"$provide\",\"factory\"),service:a(\"$provide\",\"service\"),value:a(\"$provide\",\"value\"),constant:a(\"$provide\",\"constant\",\"unshift\"),animation:a(\"$animationProvider\",\"register\"),filter:a(\"$filterProvider\",\"register\"),controller:a(\"$controllerProvider\",\"register\"),directive:a(\"$compileProvider\",\n\"directive\"),config:j,run:function(a){c.push(a);return this}};g&&j(g);return m})}})}function Ia(b){return b.replace(uc,function(a,b,d,e){return e?d.toUpperCase():d}).replace(vc,\"Moz$1\")}function db(b,a){function c(){var e;for(var b=[this],c=a,i,f,h,j,m,k;b.length;){i=b.shift();f=0;for(h=i.length;f<h;f++){j=w(i[f]);c?j.triggerHandler(\"$destroy\"):c=!c;m=0;for(e=(k=j.children()).length,j=e;m<j;m++)b.push(ga(k[m]))}}return d.apply(this,arguments)}var d=ga.fn[b],d=d.$original||d;c.$original=d;ga.fn[b]=\nc}function R(b){if(b instanceof R)return b;if(!(this instanceof R)){if(E(b)&&b.charAt(0)!=\"<\")throw Error(\"selectors not implemented\");return new R(b)}if(E(b)){var a=T.createElement(\"div\");a.innerHTML=\"<div>&#160;</div>\"+b;a.removeChild(a.firstChild);eb(this,a.childNodes);this.remove()}else eb(this,b)}function fb(b){return b.cloneNode(!0)}function ya(b){zb(b);for(var a=0,b=b.childNodes||[];a<b.length;a++)ya(b[a])}function Ab(b,a,c){var d=ca(b,\"events\");ca(b,\"handle\")&&(C(a)?n(d,function(a,c){gb(b,\nc,a);delete d[c]}):C(c)?(gb(b,a,d[a]),delete d[a]):ta(d[a],c))}function zb(b){var a=b[Ja],c=Ka[a];c&&(c.handle&&(c.events.$destroy&&c.handle({},\"$destroy\"),Ab(b)),delete Ka[a],b[Ja]=p)}function ca(b,a,c){var d=b[Ja],d=Ka[d||-1];if(B(c))d||(b[Ja]=d=++wc,d=Ka[d]={}),d[a]=c;else return d&&d[a]}function Bb(b,a,c){var d=ca(b,\"data\"),e=B(c),g=!e&&B(a),i=g&&!L(a);!d&&!i&&ca(b,\"data\",d={});if(e)d[a]=c;else if(g)if(i)return d&&d[a];else t(d,a);else return d}function La(b,a){return(\" \"+b.className+\" \").replace(/[\\n\\t]/g,\n\" \").indexOf(\" \"+a+\" \")>-1}function Cb(b,a){a&&n(a.split(\" \"),function(a){b.className=U((\" \"+b.className+\" \").replace(/[\\n\\t]/g,\" \").replace(\" \"+U(a)+\" \",\" \"))})}function Db(b,a){a&&n(a.split(\" \"),function(a){if(!La(b,a))b.className=U(b.className+\" \"+U(a))})}function eb(b,a){if(a)for(var a=!a.nodeName&&B(a.length)&&!sa(a)?a:[a],c=0;c<a.length;c++)b.push(a[c])}function Eb(b,a){return Ma(b,\"$\"+(a||\"ngController\")+\"Controller\")}function Ma(b,a,c){b=w(b);for(b[0].nodeType==9&&(b=b.find(\"html\"));b.length;){if(c=\nb.data(a))return c;b=b.parent()}}function Fb(b,a){var c=Na[a.toLowerCase()];return c&&Gb[b.nodeName]&&c}function xc(b,a){var c=function(c,e){if(!c.preventDefault)c.preventDefault=function(){c.returnValue=!1};if(!c.stopPropagation)c.stopPropagation=function(){c.cancelBubble=!0};if(!c.target)c.target=c.srcElement||T;if(C(c.defaultPrevented)){var g=c.preventDefault;c.preventDefault=function(){c.defaultPrevented=!0;g.call(c)};c.defaultPrevented=!1}c.isDefaultPrevented=function(){return c.defaultPrevented||\nc.returnValue==!1};n(a[e||c.type],function(a){a.call(b,c)});Z<=8?(c.preventDefault=null,c.stopPropagation=null,c.isDefaultPrevented=null):(delete c.preventDefault,delete c.stopPropagation,delete c.isDefaultPrevented)};c.elem=b;return c}function la(b){var a=typeof b,c;if(a==\"object\"&&b!==null)if(typeof(c=b.$$hashKey)==\"function\")c=b.$$hashKey();else{if(c===p)c=b.$$hashKey=Fa()}else c=b;return a+\":\"+c}function za(b){n(b,this.put,this)}function Hb(b){var a,c;if(typeof b==\"function\"){if(!(a=b.$inject))a=\n[],c=b.toString().replace(yc,\"\"),c=c.match(zc),n(c[1].split(Ac),function(b){b.replace(Bc,function(b,c,d){a.push(d)})}),b.$inject=a}else F(b)?(c=b.length-1,xa(b[c],\"fn\"),a=b.slice(0,c)):xa(b,\"fn\",!0);return a}function yb(b){function a(a){return function(b,c){if(L(b))n(b,rb(a));else return a(b,c)}}function c(a,b){if(H(b)||F(b))b=k.instantiate(b);if(!b.$get)throw Error(\"Provider \"+a+\" must define $get factory method.\");return m[a+f]=b}function d(a,b){return c(a,{$get:b})}function e(a){var b=[];n(a,function(a){if(!j.get(a))if(j.put(a,\n!0),E(a)){var c=Aa(a);b=b.concat(e(c.requires)).concat(c._runBlocks);try{for(var d=c._invokeQueue,c=0,f=d.length;c<f;c++){var g=d[c],o=k.get(g[0]);o[g[1]].apply(o,g[2])}}catch(h){throw h.message&&(h.message+=\" from \"+a),h;}}else if(H(a))try{b.push(k.invoke(a))}catch(l){throw l.message&&(l.message+=\" from \"+a),l;}else if(F(a))try{b.push(k.invoke(a))}catch(i){throw i.message&&(i.message+=\" from \"+String(a[a.length-1])),i;}else xa(a,\"module\")});return b}function g(a,b){function c(d){if(typeof d!==\"string\")throw Error(\"Service name expected\");\nif(a.hasOwnProperty(d)){if(a[d]===i)throw Error(\"Circular dependency: \"+h.join(\" <- \"));return a[d]}else try{return h.unshift(d),a[d]=i,a[d]=b(d)}finally{h.shift()}}function d(a,b,e){var f=[],j=Hb(a),g,o,h;o=0;for(g=j.length;o<g;o++)h=j[o],f.push(e&&e.hasOwnProperty(h)?e[h]:c(h));a.$inject||(a=a[g]);switch(b?-1:f.length){case 0:return a();case 1:return a(f[0]);case 2:return a(f[0],f[1]);case 3:return a(f[0],f[1],f[2]);case 4:return a(f[0],f[1],f[2],f[3]);case 5:return a(f[0],f[1],f[2],f[3],f[4]);\ncase 6:return a(f[0],f[1],f[2],f[3],f[4],f[5]);case 7:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6]);case 8:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7]);case 9:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7],f[8]);case 10:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7],f[8],f[9]);default:return a.apply(b,f)}}return{invoke:d,instantiate:function(a,b){var c=function(){},e;c.prototype=(F(a)?a[a.length-1]:a).prototype;c=new c;e=d(a,c,b);return L(e)?e:c},get:c,annotate:Hb,has:function(b){return m.hasOwnProperty(b+\nf)||a.hasOwnProperty(b)}}}var i={},f=\"Provider\",h=[],j=new za,m={$provide:{provider:a(c),factory:a(d),service:a(function(a,b){return d(a,[\"$injector\",function(a){return a.instantiate(b)}])}),value:a(function(a,b){return d(a,S(b))}),constant:a(function(a,b){m[a]=b;l[a]=b}),decorator:function(a,b){var c=k.get(a+f),d=c.$get;c.$get=function(){var a=u.invoke(d,c);return u.invoke(b,null,{$delegate:a})}}}},k=m.$injector=g(m,function(){throw Error(\"Unknown provider: \"+h.join(\" <- \"));}),l={},u=l.$injector=\ng(l,function(a){a=k.get(a+f);return u.invoke(a.$get,a)});n(e(b),function(a){u.invoke(a||q)});return u}function Cc(){var b=!0;this.disableAutoScrolling=function(){b=!1};this.$get=[\"$window\",\"$location\",\"$rootScope\",function(a,c,d){function e(a){var b=null;n(a,function(a){!b&&I(a.nodeName)===\"a\"&&(b=a)});return b}function g(){var b=c.hash(),d;b?(d=i.getElementById(b))?d.scrollIntoView():(d=e(i.getElementsByName(b)))?d.scrollIntoView():b===\"top\"&&a.scrollTo(0,0):a.scrollTo(0,0)}var i=a.document;b&&d.$watch(function(){return c.hash()},\nfunction(){d.$evalAsync(g)});return g}]}function Ib(b){this.register=function(a,c){b.factory(Ia(a)+\"Animation\",c)};this.$get=[\"$injector\",function(a){return function(b){if(b&&(b=Ia(b)+\"Animation\",a.has(b)))return a.get(b)}}]}function Dc(b,a,c,d){function e(a){try{a.apply(null,ka.call(arguments,1))}finally{if(o--,o===0)for(;z.length;)try{z.pop()()}catch(b){c.error(b)}}}function g(a,b){(function s(){n(r,function(a){a()});y=b(s,a)})()}function i(){x!=f.url()&&(x=f.url(),n(v,function(a){a(f.url())}))}\nvar f=this,h=a[0],j=b.location,m=b.history,k=b.setTimeout,l=b.clearTimeout,u={};f.isMock=!1;var o=0,z=[];f.$$completeOutstandingRequest=e;f.$$incOutstandingRequestCount=function(){o++};f.notifyWhenNoOutstandingRequests=function(a){n(r,function(a){a()});o===0?a():z.push(a)};var r=[],y;f.addPollFn=function(a){C(y)&&g(100,k);r.push(a);return a};var x=j.href,W=a.find(\"base\");f.url=function(a,b){if(a){if(x!=a)return x=a,d.history?b?m.replaceState(null,\"\",a):(m.pushState(null,\"\",a),W.attr(\"href\",W.attr(\"href\"))):\nb?j.replace(a):j.href=a,f}else return j.href.replace(/%27/g,\"'\")};var v=[],A=!1;f.onUrlChange=function(a){A||(d.history&&w(b).bind(\"popstate\",i),d.hashchange?w(b).bind(\"hashchange\",i):f.addPollFn(i),A=!0);v.push(a);return a};f.baseHref=function(){var a=W.attr(\"href\");return a?a.replace(/^https?\\:\\/\\/[^\\/]*/,\"\"):\"\"};var G={},D=\"\",$=f.baseHref();f.cookies=function(a,b){var d,e,f,j;if(a)if(b===p)h.cookie=escape(a)+\"=;path=\"+$+\";expires=Thu, 01 Jan 1970 00:00:00 GMT\";else{if(E(b))d=(h.cookie=escape(a)+\n\"=\"+escape(b)+\";path=\"+$).length+1,d>4096&&c.warn(\"Cookie '\"+a+\"' possibly not set or overflowed because it was too large (\"+d+\" > 4096 bytes)!\")}else{if(h.cookie!==D){D=h.cookie;d=D.split(\"; \");G={};for(f=0;f<d.length;f++)e=d[f],j=e.indexOf(\"=\"),j>0&&(a=unescape(e.substring(0,j)),G[a]===p&&(G[a]=unescape(e.substring(j+1))))}return G}};f.defer=function(a,b){var c;o++;c=k(function(){delete u[c];e(a)},b||0);u[c]=!0;return c};f.defer.cancel=function(a){return u[a]?(delete u[a],l(a),e(q),!0):!1}}function Ec(){this.$get=\n[\"$window\",\"$log\",\"$sniffer\",\"$document\",function(b,a,c,d){return new Dc(b,d,a,c)}]}function Fc(){this.$get=function(){function b(b,d){function e(a){if(a!=k){if(l){if(l==a)l=a.n}else l=a;g(a.n,a.p);g(a,k);k=a;k.n=null}}function g(a,b){if(a!=b){if(a)a.p=b;if(b)b.n=a}}if(b in a)throw Error(\"cacheId \"+b+\" taken\");var i=0,f=t({},d,{id:b}),h={},j=d&&d.capacity||Number.MAX_VALUE,m={},k=null,l=null;return a[b]={put:function(a,b){var c=m[a]||(m[a]={key:a});e(c);if(!C(b))return a in h||i++,h[a]=b,i>j&&this.remove(l.key),\nb},get:function(a){var b=m[a];if(b)return e(b),h[a]},remove:function(a){var b=m[a];if(b){if(b==k)k=b.p;if(b==l)l=b.n;g(b.n,b.p);delete m[a];delete h[a];i--}},removeAll:function(){h={};i=0;m={};k=l=null},destroy:function(){m=f=h=null;delete a[b]},info:function(){return t({},f,{size:i})}}}var a={};b.info=function(){var b={};n(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function Gc(){this.$get=[\"$cacheFactory\",function(b){return b(\"templates\")}]}function Jb(b){var a=\n{},c=\"Directive\",d=/^\\s*directive\\:\\s*([\\d\\w\\-_]+)\\s+(.*)$/,e=/(([\\d\\w\\-_]+)(?:\\:([^;]+))?;?)/,g=\"Template must have exactly one root element. was: \",i=/^\\s*(https?|ftp|mailto|file):/;this.directive=function h(d,e){E(d)?(cb(e,\"directive\"),a.hasOwnProperty(d)||(a[d]=[],b.factory(d+c,[\"$injector\",\"$exceptionHandler\",function(b,c){var e=[];n(a[d],function(a){try{var g=b.invoke(a);if(H(g))g={compile:S(g)};else if(!g.compile&&g.link)g.compile=S(g.link);g.priority=g.priority||0;g.name=g.name||d;g.require=\ng.require||g.controller&&g.name;g.restrict=g.restrict||\"A\";e.push(g)}catch(h){c(h)}});return e}])),a[d].push(e)):n(d,rb(h));return this};this.urlSanitizationWhitelist=function(a){return B(a)?(i=a,this):i};this.$get=[\"$injector\",\"$interpolate\",\"$exceptionHandler\",\"$http\",\"$templateCache\",\"$parse\",\"$controller\",\"$rootScope\",\"$document\",function(b,j,m,k,l,u,o,z,r){function y(a,b,c){a instanceof w||(a=w(a));n(a,function(b,c){b.nodeType==3&&b.nodeValue.match(/\\S+/)&&(a[c]=w(b).wrap(\"<span></span>\").parent()[0])});\nvar d=W(a,b,a,c);return function(b,c){cb(b,\"scope\");for(var e=c?Ba.clone.call(a):a,j=0,g=e.length;j<g;j++){var h=e[j];(h.nodeType==1||h.nodeType==9)&&e.eq(j).data(\"$scope\",b)}x(e,\"ng-scope\");c&&c(e,b);d&&d(b,e,e);return e}}function x(a,b){try{a.addClass(b)}catch(c){}}function W(a,b,c,d){function e(a,c,d,g){var h,i,k,l,o,m,u,z=[];o=0;for(m=c.length;o<m;o++)z.push(c[o]);u=o=0;for(m=j.length;o<m;u++)i=z[u],c=j[o++],h=j[o++],c?(c.scope?(k=a.$new(L(c.scope)),w(i).data(\"$scope\",k)):k=a,(l=c.transclude)||\n!g&&b?c(h,k,i,d,function(b){return function(c){var d=a.$new();d.$$transcluded=!0;return b(d,c).bind(\"$destroy\",$a(d,d.$destroy))}}(l||b)):c(h,k,i,p,g)):h&&h(a,i.childNodes,p,g)}for(var j=[],g,h,k,i=0;i<a.length;i++)h=new ma,g=v(a[i],[],h,d),h=(g=g.length?A(g,a[i],h,b,c):null)&&g.terminal||!a[i].childNodes||!a[i].childNodes.length?null:W(a[i].childNodes,g?g.transclude:b),j.push(g),j.push(h),k=k||g||h;return k?e:null}function v(a,b,c,j){var g=c.$attr,h;switch(a.nodeType){case 1:G(b,da(hb(a).toLowerCase()),\n\"E\",j);var i,k,l;h=a.attributes;for(var o=0,m=h&&h.length;o<m;o++)if(i=h[o],i.specified)k=i.name,l=da(k),Y.test(l)&&(k=l.substr(6).toLowerCase()),l=da(k.toLowerCase()),g[l]=k,c[l]=i=U(Z&&k==\"href\"?decodeURIComponent(a.getAttribute(k,2)):i.value),Fb(a,l)&&(c[l]=!0),s(a,b,i,l),G(b,l,\"A\",j);a=a.className;if(E(a)&&a!==\"\")for(;h=e.exec(a);)l=da(h[2]),G(b,l,\"C\",j)&&(c[l]=U(h[3])),a=a.substr(h.index+h[0].length);break;case 3:P(b,a.nodeValue);break;case 8:try{if(h=d.exec(a.nodeValue))l=da(h[1]),G(b,l,\"M\",\nj)&&(c[l]=U(h[2]))}catch(u){}}b.sort(K);return b}function A(a,b,c,d,e){function h(a,b){if(a)a.require=s.require,z.push(a);if(b)b.require=s.require,ea.push(b)}function i(a,b){var c,d=\"data\",e=!1;if(E(a)){for(;(c=a.charAt(0))==\"^\"||c==\"?\";)a=a.substr(1),c==\"^\"&&(d=\"inheritedData\"),e=e||c==\"?\";c=b[d](\"$\"+a+\"Controller\");if(!c&&!e)throw Error(\"No controller: \"+a);}else F(a)&&(c=[],n(a,function(a){c.push(i(a,b))}));return c}function k(a,d,e,g,h){var l,v,r,D,x;l=b===e?c:pc(c,new ma(w(e),c.$attr));v=l.$$element;\nif(K){var y=/^\\s*([@=&])(\\??)\\s*(\\w*)\\s*$/,s=d.$parent||d;n(K.scope,function(a,b){var c=a.match(y)||[],e=c[3]||b,g=c[2]==\"?\",c=c[1],h,k,i;d.$$isolateBindings[b]=c+e;switch(c){case \"@\":l.$observe(e,function(a){d[b]=a});l.$$observers[e].$$scope=s;l[e]&&(d[b]=j(l[e])(s));break;case \"=\":if(g&&!l[e])break;k=u(l[e]);i=k.assign||function(){h=d[b]=k(s);throw Error(Kb+l[e]+\" (directive: \"+K.name+\")\");};h=d[b]=k(s);d.$watch(function(){var a=k(s);a!==d[b]&&(a!==h?h=d[b]=a:i(s,a=h=d[b]));return a});break;case \"&\":k=\nu(l[e]);d[b]=function(a){return k(s,a)};break;default:throw Error(\"Invalid isolate scope definition for directive \"+K.name+\": \"+a);}})}q&&n(q,function(a){var b={$scope:d,$element:v,$attrs:l,$transclude:h};x=a.controller;x==\"@\"&&(x=l[a.name]);v.data(\"$\"+a.name+\"Controller\",o(x,b))});g=0;for(r=z.length;g<r;g++)try{D=z[g],D(d,v,l,D.require&&i(D.require,v))}catch(Hc){m(Hc,va(v))}a&&a(d,e.childNodes,p,h);g=0;for(r=ea.length;g<r;g++)try{D=ea[g],D(d,v,l,D.require&&i(D.require,v))}catch(J){m(J,va(v))}}for(var l=\n-Number.MAX_VALUE,z=[],ea=[],r=null,K=null,W=null,J=c.$$element=w(b),s,A,Y,G,P=d,q,na,t,B=0,C=a.length;B<C;B++){s=a[B];Y=p;if(l>s.priority)break;if(t=s.scope)O(\"isolated scope\",K,s,J),L(t)&&(x(J,\"ng-isolate-scope\"),K=s),x(J,\"ng-scope\"),r=r||s;A=s.name;if(t=s.controller)q=q||{},O(\"'\"+A+\"' controller\",q[A],s,J),q[A]=s;if(t=s.transclude)O(\"transclusion\",G,s,J),G=s,l=s.priority,t==\"element\"?(Y=w(b),J=c.$$element=w(T.createComment(\" \"+A+\": \"+c[A]+\" \")),b=J[0],ja(e,w(Y[0]),b),P=y(Y,d,l)):(Y=w(fb(b)).contents(),\nJ.html(\"\"),P=y(Y,d));if(s.template)if(O(\"template\",W,s,J),W=s,t=H(s.template)?s.template(J,c):s.template,t=Lb(t),s.replace){Y=w(\"<div>\"+U(t)+\"</div>\").contents();b=Y[0];if(Y.length!=1||b.nodeType!==1)throw Error(g+t);ja(e,J,b);A={$attr:{}};a=a.concat(v(b,a.splice(B+1,a.length-(B+1)),A));D(c,A);C=a.length}else J.html(t);if(s.templateUrl)O(\"template\",W,s,J),W=s,k=$(a.splice(B,a.length-B),k,J,c,e,s.replace,P),C=a.length;else if(s.compile)try{na=s.compile(J,c,P),H(na)?h(null,na):na&&h(na.pre,na.post)}catch(I){m(I,\nva(J))}if(s.terminal)k.terminal=!0,l=Math.max(l,s.priority)}k.scope=r&&r.scope;k.transclude=G&&P;return k}function G(d,e,j,g){var l=!1;if(a.hasOwnProperty(e))for(var k,e=b.get(e+c),i=0,o=e.length;i<o;i++)try{if(k=e[i],(g===p||g>k.priority)&&k.restrict.indexOf(j)!=-1)d.push(k),l=!0}catch(u){m(u)}return l}function D(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;n(a,function(d,e){e.charAt(0)!=\"$\"&&(b[e]&&(d+=(e===\"style\"?\";\":\" \")+b[e]),a.$set(e,d,!0,c[e]))});n(b,function(b,j){j==\"class\"?(x(e,b),a[\"class\"]=\n(a[\"class\"]?a[\"class\"]+\" \":\"\")+b):j==\"style\"?e.attr(\"style\",e.attr(\"style\")+\";\"+b):j.charAt(0)!=\"$\"&&!a.hasOwnProperty(j)&&(a[j]=b,d[j]=c[j])})}function $(a,b,c,d,e,j,h){var i=[],o,m,u=c[0],z=a.shift(),r=t({},z,{controller:null,templateUrl:null,transclude:null,scope:null}),z=H(z.templateUrl)?z.templateUrl(c,d):z.templateUrl;c.html(\"\");k.get(z,{cache:l}).success(function(l){var k,z,l=Lb(l);if(j){z=w(\"<div>\"+U(l)+\"</div>\").contents();k=z[0];if(z.length!=1||k.nodeType!==1)throw Error(g+l);l={$attr:{}};\nja(e,c,k);v(k,a,l);D(d,l)}else k=u,c.html(l);a.unshift(r);o=A(a,k,d,h);for(m=W(c[0].childNodes,h);i.length;){var ea=i.shift(),l=i.shift();z=i.shift();var x=i.shift(),y=k;l!==u&&(y=fb(k),ja(z,w(l),y));o(function(){b(m,ea,y,e,x)},ea,y,e,x)}i=null}).error(function(a,b,c,d){throw Error(\"Failed to load template: \"+d.url);});return function(a,c,d,e,j){i?(i.push(c),i.push(d),i.push(e),i.push(j)):o(function(){b(m,c,d,e,j)},c,d,e,j)}}function K(a,b){return b.priority-a.priority}function O(a,b,c,d){if(b)throw Error(\"Multiple directives [\"+\nb.name+\", \"+c.name+\"] asking for \"+a+\" on: \"+va(d));}function P(a,b){var c=j(b,!0);c&&a.push({priority:0,compile:S(function(a,b){var d=b.parent(),e=d.data(\"$binding\")||[];e.push(c);x(d.data(\"$binding\",e),\"ng-binding\");a.$watch(c,function(a){b[0].nodeValue=a})})})}function s(a,b,c,d){var e=j(c,!0);e&&b.push({priority:100,compile:S(function(a,b,c){b=c.$$observers||(c.$$observers={});if(e=j(c[d],!0))c[d]=e(a),(b[d]||(b[d]=[])).$$inter=!0,(c.$$observers&&c.$$observers[d].$$scope||a).$watch(e,function(a){c.$set(d,\na)})})})}function ja(a,b,c){var d=b[0],e=d.parentNode,j,g;if(a){j=0;for(g=a.length;j<g;j++)if(a[j]==d){a[j]=c;break}}e&&e.replaceChild(c,d);c[w.expando]=d[w.expando];b[0]=c}var ma=function(a,b){this.$$element=a;this.$attr=b||{}};ma.prototype={$normalize:da,$set:function(a,b,c,d){var e=Fb(this.$$element[0],a),j=this.$$observers;e&&(this.$$element.prop(a,b),d=e);this[a]=b;d?this.$attr[a]=d:(d=this.$attr[a])||(this.$attr[a]=d=bb(a,\"-\"));if(hb(this.$$element[0])===\"A\"&&a===\"href\")q.setAttribute(\"href\",\nb),e=q.href,e.match(i)||(this[a]=b=\"unsafe:\"+e);c!==!1&&(b===null||b===p?this.$$element.removeAttr(d):this.$$element.attr(d,b));j&&n(j[a],function(a){try{a(b)}catch(c){m(c)}})},$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers={}),e=d[a]||(d[a]=[]);e.push(b);z.$evalAsync(function(){e.$$inter||b(c[a])});return b}};var q=r[0].createElement(\"a\"),ea=j.startSymbol(),J=j.endSymbol(),Lb=ea==\"{{\"||J==\"}}\"?qa:function(a){return a.replace(/\\{\\{/g,ea).replace(/}}/g,J)},Y=/^ngAttr[A-Z]/;return y}]}\nfunction da(b){return Ia(b.replace(Ic,\"\"))}function Jc(){var b={},a=/^(\\S+)(\\s+as\\s+(\\w+))?$/;this.register=function(a,d){L(a)?t(b,a):b[a]=d};this.$get=[\"$injector\",\"$window\",function(c,d){return function(e,g){var i,f;E(e)&&(f=e.match(a),i=f[1],f=f[3],e=b.hasOwnProperty(i)?b[i]:ib(g.$scope,i,!0)||ib(d,i,!0),xa(e,i,!0));i=c.instantiate(e,g);if(f){if(typeof g.$scope!==\"object\")throw Error('Can not export controller as \"'+f+'\". No scope object provided!');g.$scope[f]=i}return i}}]}function Kc(){this.$get=\n[\"$window\",function(b){return w(b.document)}]}function Lc(){this.$get=[\"$log\",function(b){return function(a,c){b.error.apply(b,arguments)}}]}function Mc(){var b=\"{{\",a=\"}}\";this.startSymbol=function(a){return a?(b=a,this):b};this.endSymbol=function(b){return b?(a=b,this):a};this.$get=[\"$parse\",\"$exceptionHandler\",function(c,d){function e(e,h){for(var j,m,k=0,l=[],u=e.length,o=!1,z=[];k<u;)(j=e.indexOf(b,k))!=-1&&(m=e.indexOf(a,j+g))!=-1?(k!=j&&l.push(e.substring(k,j)),l.push(k=c(o=e.substring(j+g,\nm))),k.exp=o,k=m+i,o=!0):(k!=u&&l.push(e.substring(k)),k=u);if(!(u=l.length))l.push(\"\"),u=1;if(!h||o)return z.length=u,k=function(a){try{for(var b=0,c=u,j;b<c;b++){if(typeof(j=l[b])==\"function\")j=j(a),j==null||j==p?j=\"\":typeof j!=\"string\"&&(j=ha(j));z[b]=j}return z.join(\"\")}catch(g){d(Error(\"Error while interpolating: \"+e+\"\\n\"+g.toString()))}},k.exp=e,k.parts=l,k}var g=b.length,i=a.length;e.startSymbol=function(){return b};e.endSymbol=function(){return a};return e}]}function Mb(b){for(var b=b.split(\"/\"),\na=b.length;a--;)b[a]=ab(b[a]);return b.join(\"/\")}function Nb(b,a){var c=jb.exec(b);a.$$protocol=c[1];a.$$host=c[3];a.$$port=N(c[5])||Oa[c[1]]||null}function Ob(b,a){var c=Pb.exec(b);a.$$path=decodeURIComponent(c[1]);a.$$search=vb(c[3]);a.$$hash=decodeURIComponent(c[5]||\"\");if(a.$$path&&a.$$path.charAt(0)!=\"/\")a.$$path=\"/\"+a.$$path}function fa(b,a,c){return a.indexOf(b)==0?a.substr(b.length):c}function Ca(b){var a=b.indexOf(\"#\");return a==-1?b:b.substr(0,a)}function kb(b){return b.substr(0,Ca(b).lastIndexOf(\"/\")+\n1)}function Qb(b,a){var a=a||\"\",c=kb(b);this.$$parse=function(a){var b={};Nb(a,b);var g=fa(c,a);if(!E(g))throw Error('Invalid url \"'+a+'\", missing path prefix \"'+c+'\".');Ob(g,b);t(this,b);if(!this.$$path)this.$$path=\"/\";this.$$compose()};this.$$compose=function(){var a=wb(this.$$search),b=this.$$hash?\"#\"+ab(this.$$hash):\"\";this.$$url=Mb(this.$$path)+(a?\"?\"+a:\"\")+b;this.$$absUrl=c+this.$$url.substr(1)};this.$$rewrite=function(d){var e;if((e=fa(b,d))!==p)return d=e,(e=fa(a,e))!==p?c+(fa(\"/\",e)||e):\nb+d;else if((e=fa(c,d))!==p)return c+e;else if(c==d+\"/\")return c}}function lb(b,a){var c=kb(b);this.$$parse=function(d){Nb(d,this);var e=fa(b,d)||fa(c,d);if(!E(e))throw Error('Invalid url \"'+d+'\", does not start with \"'+b+'\".');e=e.charAt(0)==\"#\"?fa(a,e):e;if(!E(e))throw Error('Invalid url \"'+d+'\", missing hash prefix \"'+a+'\".');Ob(e,this);this.$$compose()};this.$$compose=function(){var c=wb(this.$$search),e=this.$$hash?\"#\"+ab(this.$$hash):\"\";this.$$url=Mb(this.$$path)+(c?\"?\"+c:\"\")+e;this.$$absUrl=\nb+(this.$$url?a+this.$$url:\"\")};this.$$rewrite=function(a){if(Ca(b)==Ca(a))return a}}function Rb(b,a){lb.apply(this,arguments);var c=kb(b);this.$$rewrite=function(d){var e;if(b==Ca(d))return d;else if(e=fa(c,d))return b+a+e;else if(c===d+\"/\")return c}}function Pa(b){return function(){return this[b]}}function Sb(b,a){return function(c){if(C(c))return this[b];this[b]=a(c);this.$$compose();return this}}function Nc(){var b=\"\",a=!1;this.hashPrefix=function(a){return B(a)?(b=a,this):b};this.html5Mode=function(b){return B(b)?\n(a=b,this):a};this.$get=[\"$rootScope\",\"$browser\",\"$sniffer\",\"$rootElement\",function(c,d,e,g){function i(a){c.$broadcast(\"$locationChangeSuccess\",f.absUrl(),a)}var f,h=d.baseHref(),j=d.url();a?(h=h?j.substring(0,j.indexOf(\"/\",j.indexOf(\"//\")+2))+h:j,e=e.history?Qb:Rb):(h=Ca(j),e=lb);f=new e(h,\"#\"+b);f.$$parse(f.$$rewrite(j));g.bind(\"click\",function(a){if(!a.ctrlKey&&!(a.metaKey||a.which==2)){for(var b=w(a.target);I(b[0].nodeName)!==\"a\";)if(b[0]===g[0]||!(b=b.parent())[0])return;var e=b.prop(\"href\"),\nj=f.$$rewrite(e);e&&!b.attr(\"target\")&&j&&!a.isDefaultPrevented()&&(a.preventDefault(),j!=d.url()&&(f.$$parse(j),c.$apply(),M.angular[\"ff-684208-preventDefault\"]=!0))}});f.absUrl()!=j&&d.url(f.absUrl(),!0);d.onUrlChange(function(a){f.absUrl()!=a&&(c.$broadcast(\"$locationChangeStart\",a,f.absUrl()).defaultPrevented?d.url(f.absUrl()):(c.$evalAsync(function(){var b=f.absUrl();f.$$parse(a);i(b)}),c.$$phase||c.$digest()))});var m=0;c.$watch(function(){var a=d.url(),b=f.$$replace;if(!m||a!=f.absUrl())m++,\nc.$evalAsync(function(){c.$broadcast(\"$locationChangeStart\",f.absUrl(),a).defaultPrevented?f.$$parse(a):(d.url(f.absUrl(),b),i(a))});f.$$replace=!1;return m});return f}]}function Oc(){var b=!0,a=this;this.debugEnabled=function(a){return B(a)?(b=a,this):b};this.$get=[\"$window\",function(c){function d(a){a instanceof Error&&(a.stack?a=a.message&&a.stack.indexOf(a.message)===-1?\"Error: \"+a.message+\"\\n\"+a.stack:a.stack:a.sourceURL&&(a=a.message+\"\\n\"+a.sourceURL+\":\"+a.line));return a}function e(a){var b=\nc.console||{},e=b[a]||b.log||q;return e.apply?function(){var a=[];n(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,b)}}return{log:e(\"log\"),warn:e(\"warn\"),info:e(\"info\"),error:e(\"error\"),debug:function(){var c=e(\"debug\");return function(){b&&c.apply(a,arguments)}}()}}]}function Pc(b,a){function c(a){return a.indexOf(r)!=-1}function d(a){a=a||1;return o+a<b.length?b.charAt(o+a):!1}function e(a){return\"0\"<=a&&a<=\"9\"}function g(a){return a==\" \"||a==\"\\r\"||a==\"\\t\"||a==\"\\n\"||\na==\"\\u000b\"||a==\"\\u00a0\"}function i(a){return\"a\"<=a&&a<=\"z\"||\"A\"<=a&&a<=\"Z\"||\"_\"==a||a==\"$\"}function f(a){return a==\"-\"||a==\"+\"||e(a)}function h(a,c,d){d=d||o;throw Error(\"Lexer Error: \"+a+\" at column\"+(B(c)?\"s \"+c+\"-\"+o+\" [\"+b.substring(c,d)+\"]\":\" \"+d)+\" in expression [\"+b+\"].\");}function j(){for(var a=\"\",c=o;o<b.length;){var j=I(b.charAt(o));if(j==\".\"||e(j))a+=j;else{var g=d();if(j==\"e\"&&f(g))a+=j;else if(f(j)&&g&&e(g)&&a.charAt(a.length-1)==\"e\")a+=j;else if(f(j)&&(!g||!e(g))&&a.charAt(a.length-\n1)==\"e\")h(\"Invalid exponent\");else break}o++}a*=1;l.push({index:c,text:a,json:!0,fn:function(){return a}})}function m(){for(var c=\"\",d=o,f,j,h,k;o<b.length;){k=b.charAt(o);if(k==\".\"||i(k)||e(k))k==\".\"&&(f=o),c+=k;else break;o++}if(f)for(j=o;j<b.length;){k=b.charAt(j);if(k==\"(\"){h=c.substr(f-d+1);c=c.substr(0,f-d);o=j;break}if(g(k))j++;else break}d={index:d,text:c};if(Da.hasOwnProperty(c))d.fn=d.json=Da[c];else{var m=Tb(c,a);d.fn=t(function(a,b){return m(a,b)},{assign:function(a,b){return Ub(a,c,b)}})}l.push(d);\nh&&(l.push({index:f,text:\".\",json:!1}),l.push({index:f+1,text:h,json:!1}))}function k(a){var c=o;o++;for(var d=\"\",e=a,f=!1;o<b.length;){var j=b.charAt(o);e+=j;if(f)j==\"u\"?(j=b.substring(o+1,o+5),j.match(/[\\da-f]{4}/i)||h(\"Invalid unicode escape [\\\\u\"+j+\"]\"),o+=4,d+=String.fromCharCode(parseInt(j,16))):(f=Qc[j],d+=f?f:j),f=!1;else if(j==\"\\\\\")f=!0;else if(j==a){o++;l.push({index:c,text:e,string:d,json:!0,fn:function(){return d}});return}else d+=j;o++}h(\"Unterminated quote\",c)}for(var l=[],u,o=0,z=[],\nr,y=\":\";o<b.length;){r=b.charAt(o);if(c(\"\\\"'\"))k(r);else if(e(r)||c(\".\")&&e(d()))j();else if(i(r)){if(m(),\"{,\".indexOf(y)!=-1&&z[0]==\"{\"&&(u=l[l.length-1]))u.json=u.text.indexOf(\".\")==-1}else if(c(\"(){}[].,;:?\"))l.push({index:o,text:r,json:\":[,\".indexOf(y)!=-1&&c(\"{[\")||c(\"}]:,\")}),c(\"{[\")&&z.unshift(r),c(\"}]\")&&z.shift(),o++;else if(g(r)){o++;continue}else{var x=r+d(),n=x+d(2),v=Da[r],A=Da[x],G=Da[n];G?(l.push({index:o,text:n,fn:G}),o+=3):A?(l.push({index:o,text:x,fn:A}),o+=2):v?(l.push({index:o,\ntext:r,fn:v,json:\"[,:\".indexOf(y)!=-1&&c(\"+-\")}),o+=1):h(\"Unexpected next character \",o,o+1)}y=r}return l}function Rc(b,a,c,d){function e(a,c){throw Error(\"Syntax Error: Token '\"+c.text+\"' \"+a+\" at column \"+(c.index+1)+\" of the expression [\"+b+\"] starting at [\"+b.substring(c.index)+\"].\");}function g(){if(O.length===0)throw Error(\"Unexpected end of expression: \"+b);return O[0]}function i(a,b,c,d){if(O.length>0){var e=O[0],f=e.text;if(f==a||f==b||f==c||f==d||!a&&!b&&!c&&!d)return e}return!1}function f(b,\nc,d,f){return(b=i(b,c,d,f))?(a&&!b.json&&e(\"is not valid json\",b),O.shift(),b):!1}function h(a){f(a)||e(\"is unexpected, expecting [\"+a+\"]\",i())}function j(a,b){return t(function(c,d){return a(c,d,b)},{constant:b.constant})}function m(a,b,c){return t(function(d,e){return a(d,e)?b(d,e):c(d,e)},{constant:a.constant&&b.constant&&c.constant})}function k(a,b,c){return t(function(d,e){return b(d,e,a,c)},{constant:a.constant&&c.constant})}function l(){for(var a=[];;)if(O.length>0&&!i(\"}\",\")\",\";\",\"]\")&&a.push(w()),\n!f(\";\"))return a.length==1?a[0]:function(b,c){for(var d,e=0;e<a.length;e++){var f=a[e];f&&(d=f(b,c))}return d}}function u(){for(var a=f(),b=c(a.text),d=[];;)if(a=f(\":\"))d.push(P());else{var e=function(a,c,e){for(var e=[e],f=0;f<d.length;f++)e.push(d[f](a,c));return b.apply(a,e)};return function(){return e}}}function o(){var a=z(),b,c;if(f(\"?\"))if(b=o(),c=f(\":\"))return m(a,b,o());else e(\"expected :\",c);else return a}function z(){for(var a=r(),b;;)if(b=f(\"||\"))a=k(a,b.fn,r());else return a}function r(){var a=\ny(),b;if(b=f(\"&&\"))a=k(a,b.fn,r());return a}function y(){var a=x(),b;if(b=f(\"==\",\"!=\",\"===\",\"!==\"))a=k(a,b.fn,y());return a}function x(){var a;a=n();for(var b;b=f(\"+\",\"-\");)a=k(a,b.fn,n());if(b=f(\"<\",\">\",\"<=\",\">=\"))a=k(a,b.fn,x());return a}function n(){for(var a=v(),b;b=f(\"*\",\"/\",\"%\");)a=k(a,b.fn,v());return a}function v(){var a;return f(\"+\")?A():(a=f(\"-\"))?k($,a.fn,v()):(a=f(\"!\"))?j(a.fn,v()):A()}function A(){var a;if(f(\"(\"))a=w(),h(\")\");else if(f(\"[\"))a=G();else if(f(\"{\"))a=D();else{var b=f();(a=\nb.fn)||e(\"not a primary expression\",b);if(b.json)a.constant=a.literal=!0}for(var c;b=f(\"(\",\"[\",\".\");)b.text===\"(\"?(a=s(a,c),c=null):b.text===\"[\"?(c=a,a=ma(a)):b.text===\".\"?(c=a,a=ja(a)):e(\"IMPOSSIBLE\");return a}function G(){var a=[],b=!0;if(g().text!=\"]\"){do{var c=P();a.push(c);c.constant||(b=!1)}while(f(\",\"))}h(\"]\");return t(function(b,c){for(var d=[],e=0;e<a.length;e++)d.push(a[e](b,c));return d},{literal:!0,constant:b})}function D(){var a=[],b=!0;if(g().text!=\"}\"){do{var c=f(),c=c.string||c.text;\nh(\":\");var d=P();a.push({key:c,value:d});d.constant||(b=!1)}while(f(\",\"))}h(\"}\");return t(function(b,c){for(var d={},e=0;e<a.length;e++){var f=a[e];d[f.key]=f.value(b,c)}return d},{literal:!0,constant:b})}var $=S(0),K,O=Pc(b,d),P=function(){var a=o(),c,d;return(d=f(\"=\"))?(a.assign||e(\"implies assignment but [\"+b.substring(0,d.index)+\"] can not be assigned to\",d),c=o(),function(b,d){return a.assign(b,c(b,d),d)}):a},s=function(a,b){var c=[];if(g().text!=\")\"){do c.push(P());while(f(\",\"))}h(\")\");return function(d,\ne){for(var f=[],j=b?b(d,e):d,g=0;g<c.length;g++)f.push(c[g](d,e));g=a(d,e,j)||q;return g.apply?g.apply(j,f):g(f[0],f[1],f[2],f[3],f[4])}},ja=function(a){var b=f().text,c=Tb(b,d);return t(function(b,d,e){return c(e||a(b,d),d)},{assign:function(c,d,e){return Ub(a(c,e),b,d)}})},ma=function(a){var b=P();h(\"]\");return t(function(c,d){var e=a(c,d),f=b(c,d),j;if(!e)return p;if((e=e[f])&&e.then){j=e;if(!(\"$$v\"in e))j.$$v=p,j.then(function(a){j.$$v=a});e=e.$$v}return e},{assign:function(c,d,e){return a(c,\ne)[b(c,e)]=d}})},w=function(){for(var a=P(),b;;)if(b=f(\"|\"))a=k(a,b.fn,u());else return a};a?(P=z,s=ja=ma=w=function(){e(\"is not valid json\",{text:b,index:0})},K=A()):K=l();O.length!==0&&e(\"is an unexpected token\",O[0]);K.literal=!!K.literal;K.constant=!!K.constant;return K}function Ub(b,a,c){for(var a=a.split(\".\"),d=0;a.length>1;d++){var e=a.shift(),g=b[e];g||(g={},b[e]=g);b=g}return b[a.shift()]=c}function ib(b,a,c){if(!a)return b;for(var a=a.split(\".\"),d,e=b,g=a.length,i=0;i<g;i++)d=a[i],b&&(b=\n(e=b)[d]);return!c&&H(b)?$a(e,b):b}function Vb(b,a,c,d,e){return function(g,i){var f=i&&i.hasOwnProperty(b)?i:g,h;if(f===null||f===p)return f;if((f=f[b])&&f.then){if(!(\"$$v\"in f))h=f,h.$$v=p,h.then(function(a){h.$$v=a});f=f.$$v}if(!a||f===null||f===p)return f;if((f=f[a])&&f.then){if(!(\"$$v\"in f))h=f,h.$$v=p,h.then(function(a){h.$$v=a});f=f.$$v}if(!c||f===null||f===p)return f;if((f=f[c])&&f.then){if(!(\"$$v\"in f))h=f,h.$$v=p,h.then(function(a){h.$$v=a});f=f.$$v}if(!d||f===null||f===p)return f;if((f=\nf[d])&&f.then){if(!(\"$$v\"in f))h=f,h.$$v=p,h.then(function(a){h.$$v=a});f=f.$$v}if(!e||f===null||f===p)return f;if((f=f[e])&&f.then){if(!(\"$$v\"in f))h=f,h.$$v=p,h.then(function(a){h.$$v=a});f=f.$$v}return f}}function Tb(b,a){if(mb.hasOwnProperty(b))return mb[b];var c=b.split(\".\"),d=c.length,e;if(a)e=d<6?Vb(c[0],c[1],c[2],c[3],c[4]):function(a,b){var e=0,j;do j=Vb(c[e++],c[e++],c[e++],c[e++],c[e++])(a,b),b=p,a=j;while(e<d);return j};else{var g=\"var l, fn, p;\\n\";n(c,function(a,b){g+=\"if(s === null || s === undefined) return s;\\nl=s;\\ns=\"+\n(b?\"s\":'((k&&k.hasOwnProperty(\"'+a+'\"))?k:s)')+'[\"'+a+'\"];\\nif (s && s.then) {\\n if (!(\"$$v\" in s)) {\\n p=s;\\n p.$$v = undefined;\\n p.then(function(v) {p.$$v=v;});\\n}\\n s=s.$$v\\n}\\n'});g+=\"return s;\";e=Function(\"s\",\"k\",g);e.toString=function(){return g}}return mb[b]=e}function Sc(){var b={};this.$get=[\"$filter\",\"$sniffer\",function(a,c){return function(d){switch(typeof d){case \"string\":return b.hasOwnProperty(d)?b[d]:b[d]=Rc(d,!1,a,c.csp);case \"function\":return d;default:return q}}}]}function Tc(){this.$get=\n[\"$rootScope\",\"$exceptionHandler\",function(b,a){return Uc(function(a){b.$evalAsync(a)},a)}]}function Uc(b,a){function c(a){return a}function d(a){return i(a)}var e=function(){var f=[],h,j;return j={resolve:function(a){if(f){var c=f;f=p;h=g(a);c.length&&b(function(){for(var a,b=0,d=c.length;b<d;b++)a=c[b],h.then(a[0],a[1])})}},reject:function(a){j.resolve(i(a))},promise:{then:function(b,j){var g=e(),i=function(d){try{g.resolve((b||c)(d))}catch(e){a(e),g.reject(e)}},o=function(b){try{g.resolve((j||\nd)(b))}catch(c){a(c),g.reject(c)}};f?f.push([i,o]):h.then(i,o);return g.promise},always:function(a){function b(a,c){var d=e();c?d.resolve(a):d.reject(a);return d.promise}function d(e,f){var j=null;try{j=(a||c)()}catch(g){return b(g,!1)}return j&&j.then?j.then(function(){return b(e,f)},function(a){return b(a,!1)}):b(e,f)}return this.then(function(a){return d(a,!0)},function(a){return d(a,!1)})}}}},g=function(a){return a&&a.then?a:{then:function(c){var d=e();b(function(){d.resolve(c(a))});return d.promise}}},\ni=function(a){return{then:function(c,j){var g=e();b(function(){g.resolve((j||d)(a))});return g.promise}}};return{defer:e,reject:i,when:function(f,h,j){var m=e(),k,l=function(b){try{return(h||c)(b)}catch(d){return a(d),i(d)}},u=function(b){try{return(j||d)(b)}catch(c){return a(c),i(c)}};b(function(){g(f).then(function(a){k||(k=!0,m.resolve(g(a).then(l,u)))},function(a){k||(k=!0,m.resolve(u(a)))})});return m.promise},all:function(a){var b=e(),c=0,d=F(a)?[]:{};n(a,function(a,e){c++;g(a).then(function(a){d.hasOwnProperty(e)||\n(d[e]=a,--c||b.resolve(d))},function(a){d.hasOwnProperty(e)||b.reject(a)})});c===0&&b.resolve(d);return b.promise}}}function Vc(){var b={};this.when=function(a,c){b[a]=t({reloadOnSearch:!0,caseInsensitiveMatch:!1},c);if(a){var d=a[a.length-1]==\"/\"?a.substr(0,a.length-1):a+\"/\";b[d]={redirectTo:a}}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=[\"$rootScope\",\"$location\",\"$routeParams\",\"$q\",\"$injector\",\"$http\",\"$templateCache\",function(a,c,d,e,g,i,f){function h(a,b,c){for(var b=\n\"^\"+b.replace(/[-\\/\\\\^$:*+?.()|[\\]{}]/g,\"\\\\$&\")+\"$\",d=\"\",e=[],f={},j=/\\\\([:*])(\\w+)/g,g,i=0;(g=j.exec(b))!==null;){d+=b.slice(i,g.index);switch(g[1]){case \":\":d+=\"([^\\\\/]*)\";break;case \"*\":d+=\"(.*)\"}e.push(g[2]);i=j.lastIndex}d+=b.substr(i);var h=a.match(RegExp(d,c.caseInsensitiveMatch?\"i\":\"\"));h&&n(e,function(a,b){f[a]=h[b+1]});return h?f:null}function j(){var b=m(),j=u.current;if(b&&j&&b.$$route===j.$$route&&ia(b.pathParams,j.pathParams)&&!b.reloadOnSearch&&!l)j.params=b.params,V(j.params,d),a.$broadcast(\"$routeUpdate\",\nj);else if(b||j)l=!1,a.$broadcast(\"$routeChangeStart\",b,j),(u.current=b)&&b.redirectTo&&(E(b.redirectTo)?c.path(k(b.redirectTo,b.params)).search(b.params).replace():c.url(b.redirectTo(b.pathParams,c.path(),c.search())).replace()),e.when(b).then(function(){if(b){var a=t({},b.resolve),c;n(a,function(b,c){a[c]=E(b)?g.get(b):g.invoke(b)});if(B(c=b.template))H(c)&&(c=c(b.params));else if(B(c=b.templateUrl))if(H(c)&&(c=c(b.params)),B(c))b.loadedTemplateUrl=c,c=i.get(c,{cache:f}).then(function(a){return a.data});\nB(c)&&(a.$template=c);return e.all(a)}}).then(function(c){if(b==u.current){if(b)b.locals=c,V(b.params,d);a.$broadcast(\"$routeChangeSuccess\",b,j)}},function(c){b==u.current&&a.$broadcast(\"$routeChangeError\",b,j,c)})}function m(){var a,d;n(b,function(b,e){if(!d&&(a=h(c.path(),e,b)))d=tb(b,{params:t({},c.search(),a),pathParams:a}),d.$$route=b});return d||b[null]&&tb(b[null],{params:{},pathParams:{}})}function k(a,b){var c=[];n((a||\"\").split(\":\"),function(a,d){if(d==0)c.push(a);else{var e=a.match(/(\\w+)(.*)/),\nf=e[1];c.push(b[f]);c.push(e[2]||\"\");delete b[f]}});return c.join(\"\")}var l=!1,u={routes:b,reload:function(){l=!0;a.$evalAsync(j)}};a.$on(\"$locationChangeSuccess\",j);return u}]}function Wc(){this.$get=S({})}function Xc(){var b=10;this.digestTtl=function(a){arguments.length&&(b=a);return b};this.$get=[\"$injector\",\"$exceptionHandler\",\"$parse\",function(a,c,d){function e(){this.$id=Fa();this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;\nthis[\"this\"]=this.$root=this;this.$$destroyed=!1;this.$$asyncQueue=[];this.$$listeners={};this.$$isolateBindings={}}function g(a){if(h.$$phase)throw Error(h.$$phase+\" already in progress\");h.$$phase=a}function i(a,b){var c=d(a);xa(c,b);return c}function f(){}e.prototype={$new:function(a){if(H(a))throw Error(\"API-CHANGE: Use $controller to instantiate controllers.\");a?(a=new e,a.$root=this.$root):(a=function(){},a.prototype=this,a=new a,a.$id=Fa());a[\"this\"]=a;a.$$listeners={};a.$parent=this;a.$$watchers=\na.$$nextSibling=a.$$childHead=a.$$childTail=null;a.$$prevSibling=this.$$childTail;this.$$childHead?this.$$childTail=this.$$childTail.$$nextSibling=a:this.$$childHead=this.$$childTail=a;return a},$watch:function(a,b,c){var d=i(a,\"watch\"),e=this.$$watchers,g={fn:b,last:f,get:d,exp:a,eq:!!c};if(!H(b)){var h=i(b||q,\"listener\");g.fn=function(a,b,c){h(c)}}if(typeof a==\"string\"&&d.constant){var r=g.fn;g.fn=function(a,b,c){r.call(this,a,b,c);ta(e,g)}}if(!e)e=this.$$watchers=[];e.unshift(g);return function(){ta(e,\ng)}},$watchCollection:function(a,b){var c=this,e,f,g=0,i=d(a),h=[],n={},x=0;return this.$watch(function(){f=i(c);var a,b;if(L(f))if(Xa(f)){if(e!==h)e=h,x=e.length=0,g++;a=f.length;if(x!==a)g++,e.length=x=a;for(b=0;b<a;b++)e[b]!==f[b]&&(g++,e[b]=f[b])}else{e!==n&&(e=n={},x=0,g++);a=0;for(b in f)f.hasOwnProperty(b)&&(a++,e.hasOwnProperty(b)?e[b]!==f[b]&&(g++,e[b]=f[b]):(x++,e[b]=f[b],g++));if(x>a)for(b in g++,e)e.hasOwnProperty(b)&&!f.hasOwnProperty(b)&&(x--,delete e[b])}else e!==f&&(e=f,g++);return g},\nfunction(){b(f,e,c)})},$digest:function(){var a,d,e,i,u=this.$$asyncQueue,o,z,r=b,n,x=[],p,v;g(\"$digest\");do{z=!1;for(n=this;u.length;)try{n.$eval(u.shift())}catch(A){c(A)}do{if(i=n.$$watchers)for(o=i.length;o--;)try{if(a=i[o],(d=a.get(n))!==(e=a.last)&&!(a.eq?ia(d,e):typeof d==\"number\"&&typeof e==\"number\"&&isNaN(d)&&isNaN(e)))z=!0,a.last=a.eq?V(d):d,a.fn(d,e===f?d:e,n),r<5&&(p=4-r,x[p]||(x[p]=[]),v=H(a.exp)?\"fn: \"+(a.exp.name||a.exp.toString()):a.exp,v+=\"; newVal: \"+ha(d)+\"; oldVal: \"+ha(e),x[p].push(v))}catch(G){c(G)}if(!(i=\nn.$$childHead||n!==this&&n.$$nextSibling))for(;n!==this&&!(i=n.$$nextSibling);)n=n.$parent}while(n=i);if(z&&!r--)throw h.$$phase=null,Error(b+\" $digest() iterations reached. Aborting!\\nWatchers fired in the last 5 iterations: \"+ha(x));}while(z||u.length);h.$$phase=null},$destroy:function(){if(!(h==this||this.$$destroyed)){var a=this.$parent;this.$broadcast(\"$destroy\");this.$$destroyed=!0;if(a.$$childHead==this)a.$$childHead=this.$$nextSibling;if(a.$$childTail==this)a.$$childTail=this.$$prevSibling;\nif(this.$$prevSibling)this.$$prevSibling.$$nextSibling=this.$$nextSibling;if(this.$$nextSibling)this.$$nextSibling.$$prevSibling=this.$$prevSibling;this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null}},$eval:function(a,b){return d(a)(this,b)},$evalAsync:function(a){this.$$asyncQueue.push(a)},$apply:function(a){try{return g(\"$apply\"),this.$eval(a)}catch(b){c(b)}finally{h.$$phase=null;try{h.$digest()}catch(d){throw c(d),d;}}},$on:function(a,b){var c=this.$$listeners[a];\nc||(this.$$listeners[a]=c=[]);c.push(b);return function(){c[Ga(c,b)]=null}},$emit:function(a,b){var d=[],e,f=this,g=!1,i={name:a,targetScope:f,stopPropagation:function(){g=!0},preventDefault:function(){i.defaultPrevented=!0},defaultPrevented:!1},h=[i].concat(ka.call(arguments,1)),n,x;do{e=f.$$listeners[a]||d;i.currentScope=f;n=0;for(x=e.length;n<x;n++)if(e[n])try{if(e[n].apply(null,h),g)return i}catch(p){c(p)}else e.splice(n,1),n--,x--;f=f.$parent}while(f);return i},$broadcast:function(a,b){var d=\nthis,e=this,f={name:a,targetScope:this,preventDefault:function(){f.defaultPrevented=!0},defaultPrevented:!1},g=[f].concat(ka.call(arguments,1)),i,h;do{d=e;f.currentScope=d;e=d.$$listeners[a]||[];i=0;for(h=e.length;i<h;i++)if(e[i])try{e[i].apply(null,g)}catch(n){c(n)}else e.splice(i,1),i--,h--;if(!(e=d.$$childHead||d!==this&&d.$$nextSibling))for(;d!==this&&!(e=d.$$nextSibling);)d=d.$parent}while(d=e);return f}};var h=new e;return h}]}function Yc(){this.$get=[\"$window\",\"$document\",function(b,a){var c=\n{},d=N((/android (\\d+)/.exec(I((b.navigator||{}).userAgent))||[])[1]),e=a[0]||{},g,i=/^(Moz|webkit|O|ms)(?=[A-Z])/,f=e.body&&e.body.style,h=!1,j=!1;if(f){for(var m in f)if(h=i.exec(m)){g=h[0];g=g.substr(0,1).toUpperCase()+g.substr(1);break}h=!!(\"transition\"in f||g+\"Transition\"in f);j=!!(\"animation\"in f||g+\"Animation\"in f)}return{history:!(!b.history||!b.history.pushState||d<4),hashchange:\"onhashchange\"in b&&(!e.documentMode||e.documentMode>7),hasEvent:function(a){if(a==\"input\"&&Z==9)return!1;if(C(c[a])){var b=\ne.createElement(\"div\");c[a]=\"on\"+a in b}return c[a]},csp:e.securityPolicy?e.securityPolicy.isActive:!1,vendorPrefix:g,transitions:h,animations:j}}]}function Zc(){this.$get=S(M)}function Wb(b){var a={},c,d,e;if(!b)return a;n(b.split(\"\\n\"),function(b){e=b.indexOf(\":\");c=I(U(b.substr(0,e)));d=U(b.substr(e+1));c&&(a[c]?a[c]+=\", \"+d:a[c]=d)});return a}function $c(b,a){var c=ad.exec(b);if(c==null)return!0;var d={protocol:c[2],host:c[4],port:N(c[6])||Oa[c[2]]||null,relativeProtocol:c[2]===p||c[2]===\"\"},\nc=jb.exec(a),c={protocol:c[1],host:c[3],port:N(c[5])||Oa[c[1]]||null};return(d.protocol==c.protocol||d.relativeProtocol)&&d.host==c.host&&(d.port==c.port||d.relativeProtocol&&c.port==Oa[c.protocol])}function Xb(b){var a=L(b)?b:p;return function(c){a||(a=Wb(b));return c?a[I(c)]||null:a}}function Yb(b,a,c){if(H(c))return c(b,a);n(c,function(c){b=c(b,a)});return b}function bd(){var b=/^\\s*(\\[|\\{[^\\{])/,a=/[\\}\\]]\\s*$/,c=/^\\)\\]\\}',?\\n/,d={\"Content-Type\":\"application/json;charset=utf-8\"},e=this.defaults=\n{transformResponse:[function(d){E(d)&&(d=d.replace(c,\"\"),b.test(d)&&a.test(d)&&(d=ub(d,!0)));return d}],transformRequest:[function(a){return L(a)&&Ea.apply(a)!==\"[object File]\"?ha(a):a}],headers:{common:{Accept:\"application/json, text/plain, */*\"},post:d,put:d,patch:d},xsrfCookieName:\"XSRF-TOKEN\",xsrfHeaderName:\"X-XSRF-TOKEN\"},g=this.interceptors=[],i=this.responseInterceptors=[];this.$get=[\"$httpBackend\",\"$browser\",\"$cacheFactory\",\"$rootScope\",\"$q\",\"$injector\",function(a,b,c,d,k,l){function u(a){function c(a){var b=\nt({},a,{data:Yb(a.data,a.headers,d.transformResponse)});return 200<=a.status&&a.status<300?b:k.reject(b)}var d={transformRequest:e.transformRequest,transformResponse:e.transformResponse},f={};t(d,a);d.headers=f;d.method=oa(d.method);t(f,e.headers.common,e.headers[I(d.method)],a.headers);(a=$c(d.url,b.url())?b.cookies()[d.xsrfCookieName||e.xsrfCookieName]:p)&&(f[d.xsrfHeaderName||e.xsrfHeaderName]=a);var g=[function(a){var b=Yb(a.data,Xb(f),a.transformRequest);C(a.data)&&delete f[\"Content-Type\"];if(C(a.withCredentials)&&\n!C(e.withCredentials))a.withCredentials=e.withCredentials;return o(a,b,f).then(c,c)},p],j=k.when(d);for(n(y,function(a){(a.request||a.requestError)&&g.unshift(a.request,a.requestError);(a.response||a.responseError)&&g.push(a.response,a.responseError)});g.length;)var a=g.shift(),i=g.shift(),j=j.then(a,i);j.success=function(a){j.then(function(b){a(b.data,b.status,b.headers,d)});return j};j.error=function(a){j.then(null,function(b){a(b.data,b.status,b.headers,d)});return j};return j}function o(b,c,g){function j(a,\nb,c){n&&(200<=a&&a<300?n.put(s,[a,b,Wb(c)]):n.remove(s));i(b,a,c);d.$$phase||d.$apply()}function i(a,c,d){c=Math.max(c,0);(200<=c&&c<300?l.resolve:l.reject)({data:a,status:c,headers:Xb(d),config:b})}function h(){var a=Ga(u.pendingRequests,b);a!==-1&&u.pendingRequests.splice(a,1)}var l=k.defer(),o=l.promise,n,p,s=z(b.url,b.params);u.pendingRequests.push(b);o.then(h,h);if((b.cache||e.cache)&&b.cache!==!1&&b.method==\"GET\")n=L(b.cache)?b.cache:L(e.cache)?e.cache:r;if(n)if(p=n.get(s))if(p.then)return p.then(h,\nh),p;else F(p)?i(p[1],p[0],V(p[2])):i(p,200,{});else n.put(s,o);p||a(b.method,s,c,j,g,b.timeout,b.withCredentials,b.responseType);return o}function z(a,b){if(!b)return a;var c=[];nc(b,function(a,b){a==null||a==p||(F(a)||(a=[a]),n(a,function(a){L(a)&&(a=ha(a));c.push(wa(b)+\"=\"+wa(a))}))});return a+(a.indexOf(\"?\")==-1?\"?\":\"&\")+c.join(\"&\")}var r=c(\"$http\"),y=[];n(g,function(a){y.unshift(E(a)?l.get(a):l.invoke(a))});n(i,function(a,b){var c=E(a)?l.get(a):l.invoke(a);y.splice(b,0,{response:function(a){return c(k.when(a))},\nresponseError:function(a){return c(k.reject(a))}})});u.pendingRequests=[];(function(a){n(arguments,function(a){u[a]=function(b,c){return u(t(c||{},{method:a,url:b}))}})})(\"get\",\"delete\",\"head\",\"jsonp\");(function(a){n(arguments,function(a){u[a]=function(b,c,d){return u(t(d||{},{method:a,url:b,data:c}))}})})(\"post\",\"put\");u.defaults=e;return u}]}function cd(){this.$get=[\"$browser\",\"$window\",\"$document\",function(b,a,c){return dd(b,ed,b.defer,a.angular.callbacks,c[0],a.location.protocol.replace(\":\",\"\"))}]}\nfunction dd(b,a,c,d,e,g){function i(a,b){var c=e.createElement(\"script\"),d=function(){e.body.removeChild(c);b&&b()};c.type=\"text/javascript\";c.src=a;Z?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror=d;e.body.appendChild(c);return d}return function(e,h,j,m,k,l,u,o){function z(){p=-1;t&&t();v&&v.abort()}function r(a,d,e,f){var j=(h.match(jb)||[\"\",g])[1];A&&c.cancel(A);t=v=null;d=j==\"file\"?e?200:404:d;a(d==1223?204:d,e,f);b.$$completeOutstandingRequest(q)}\nvar p;b.$$incOutstandingRequestCount();h=h||b.url();if(I(e)==\"jsonp\"){var x=\"_\"+(d.counter++).toString(36);d[x]=function(a){d[x].data=a};var t=i(h.replace(\"JSON_CALLBACK\",\"angular.callbacks.\"+x),function(){d[x].data?r(m,200,d[x].data):r(m,p||-2);delete d[x]})}else{var v=new a;v.open(e,h,!0);n(k,function(a,b){a&&v.setRequestHeader(b,a)});v.onreadystatechange=function(){if(v.readyState==4){var a=v.getAllResponseHeaders(),b=[\"Cache-Control\",\"Content-Language\",\"Content-Type\",\"Expires\",\"Last-Modified\",\n\"Pragma\"];a||(a=\"\",n(b,function(b){var c=v.getResponseHeader(b);c&&(a+=b+\": \"+c+\"\\n\")}));r(m,p||v.status,v.responseType?v.response:v.responseText,a)}};if(u)v.withCredentials=!0;if(o)v.responseType=o;v.send(j||\"\")}if(l>0)var A=c(z,l);else l&&l.then&&l.then(z)}}function fd(){this.$get=function(){return{id:\"en-us\",NUMBER_FORMATS:{DECIMAL_SEP:\".\",GROUP_SEP:\",\",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:\"\",posSuf:\"\",negPre:\"-\",negSuf:\"\",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:\"\\u00a4\",\nposSuf:\"\",negPre:\"(\\u00a4\",negSuf:\")\",gSize:3,lgSize:3}],CURRENCY_SYM:\"$\"},DATETIME_FORMATS:{MONTH:\"January,February,March,April,May,June,July,August,September,October,November,December\".split(\",\"),SHORTMONTH:\"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec\".split(\",\"),DAY:\"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday\".split(\",\"),SHORTDAY:\"Sun,Mon,Tue,Wed,Thu,Fri,Sat\".split(\",\"),AMPMS:[\"AM\",\"PM\"],medium:\"MMM d, y h:mm:ss a\",\"short\":\"M/d/yy h:mm a\",fullDate:\"EEEE, MMMM d, y\",longDate:\"MMMM d, y\",\nmediumDate:\"MMM d, y\",shortDate:\"M/d/yy\",mediumTime:\"h:mm:ss a\",shortTime:\"h:mm a\"},pluralCat:function(b){return b===1?\"one\":\"other\"}}}}function gd(){this.$get=[\"$rootScope\",\"$browser\",\"$q\",\"$exceptionHandler\",function(b,a,c,d){function e(e,f,h){var j=c.defer(),m=j.promise,k=B(h)&&!h,f=a.defer(function(){try{j.resolve(e())}catch(a){j.reject(a),d(a)}k||b.$apply()},f),h=function(){delete g[m.$$timeoutId]};m.$$timeoutId=f;g[f]=j;m.then(h,h);return m}var g={};e.cancel=function(b){return b&&b.$$timeoutId in\ng?(g[b.$$timeoutId].reject(\"canceled\"),a.defer.cancel(b.$$timeoutId)):!1};return e}]}function Zb(b){function a(a,e){return b.factory(a+c,e)}var c=\"Filter\";this.register=a;this.$get=[\"$injector\",function(a){return function(b){return a.get(b+c)}}];a(\"currency\",$b);a(\"date\",ac);a(\"filter\",hd);a(\"json\",id);a(\"limitTo\",jd);a(\"lowercase\",kd);a(\"number\",bc);a(\"orderBy\",cc);a(\"uppercase\",ld)}function hd(){return function(b,a,c){if(!F(b))return b;var d=[];d.check=function(a){for(var b=0;b<d.length;b++)if(!d[b](a))return!1;\nreturn!0};switch(typeof c){case \"function\":break;case \"boolean\":if(c==!0){c=function(a,b){return Ha.equals(a,b)};break}default:c=function(a,b){b=(\"\"+b).toLowerCase();return(\"\"+a).toLowerCase().indexOf(b)>-1}}var e=function(a,b){if(typeof b==\"string\"&&b.charAt(0)===\"!\")return!e(a,b.substr(1));switch(typeof a){case \"boolean\":case \"number\":case \"string\":return c(a,b);case \"object\":switch(typeof b){case \"object\":return c(a,b);default:for(var d in a)if(d.charAt(0)!==\"$\"&&e(a[d],b))return!0}return!1;case \"array\":for(d=\n0;d<a.length;d++)if(e(a[d],b))return!0;return!1;default:return!1}};switch(typeof a){case \"boolean\":case \"number\":case \"string\":a={$:a};case \"object\":for(var g in a)g==\"$\"?function(){if(a[g]){var b=g;d.push(function(c){return e(c,a[b])})}}():function(){if(a[g]){var b=g;d.push(function(c){return e(ib(c,b),a[b])})}}();break;case \"function\":d.push(a);break;default:return b}for(var i=[],f=0;f<b.length;f++){var h=b[f];d.check(h)&&i.push(h)}return i}}function $b(b){var a=b.NUMBER_FORMATS;return function(b,\nd){if(C(d))d=a.CURRENCY_SYM;return dc(b,a.PATTERNS[1],a.GROUP_SEP,a.DECIMAL_SEP,2).replace(/\\u00A4/g,d)}}function bc(b){var a=b.NUMBER_FORMATS;return function(b,d){return dc(b,a.PATTERNS[0],a.GROUP_SEP,a.DECIMAL_SEP,d)}}function dc(b,a,c,d,e){if(isNaN(b)||!isFinite(b))return\"\";var g=b<0,b=Math.abs(b),i=b+\"\",f=\"\",h=[],j=!1;if(i.indexOf(\"e\")!==-1){var m=i.match(/([\\d\\.]+)e(-?)(\\d+)/);m&&m[2]==\"-\"&&m[3]>e+1?i=\"0\":(f=i,j=!0)}if(!j){i=(i.split(ec)[1]||\"\").length;C(e)&&(e=Math.min(Math.max(a.minFrac,i),\na.maxFrac));var i=Math.pow(10,e),b=Math.round(b*i)/i,b=(\"\"+b).split(ec),i=b[0],b=b[1]||\"\",j=0,m=a.lgSize,k=a.gSize;if(i.length>=m+k)for(var j=i.length-m,l=0;l<j;l++)(j-l)%k===0&&l!==0&&(f+=c),f+=i.charAt(l);for(l=j;l<i.length;l++)(i.length-l)%m===0&&l!==0&&(f+=c),f+=i.charAt(l);for(;b.length<e;)b+=\"0\";e&&e!==\"0\"&&(f+=d+b.substr(0,e))}h.push(g?a.negPre:a.posPre);h.push(f);h.push(g?a.negSuf:a.posSuf);return h.join(\"\")}function nb(b,a,c){var d=\"\";b<0&&(d=\"-\",b=-b);for(b=\"\"+b;b.length<a;)b=\"0\"+b;c&&(b=\nb.substr(b.length-a));return d+b}function Q(b,a,c,d){c=c||0;return function(e){e=e[\"get\"+b]();if(c>0||e>-c)e+=c;e===0&&c==-12&&(e=12);return nb(e,a,d)}}function Qa(b,a){return function(c,d){var e=c[\"get\"+b](),g=oa(a?\"SHORT\"+b:b);return d[g][e]}}function ac(b){function a(a){var b;if(b=a.match(c)){var a=new Date(0),g=0,i=0,f=b[8]?a.setUTCFullYear:a.setFullYear,h=b[8]?a.setUTCHours:a.setHours;b[9]&&(g=N(b[9]+b[10]),i=N(b[9]+b[11]));f.call(a,N(b[1]),N(b[2])-1,N(b[3]));g=N(b[4]||0)-g;i=N(b[5]||0)-i;f=\nN(b[6]||0);b=Math.round(parseFloat(\"0.\"+(b[7]||0))*1E3);h.call(a,g,i,f,b)}return a}var c=/^(\\d{4})-?(\\d\\d)-?(\\d\\d)(?:T(\\d\\d)(?::?(\\d\\d)(?::?(\\d\\d)(?:\\.(\\d+))?)?)?(Z|([+-])(\\d\\d):?(\\d\\d))?)?$/;return function(c,e){var g=\"\",i=[],f,h,e=e||\"mediumDate\",e=b.DATETIME_FORMATS[e]||e;E(c)&&(c=md.test(c)?N(c):a(c));Ya(c)&&(c=new Date(c));if(!ra(c))return c;for(;e;)(h=nd.exec(e))?(i=i.concat(ka.call(h,1)),e=i.pop()):(i.push(e),e=null);n(i,function(a){f=od[a];g+=f?f(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,\n\"\").replace(/''/g,\"'\")});return g}}function id(){return function(b){return ha(b,!0)}}function jd(){return function(b,a){if(!F(b)&&!E(b))return b;a=N(a);if(E(b))return a?a>=0?b.slice(0,a):b.slice(a,b.length):\"\";var c=[],d,e;a>b.length?a=b.length:a<-b.length&&(a=-b.length);a>0?(d=0,e=a):(d=b.length+a,e=b.length);for(;d<e;d++)c.push(b[d]);return c}}function cc(b){return function(a,c,d){function e(a,b){return ua(b)?function(b,c){return a(c,b)}:a}if(!F(a))return a;if(!c)return a;for(var c=F(c)?c:[c],c=\nZa(c,function(a){var c=!1,d=a||qa;if(E(a)){if(a.charAt(0)==\"+\"||a.charAt(0)==\"-\")c=a.charAt(0)==\"-\",a=a.substring(1);d=b(a)}return e(function(a,b){var c;c=d(a);var e=d(b),f=typeof c,g=typeof e;f==g?(f==\"string\"&&(c=c.toLowerCase()),f==\"string\"&&(e=e.toLowerCase()),c=c===e?0:c<e?-1:1):c=f<g?-1:1;return c},c)}),g=[],i=0;i<a.length;i++)g.push(a[i]);return g.sort(e(function(a,b){for(var d=0;d<c.length;d++){var e=c[d](a,b);if(e!==0)return e}return 0},d))}}function aa(b){H(b)&&(b={link:b});b.restrict=b.restrict||\n\"AC\";return S(b)}function fc(b,a){function c(a,c){c=c?\"-\"+bb(c,\"-\"):\"\";b.removeClass((a?Ra:Sa)+c).addClass((a?Sa:Ra)+c)}var d=this,e=b.parent().controller(\"form\")||Ta,g=0,i=d.$error={},f=[];d.$name=a.name;d.$dirty=!1;d.$pristine=!0;d.$valid=!0;d.$invalid=!1;e.$addControl(d);b.addClass(pa);c(!0);d.$addControl=function(a){f.push(a);a.$name&&!d.hasOwnProperty(a.$name)&&(d[a.$name]=a)};d.$removeControl=function(a){a.$name&&d[a.$name]===a&&delete d[a.$name];n(i,function(b,c){d.$setValidity(c,!0,a)});ta(f,\na)};d.$setValidity=function(a,b,f){var k=i[a];if(b){if(k&&(ta(k,f),!k.length)){g--;if(!g)c(b),d.$valid=!0,d.$invalid=!1;i[a]=!1;c(!0,a);e.$setValidity(a,!0,d)}}else{g||c(b);if(k){if(Ga(k,f)!=-1)return}else i[a]=k=[],g++,c(!1,a),e.$setValidity(a,!1,d);k.push(f);d.$valid=!1;d.$invalid=!0}};d.$setDirty=function(){b.removeClass(pa).addClass(Ua);d.$dirty=!0;d.$pristine=!1;e.$setDirty()};d.$setPristine=function(){b.removeClass(Ua).addClass(pa);d.$dirty=!1;d.$pristine=!0;n(f,function(a){a.$setPristine()})}}\nfunction X(b){return C(b)||b===\"\"||b===null||b!==b}function Va(b,a,c,d,e,g){var i=function(){var e=a.val();if(ua(c.ngTrim||\"T\"))e=U(e);d.$viewValue!==e&&b.$apply(function(){d.$setViewValue(e)})};if(e.hasEvent(\"input\"))a.bind(\"input\",i);else{var f,h=function(){f||(f=g.defer(function(){i();f=null}))};a.bind(\"keydown\",function(a){a=a.keyCode;a===91||15<a&&a<19||37<=a&&a<=40||h()});a.bind(\"change\",i);e.hasEvent(\"paste\")&&a.bind(\"paste cut\",h)}d.$render=function(){a.val(X(d.$viewValue)?\"\":d.$viewValue)};\nvar j=c.ngPattern,m=function(a,b){return X(b)||a.test(b)?(d.$setValidity(\"pattern\",!0),b):(d.$setValidity(\"pattern\",!1),p)};j&&((e=j.match(/^\\/(.*)\\/([gim]*)$/))?(j=RegExp(e[1],e[2]),e=function(a){return m(j,a)}):e=function(a){var c=b.$eval(j);if(!c||!c.test)throw Error(\"Expected \"+j+\" to be a RegExp but was \"+c);return m(c,a)},d.$formatters.push(e),d.$parsers.push(e));if(c.ngMinlength){var k=N(c.ngMinlength),e=function(a){return!X(a)&&a.length<k?(d.$setValidity(\"minlength\",!1),p):(d.$setValidity(\"minlength\",\n!0),a)};d.$parsers.push(e);d.$formatters.push(e)}if(c.ngMaxlength){var l=N(c.ngMaxlength),e=function(a){return!X(a)&&a.length>l?(d.$setValidity(\"maxlength\",!1),p):(d.$setValidity(\"maxlength\",!0),a)};d.$parsers.push(e);d.$formatters.push(e)}}function ob(b,a){b=\"ngClass\"+b;return aa(function(c,d,e){function g(b){if(a===!0||c.$index%2===a)h&&!ia(b,h)&&i(h),f(b);h=V(b)}function i(a){L(a)&&!F(a)&&(a=Za(a,function(a,b){if(a)return b}));d.removeClass(F(a)?a.join(\" \"):a)}function f(a){L(a)&&!F(a)&&(a=Za(a,\nfunction(a,b){if(a)return b}));a&&d.addClass(F(a)?a.join(\" \"):a)}var h=p;c.$watch(e[b],g,!0);e.$observe(\"class\",function(){var a=c.$eval(e[b]);g(a,a)});b!==\"ngClass\"&&c.$watch(\"$index\",function(d,g){var h=d&1;h!==g&1&&(h===a?f(c.$eval(e[b])):i(c.$eval(e[b])))})})}var I=function(b){return E(b)?b.toLowerCase():b},oa=function(b){return E(b)?b.toUpperCase():b},Z=N((/msie (\\d+)/.exec(I(navigator.userAgent))||[])[1]),w,ga,ka=[].slice,Wa=[].push,Ea=Object.prototype.toString,mc=M.angular,Ha=M.angular||(M.angular=\n{}),Aa,hb,ba=[\"0\",\"0\",\"0\"];q.$inject=[];qa.$inject=[];hb=Z<9?function(b){b=b.nodeName?b:b[0];return b.scopeName&&b.scopeName!=\"HTML\"?oa(b.scopeName+\":\"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};var sc=/[A-Z]/g,pd={full:\"1.1.5\",major:1,minor:1,dot:5,codeName:\"triangle-squarification\"},Ka=R.cache={},Ja=R.expando=\"ng-\"+(new Date).getTime(),wc=1,gc=M.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent(\"on\"+a,c)},gb=\nM.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent(\"on\"+a,c)},uc=/([\\:\\-\\_]+(.))/g,vc=/^moz([A-Z])/,Ba=R.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;T.readyState===\"complete\"?setTimeout(a):(this.bind(\"DOMContentLoaded\",a),R(M).bind(\"load\",a))},toString:function(){var b=[];n(this,function(a){b.push(\"\"+a)});return\"[\"+b.join(\", \")+\"]\"},eq:function(b){return b>=0?w(this[b]):w(this[this.length+b])},length:0,push:Wa,sort:[].sort,\nsplice:[].splice},Na={};n(\"multiple,selected,checked,disabled,readOnly,required,open\".split(\",\"),function(b){Na[I(b)]=b});var Gb={};n(\"input,select,option,textarea,button,form,details\".split(\",\"),function(b){Gb[oa(b)]=!0});n({data:Bb,inheritedData:Ma,scope:function(b){return Ma(b,\"$scope\")},controller:Eb,injector:function(b){return Ma(b,\"$injector\")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:La,css:function(b,a,c){a=Ia(a);if(B(c))b.style[a]=c;else{var d;Z<=8&&(d=b.currentStyle&&b.currentStyle[a],\nd===\"\"&&(d=\"auto\"));d=d||b.style[a];Z<=8&&(d=d===\"\"?p:d);return d}},attr:function(b,a,c){var d=I(a);if(Na[d])if(B(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||q).specified?d:p;else if(B(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),b===null?p:b},prop:function(b,a,c){if(B(c))b[a]=c;else return b[a]},text:t(Z<9?function(b,a){if(b.nodeType==1){if(C(a))return b.innerText;b.innerText=a}else{if(C(a))return b.nodeValue;\nb.nodeValue=a}}:function(b,a){if(C(a))return b.textContent;b.textContent=a},{$dv:\"\"}),val:function(b,a){if(C(a))return b.value;b.value=a},html:function(b,a){if(C(a))return b.innerHTML;for(var c=0,d=b.childNodes;c<d.length;c++)ya(d[c]);b.innerHTML=a}},function(b,a){R.prototype[a]=function(a,d){var e,g;if((b.length==2&&b!==La&&b!==Eb?a:d)===p)if(L(a)){for(e=0;e<this.length;e++)if(b===Bb)b(this[e],a);else for(g in a)b(this[e],g,a[g]);return this}else{if(this.length)return b(this[0],a,d)}else{for(e=0;e<\nthis.length;e++)b(this[e],a,d);return this}return b.$dv}});n({removeData:zb,dealoc:ya,bind:function a(c,d,e){var g=ca(c,\"events\"),i=ca(c,\"handle\");g||ca(c,\"events\",g={});i||ca(c,\"handle\",i=xc(c,g));n(d.split(\" \"),function(d){var h=g[d];if(!h){if(d==\"mouseenter\"||d==\"mouseleave\"){var j=T.body.contains||T.body.compareDocumentPosition?function(a,c){var d=a.nodeType===9?a.documentElement:a,e=c&&c.parentNode;return a===e||!(!e||!(e.nodeType===1&&(d.contains?d.contains(e):a.compareDocumentPosition&&a.compareDocumentPosition(e)&\n16)))}:function(a,c){if(c)for(;c=c.parentNode;)if(c===a)return!0;return!1};g[d]=[];a(c,{mouseleave:\"mouseout\",mouseenter:\"mouseover\"}[d],function(a){var c=a.relatedTarget;(!c||c!==this&&!j(this,c))&&i(a,d)})}else gc(c,d,i),g[d]=[];h=g[d]}h.push(e)})},unbind:Ab,replaceWith:function(a,c){var d,e=a.parentNode;ya(a);n(new R(c),function(c){d?e.insertBefore(c,d.nextSibling):e.replaceChild(c,a);d=c})},children:function(a){var c=[];n(a.childNodes,function(a){a.nodeType===1&&c.push(a)});return c},contents:function(a){return a.childNodes||\n[]},append:function(a,c){n(new R(c),function(c){(a.nodeType===1||a.nodeType===11)&&a.appendChild(c)})},prepend:function(a,c){if(a.nodeType===1){var d=a.firstChild;n(new R(c),function(c){d?a.insertBefore(c,d):(a.appendChild(c),d=c)})}},wrap:function(a,c){var c=w(c)[0],d=a.parentNode;d&&d.replaceChild(c,a);c.appendChild(a)},remove:function(a){ya(a);var c=a.parentNode;c&&c.removeChild(a)},after:function(a,c){var d=a,e=a.parentNode;n(new R(c),function(a){e.insertBefore(a,d.nextSibling);d=a})},addClass:Db,\nremoveClass:Cb,toggleClass:function(a,c,d){C(d)&&(d=!La(a,c));(d?Db:Cb)(a,c)},parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},next:function(a){if(a.nextElementSibling)return a.nextElementSibling;for(a=a.nextSibling;a!=null&&a.nodeType!==1;)a=a.nextSibling;return a},find:function(a,c){return a.getElementsByTagName(c)},clone:fb,triggerHandler:function(a,c){var d=(ca(a,\"events\")||{})[c];n(d,function(c){c.call(a,{preventDefault:q})})}},function(a,c){R.prototype[c]=function(c,e){for(var g,\ni=0;i<this.length;i++)g==p?(g=a(this[i],c,e),g!==p&&(g=w(g))):eb(g,a(this[i],c,e));return g==p?this:g}});za.prototype={put:function(a,c){this[la(a)]=c},get:function(a){return this[la(a)]},remove:function(a){var c=this[a=la(a)];delete this[a];return c}};var zc=/^function\\s*[^\\(]*\\(\\s*([^\\)]*)\\)/m,Ac=/,/,Bc=/^\\s*(_?)(\\S+?)\\1\\s*$/,yc=/((\\/\\/.*$)|(\\/\\*[\\s\\S]*?\\*\\/))/mg;Ib.$inject=[\"$provide\"];var qd=function(){var a=\"$ngAnimateController\",c={running:!0};this.$get=[\"$animation\",\"$window\",\"$sniffer\",\"$rootElement\",\n\"$rootScope\",function(d,e,g,i){i.data(a,c);i=function(c,i){function j(j,k,o){return function(m,r,p){function x(a){var c=0,a=E(a)?a.split(/\\s*,\\s*/):[];n(a,function(a){c=Math.max(parseFloat(a)||0,c)});return c}function t(){m.addClass(K);if($)$(m,v,P);else if(H(e.getComputedStyle)){var a=g.vendorPrefix+\"Animation\",c=g.vendorPrefix+\"Transition\",d=0;n(m,function(f){if(f.nodeType==1){var g=\"transition\",i=c,j=1,h=e.getComputedStyle(f)||{};if(parseFloat(h.animationDuration)>0||parseFloat(h[a+\"Duration\"])>\n0)g=\"animation\",i=a,j=Math.max(parseInt(h[g+\"IterationCount\"])||0,parseInt(h[i+\"IterationCount\"])||0,j);f=Math.max(x(h[g+\"Delay\"]),x(h[i+\"Delay\"]));g=Math.max(x(h[g+\"Duration\"]),x(h[i+\"Duration\"]));d=Math.max(f+j*g,d)}});e.setTimeout(v,d*1E3)}else v()}function v(){if(!v.run)v.run=!0,o(m,r,p),m.removeClass(w),m.removeClass(K),m.removeData(a)}var A=c.$eval(i.ngAnimate),w=A?L(A)?A[j]:A+\"-\"+j:\"\",D=d(w),A=D&&D.setup,$=D&&D.start,D=D&&D.cancel;if(w){var K=w+\"-active\";r||(r=p?p.parent():m.parent());if(!g.transitions&&\n!A&&!$||(r.inheritedData(a)||q).running)k(m,r,p),o(m,r,p);else{var O=m.data(a)||{};O.running&&((D||q)(m),O.done());m.data(a,{running:!0,done:v});m.addClass(w);k(m,r,p);if(m.length==0)return v();var P=(A||q)(m);e.setTimeout(t,1)}}else k(m,r,p),o(m,r,p)}}function m(a,c,d){d?d.after(a):c.append(a)}var k={};k.enter=j(\"enter\",m,q);k.leave=j(\"leave\",q,function(a){a.remove()});k.move=j(\"move\",function(a,c,d){m(a,c,d)},q);k.show=j(\"show\",function(a){a.css(\"display\",\"\")},q);k.hide=j(\"hide\",q,function(a){a.css(\"display\",\n\"none\")});k.animate=function(a,c){j(a,q,q)(c)};return k};i.enabled=function(a){if(arguments.length)c.running=!a;return!c.running};return i}]},Kb=\"Non-assignable model expression: \";Jb.$inject=[\"$provide\"];var Ic=/^(x[\\:\\-_]|data[\\:\\-_])/i,jb=/^([^:]+):\\/\\/(\\w+:{0,1}\\w*@)?(\\{?[\\w\\.-]*\\}?)(:([0-9]+))?(\\/[^\\?#]*)?(\\?([^#]*))?(#(.*))?$/,Pb=/^([^\\?#]*)(\\?([^#]*))?(#(.*))?$/,Oa={http:80,https:443,ftp:21};Rb.prototype=lb.prototype=Qb.prototype={$$replace:!1,absUrl:Pa(\"$$absUrl\"),url:function(a,c){if(C(a))return this.$$url;\nvar d=Pb.exec(a);d[1]&&this.path(decodeURIComponent(d[1]));if(d[2]||d[1])this.search(d[3]||\"\");this.hash(d[5]||\"\",c);return this},protocol:Pa(\"$$protocol\"),host:Pa(\"$$host\"),port:Pa(\"$$port\"),path:Sb(\"$$path\",function(a){return a.charAt(0)==\"/\"?a:\"/\"+a}),search:function(a,c){if(C(a))return this.$$search;B(c)?c===null?delete this.$$search[a]:this.$$search[a]=c:this.$$search=E(a)?vb(a):a;this.$$compose();return this},hash:Sb(\"$$hash\",qa),replace:function(){this.$$replace=!0;return this}};var Da={\"null\":function(){return null},\n\"true\":function(){return!0},\"false\":function(){return!1},undefined:q,\"+\":function(a,c,d,e){d=d(a,c);e=e(a,c);return B(d)?B(e)?d+e:d:B(e)?e:p},\"-\":function(a,c,d,e){d=d(a,c);e=e(a,c);return(B(d)?d:0)-(B(e)?e:0)},\"*\":function(a,c,d,e){return d(a,c)*e(a,c)},\"/\":function(a,c,d,e){return d(a,c)/e(a,c)},\"%\":function(a,c,d,e){return d(a,c)%e(a,c)},\"^\":function(a,c,d,e){return d(a,c)^e(a,c)},\"=\":q,\"===\":function(a,c,d,e){return d(a,c)===e(a,c)},\"!==\":function(a,c,d,e){return d(a,c)!==e(a,c)},\"==\":function(a,\nc,d,e){return d(a,c)==e(a,c)},\"!=\":function(a,c,d,e){return d(a,c)!=e(a,c)},\"<\":function(a,c,d,e){return d(a,c)<e(a,c)},\">\":function(a,c,d,e){return d(a,c)>e(a,c)},\"<=\":function(a,c,d,e){return d(a,c)<=e(a,c)},\">=\":function(a,c,d,e){return d(a,c)>=e(a,c)},\"&&\":function(a,c,d,e){return d(a,c)&&e(a,c)},\"||\":function(a,c,d,e){return d(a,c)||e(a,c)},\"&\":function(a,c,d,e){return d(a,c)&e(a,c)},\"|\":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},\"!\":function(a,c,d){return!d(a,c)}},Qc={n:\"\\n\",f:\"\\u000c\",r:\"\\r\",\nt:\"\\t\",v:\"\\u000b\",\"'\":\"'\",'\"':'\"'},mb={},ad=/^(([^:]+):)?\\/\\/(\\w+:{0,1}\\w*@)?([\\w\\.-]*)?(:([0-9]+))?(.*)$/,ed=M.XMLHttpRequest||function(){try{return new ActiveXObject(\"Msxml2.XMLHTTP.6.0\")}catch(a){}try{return new ActiveXObject(\"Msxml2.XMLHTTP.3.0\")}catch(c){}try{return new ActiveXObject(\"Msxml2.XMLHTTP\")}catch(d){}throw Error(\"This browser does not support XMLHttpRequest.\");};Zb.$inject=[\"$provide\"];$b.$inject=[\"$locale\"];bc.$inject=[\"$locale\"];var ec=\".\",od={yyyy:Q(\"FullYear\",4),yy:Q(\"FullYear\",\n2,0,!0),y:Q(\"FullYear\",1),MMMM:Qa(\"Month\"),MMM:Qa(\"Month\",!0),MM:Q(\"Month\",2,1),M:Q(\"Month\",1,1),dd:Q(\"Date\",2),d:Q(\"Date\",1),HH:Q(\"Hours\",2),H:Q(\"Hours\",1),hh:Q(\"Hours\",2,-12),h:Q(\"Hours\",1,-12),mm:Q(\"Minutes\",2),m:Q(\"Minutes\",1),ss:Q(\"Seconds\",2),s:Q(\"Seconds\",1),sss:Q(\"Milliseconds\",3),EEEE:Qa(\"Day\"),EEE:Qa(\"Day\",!0),a:function(a,c){return a.getHours()<12?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){var a=-1*a.getTimezoneOffset(),c=a>=0?\"+\":\"\";c+=nb(Math[a>0?\"floor\":\"ceil\"](a/60),2)+nb(Math.abs(a%60),\n2);return c}},nd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,md=/^\\d+$/;ac.$inject=[\"$locale\"];var kd=S(I),ld=S(oa);cc.$inject=[\"$parse\"];var rd=S({restrict:\"E\",compile:function(a,c){Z<=8&&(!c.href&&!c.name&&c.$set(\"href\",\"\"),a.append(T.createComment(\"IE fix\")));return function(a,c){c.bind(\"click\",function(a){c.attr(\"href\")||a.preventDefault()})}}}),pb={};n(Na,function(a,c){var d=da(\"ng-\"+c);pb[d]=function(){return{priority:100,compile:function(){return function(a,\ng,i){a.$watch(i[d],function(a){i.$set(c,!!a)})}}}}});n([\"src\",\"srcset\",\"href\"],function(a){var c=da(\"ng-\"+a);pb[c]=function(){return{priority:99,link:function(d,e,g){g.$observe(c,function(c){c&&(g.$set(a,c),Z&&e.prop(a,g[a]))})}}}});var Ta={$addControl:q,$removeControl:q,$setValidity:q,$setDirty:q,$setPristine:q};fc.$inject=[\"$element\",\"$attrs\",\"$scope\"];var Wa=function(a){return[\"$timeout\",function(c){var d={name:\"form\",restrict:\"E\",controller:fc,compile:function(){return{pre:function(a,d,i,f){if(!i.action){var h=\nfunction(a){a.preventDefault?a.preventDefault():a.returnValue=!1};gc(d[0],\"submit\",h);d.bind(\"$destroy\",function(){c(function(){gb(d[0],\"submit\",h)},0,!1)})}var j=d.parent().controller(\"form\"),m=i.name||i.ngForm;m&&(a[m]=f);j&&d.bind(\"$destroy\",function(){j.$removeControl(f);m&&(a[m]=p);t(f,Ta)})}}}};return a?t(V(d),{restrict:\"EAC\"}):d}]},sd=Wa(),td=Wa(!0),ud=/^(ftp|http|https):\\/\\/(\\w+:{0,1}\\w*@)?(\\S+)(:[0-9]+)?(\\/|\\/([\\w#!:.?+=&%@!\\-\\/]))?$/,vd=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}$/,\nwd=/^\\s*(\\-|\\+)?(\\d+|(\\d*(\\.\\d*)))\\s*$/,hc={text:Va,number:function(a,c,d,e,g,i){Va(a,c,d,e,g,i);e.$parsers.push(function(a){var c=X(a);return c||wd.test(a)?(e.$setValidity(\"number\",!0),a===\"\"?null:c?a:parseFloat(a)):(e.$setValidity(\"number\",!1),p)});e.$formatters.push(function(a){return X(a)?\"\":\"\"+a});if(d.min){var f=parseFloat(d.min),a=function(a){return!X(a)&&a<f?(e.$setValidity(\"min\",!1),p):(e.$setValidity(\"min\",!0),a)};e.$parsers.push(a);e.$formatters.push(a)}if(d.max){var h=parseFloat(d.max),\nd=function(a){return!X(a)&&a>h?(e.$setValidity(\"max\",!1),p):(e.$setValidity(\"max\",!0),a)};e.$parsers.push(d);e.$formatters.push(d)}e.$formatters.push(function(a){return X(a)||Ya(a)?(e.$setValidity(\"number\",!0),a):(e.$setValidity(\"number\",!1),p)})},url:function(a,c,d,e,g,i){Va(a,c,d,e,g,i);a=function(a){return X(a)||ud.test(a)?(e.$setValidity(\"url\",!0),a):(e.$setValidity(\"url\",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a,c,d,e,g,i){Va(a,c,d,e,g,i);a=function(a){return X(a)||vd.test(a)?\n(e.$setValidity(\"email\",!0),a):(e.$setValidity(\"email\",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){C(d.name)&&c.attr(\"name\",Fa());c.bind(\"click\",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe(\"value\",e.$render)},checkbox:function(a,c,d,e){var g=d.ngTrueValue,i=d.ngFalseValue;E(g)||(g=!0);E(i)||(i=!1);c.bind(\"click\",function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});\ne.$render=function(){c[0].checked=e.$viewValue};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:i})},hidden:q,button:q,submit:q,reset:q},ic=[\"$browser\",\"$sniffer\",function(a,c){return{restrict:\"E\",require:\"?ngModel\",link:function(d,e,g,i){i&&(hc[I(g.type)]||hc.text)(d,e,g,i,c,a)}}}],Sa=\"ng-valid\",Ra=\"ng-invalid\",pa=\"ng-pristine\",Ua=\"ng-dirty\",xd=[\"$scope\",\"$exceptionHandler\",\"$attrs\",\"$element\",\"$parse\",function(a,c,d,e,g){function i(a,c){c=c?\"-\"+bb(c,\"-\"):\"\";\ne.removeClass((a?Ra:Sa)+c).addClass((a?Sa:Ra)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var f=g(d.ngModel),h=f.assign;if(!h)throw Error(Kb+d.ngModel+\" (\"+va(e)+\")\");this.$render=q;var j=e.inheritedData(\"$formController\")||Ta,m=0,k=this.$error={};e.addClass(pa);i(!0);this.$setValidity=function(a,c){if(k[a]!==!c){if(c){if(k[a]&&m--,!m)i(!0),this.$valid=\n!0,this.$invalid=!1}else i(!1),this.$invalid=!0,this.$valid=!1,m++;k[a]=!c;i(c,a);j.$setValidity(a,c,this)}};this.$setPristine=function(){this.$dirty=!1;this.$pristine=!0;e.removeClass(Ua).addClass(pa)};this.$setViewValue=function(d){this.$viewValue=d;if(this.$pristine)this.$dirty=!0,this.$pristine=!1,e.removeClass(pa).addClass(Ua),j.$setDirty();n(this.$parsers,function(a){d=a(d)});if(this.$modelValue!==d)this.$modelValue=d,h(a,d),n(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})};\nvar l=this;a.$watch(function(){var c=f(a);if(l.$modelValue!==c){var d=l.$formatters,e=d.length;for(l.$modelValue=c;e--;)c=d[e](c);if(l.$viewValue!==c)l.$viewValue=c,l.$render()}})}],yd=function(){return{require:[\"ngModel\",\"^?form\"],controller:xd,link:function(a,c,d,e){var g=e[0],i=e[1]||Ta;i.$addControl(g);c.bind(\"$destroy\",function(){i.$removeControl(g)})}}},zd=S({require:\"ngModel\",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),jc=function(){return{require:\"?ngModel\",\nlink:function(a,c,d,e){if(e){d.required=!0;var g=function(a){if(d.required&&(X(a)||a===!1))e.$setValidity(\"required\",!1);else return e.$setValidity(\"required\",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe(\"required\",function(){g(e.$viewValue)})}}}},Ad=function(){return{require:\"ngModel\",link:function(a,c,d,e){var g=(a=/\\/(.*)\\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||\",\";e.$parsers.push(function(a){var c=[];a&&n(a.split(g),function(a){a&&c.push(U(a))});return c});e.$formatters.push(function(a){return F(a)?\na.join(\", \"):p})}}},Bd=/^(true|false|\\d+)$/,Cd=function(){return{priority:100,compile:function(a,c){return Bd.test(c.ngValue)?function(a,c,g){g.$set(\"value\",a.$eval(g.ngValue))}:function(a,c,g){a.$watch(g.ngValue,function(a){g.$set(\"value\",a,!1)})}}}},Dd=aa(function(a,c,d){c.addClass(\"ng-binding\").data(\"$binding\",d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==p?\"\":a)})}),Ed=[\"$interpolate\",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass(\"ng-binding\").data(\"$binding\",\nc);e.$observe(\"ngBindTemplate\",function(a){d.text(a)})}}],Fd=[function(){return function(a,c,d){c.addClass(\"ng-binding\").data(\"$binding\",d.ngBindHtmlUnsafe);a.$watch(d.ngBindHtmlUnsafe,function(a){c.html(a||\"\")})}}],Gd=ob(\"\",!0),Hd=ob(\"Odd\",0),Id=ob(\"Even\",1),Jd=aa({compile:function(a,c){c.$set(\"ngCloak\",p);a.removeClass(\"ng-cloak\")}}),Kd=[function(){return{scope:!0,controller:\"@\"}}],Ld=[\"$sniffer\",function(a){return{priority:1E3,compile:function(){a.csp=!0}}}],kc={};n(\"click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress\".split(\" \"),\nfunction(a){var c=da(\"ng-\"+a);kc[c]=[\"$parse\",function(d){return function(e,g,i){var f=d(i[c]);g.bind(I(a),function(a){e.$apply(function(){f(e,{$event:a})})})}}]});var Md=aa(function(a,c,d){c.bind(\"submit\",function(){a.$apply(d.ngSubmit)})}),Nd=[\"$animator\",function(a){return{transclude:\"element\",priority:1E3,terminal:!0,restrict:\"A\",compile:function(c,d,e){return function(c,d,f){var h=a(c,f),j,m;c.$watch(f.ngIf,function(a){j&&(h.leave(j),j=p);m&&(m.$destroy(),m=p);ua(a)&&(m=c.$new(),e(m,function(a){j=\na;h.enter(a,d.parent(),d)}))})}}}}],Od=[\"$http\",\"$templateCache\",\"$anchorScroll\",\"$compile\",\"$animator\",function(a,c,d,e,g){return{restrict:\"ECA\",terminal:!0,compile:function(i,f){var h=f.ngInclude||f.src,j=f.onload||\"\",m=f.autoscroll;return function(f,i,n){var o=g(f,n),p=0,r,t=function(){r&&(r.$destroy(),r=null);o.leave(i.contents(),i)};f.$watch(h,function(g){var h=++p;g?(a.get(g,{cache:c}).success(function(a){h===p&&(r&&r.$destroy(),r=f.$new(),o.leave(i.contents(),i),a=w(\"<div/>\").html(a).contents(),\no.enter(a,i),e(a)(r),B(m)&&(!m||f.$eval(m))&&d(),r.$emit(\"$includeContentLoaded\"),f.$eval(j))}).error(function(){h===p&&t()}),f.$emit(\"$includeContentRequested\")):t()})}}}}],Pd=aa({compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Qd=aa({terminal:!0,priority:1E3}),Rd=[\"$locale\",\"$interpolate\",function(a,c){var d=/{}/g;return{restrict:\"EA\",link:function(e,g,i){var f=i.count,h=g.attr(i.$attr.when),j=i.offset||0,m=e.$eval(h),k={},l=c.startSymbol(),p=c.endSymbol();n(m,function(a,e){k[e]=\nc(a.replace(d,l+f+\"-\"+j+p))});e.$watch(function(){var c=parseFloat(e.$eval(f));return isNaN(c)?\"\":(c in m||(c=a.pluralCat(c-j)),k[c](e,g,!0))},function(a){g.text(a)})}}}],Sd=[\"$parse\",\"$animator\",function(a,c){return{transclude:\"element\",priority:1E3,terminal:!0,compile:function(d,e,g){return function(d,e,h){var j=c(d,h),m=h.ngRepeat,k=m.match(/^\\s*(.+)\\s+in\\s+(.*?)\\s*(\\s+track\\s+by\\s+(.+)\\s*)?$/),l,p,o,z,r,t={$id:la};if(!k)throw Error(\"Expected ngRepeat in form of '_item_ in _collection_[ track by _id_]' but got '\"+\nm+\"'.\");h=k[1];o=k[2];(k=k[4])?(l=a(k),p=function(a,c,e){r&&(t[r]=a);t[z]=c;t.$index=e;return l(d,t)}):p=function(a,c){return la(c)};k=h.match(/^(?:([\\$\\w]+)|\\(([\\$\\w]+)\\s*,\\s*([\\$\\w]+)\\))$/);if(!k)throw Error(\"'item' in 'item in collection' should be identifier or (key, value) but got '\"+h+\"'.\");z=k[3]||k[1];r=k[2];var x={};d.$watchCollection(o,function(a){var c,h,k=e,l,o={},t,q,w,s,B,y,C=[];if(Xa(a))B=a;else{B=[];for(w in a)a.hasOwnProperty(w)&&w.charAt(0)!=\"$\"&&B.push(w);B.sort()}t=B.length;h=\nC.length=B.length;for(c=0;c<h;c++)if(w=a===B?c:B[c],s=a[w],l=p(w,s,c),x.hasOwnProperty(l))y=x[l],delete x[l],o[l]=y,C[c]=y;else if(o.hasOwnProperty(l))throw n(C,function(a){a&&a.element&&(x[a.id]=a)}),Error(\"Duplicates in a repeater are not allowed. Repeater: \"+m+\" key: \"+l);else C[c]={id:l},o[l]=!1;for(w in x)if(x.hasOwnProperty(w))y=x[w],j.leave(y.element),y.element[0].$$NG_REMOVED=!0,y.scope.$destroy();c=0;for(h=B.length;c<h;c++){w=a===B?c:B[c];s=a[w];y=C[c];if(y.element){q=y.scope;l=k[0];do l=\nl.nextSibling;while(l&&l.$$NG_REMOVED);y.element[0]!=l&&j.move(y.element,null,k);k=y.element}else q=d.$new();q[z]=s;r&&(q[r]=w);q.$index=c;q.$first=c===0;q.$last=c===t-1;q.$middle=!(q.$first||q.$last);y.element||g(q,function(a){j.enter(a,null,k);k=a;y.scope=q;y.element=a;o[y.id]=y})}x=o})}}}}],Td=[\"$animator\",function(a){return function(c,d,e){var g=a(c,e);c.$watch(e.ngShow,function(a){g[ua(a)?\"show\":\"hide\"](d)})}}],Ud=[\"$animator\",function(a){return function(c,d,e){var g=a(c,e);c.$watch(e.ngHide,\nfunction(a){g[ua(a)?\"hide\":\"show\"](d)})}}],Vd=aa(function(a,c,d){a.$watch(d.ngStyle,function(a,d){d&&a!==d&&n(d,function(a,d){c.css(d,\"\")});a&&c.css(a)},!0)}),Wd=[\"$animator\",function(a){return{restrict:\"EA\",require:\"ngSwitch\",controller:[\"$scope\",function(){this.cases={}}],link:function(c,d,e,g){var i=a(c,e),f,h,j=[];c.$watch(e.ngSwitch||e.on,function(a){for(var d=0,l=j.length;d<l;d++)j[d].$destroy(),i.leave(h[d]);h=[];j=[];if(f=g.cases[\"!\"+a]||g.cases[\"?\"])c.$eval(e.change),n(f,function(a){var d=\nc.$new();j.push(d);a.transclude(d,function(c){var d=a.element;h.push(c);i.enter(c,d.parent(),d)})})})}}}],Xd=aa({transclude:\"element\",priority:500,require:\"^ngSwitch\",compile:function(a,c,d){return function(a,g,i,f){f.cases[\"!\"+c.ngSwitchWhen]=f.cases[\"!\"+c.ngSwitchWhen]||[];f.cases[\"!\"+c.ngSwitchWhen].push({transclude:d,element:g})}}}),Yd=aa({transclude:\"element\",priority:500,require:\"^ngSwitch\",compile:function(a,c,d){return function(a,c,i,f){f.cases[\"?\"]=f.cases[\"?\"]||[];f.cases[\"?\"].push({transclude:d,\nelement:c})}}}),Zd=aa({controller:[\"$transclude\",\"$element\",function(a,c){a(function(a){c.append(a)})}]}),$d=[\"$http\",\"$templateCache\",\"$route\",\"$anchorScroll\",\"$compile\",\"$controller\",\"$animator\",function(a,c,d,e,g,i,f){return{restrict:\"ECA\",terminal:!0,link:function(a,c,m){function k(){var f=d.current&&d.current.locals,k=f&&f.$template;if(k){o.leave(c.contents(),c);l&&(l.$destroy(),l=null);k=w(\"<div></div>\").html(k).contents();o.enter(k,c);var k=g(k),m=d.current;l=m.scope=a.$new();if(m.controller)f.$scope=\nl,f=i(m.controller,f),m.controllerAs&&(l[m.controllerAs]=f),c.children().data(\"$ngControllerController\",f);k(l);l.$emit(\"$viewContentLoaded\");l.$eval(n);e()}else o.leave(c.contents(),c),l&&(l.$destroy(),l=null)}var l,n=m.onload||\"\",o=f(a,m);a.$on(\"$routeChangeSuccess\",k);k()}}}],ae=[\"$templateCache\",function(a){return{restrict:\"E\",terminal:!0,compile:function(c,d){d.type==\"text/ng-template\"&&a.put(d.id,c[0].text)}}}],be=S({terminal:!0}),ce=[\"$compile\",\"$parse\",function(a,c){var d=/^\\s*(.*?)(?:\\s+as\\s+(.*?))?(?:\\s+group\\s+by\\s+(.*))?\\s+for\\s+(?:([\\$\\w][\\$\\w\\d]*)|(?:\\(\\s*([\\$\\w][\\$\\w\\d]*)\\s*,\\s*([\\$\\w][\\$\\w\\d]*)\\s*\\)))\\s+in\\s+(.*?)(?:\\s+track\\s+by\\s+(.*?))?$/,\ne={$setViewValue:q};return{restrict:\"E\",require:[\"select\",\"?ngModel\"],controller:[\"$element\",\"$scope\",\"$attrs\",function(a,c,d){var h=this,j={},m=e,k;h.databound=d.ngModel;h.init=function(a,c,d){m=a;k=d};h.addOption=function(c){j[c]=!0;m.$viewValue==c&&(a.val(c),k.parent()&&k.remove())};h.removeOption=function(a){this.hasOption(a)&&(delete j[a],m.$viewValue==a&&this.renderUnknownOption(a))};h.renderUnknownOption=function(c){c=\"? \"+la(c)+\" ?\";k.val(c);a.prepend(k);a.val(c);k.prop(\"selected\",!0)};h.hasOption=\nfunction(a){return j.hasOwnProperty(a)};c.$on(\"$destroy\",function(){h.renderUnknownOption=q})}],link:function(e,i,f,h){function j(a,c,d,e){d.$render=function(){var a=d.$viewValue;e.hasOption(a)?(v.parent()&&v.remove(),c.val(a),a===\"\"&&t.prop(\"selected\",!0)):C(a)&&t?c.val(\"\"):e.renderUnknownOption(a)};c.bind(\"change\",function(){a.$apply(function(){v.parent()&&v.remove();d.$setViewValue(c.val())})})}function m(a,c,d){var e;d.$render=function(){var a=new za(d.$viewValue);n(c.find(\"option\"),function(c){c.selected=\nB(a.get(c.value))})};a.$watch(function(){ia(e,d.$viewValue)||(e=V(d.$viewValue),d.$render())});c.bind(\"change\",function(){a.$apply(function(){var a=[];n(c.find(\"option\"),function(c){c.selected&&a.push(c.value)});d.$setViewValue(a)})})}function k(e,f,g){function i(){var a={\"\":[]},c=[\"\"],d,h,q,v,s;q=g.$modelValue;v=u(e)||[];var z=l?qb(v):v,B,y,A;y={};s=!1;var C,D;if(o)if(t&&F(q)){s=new za([]);for(h=0;h<q.length;h++)y[k]=q[h],s.put(t(e,y),q[h])}else s=new za(q);for(A=0;B=z.length,A<B;A++){y[k]=v[l?y[l]=\nz[A]:A];d=m(e,y)||\"\";if(!(h=a[d]))h=a[d]=[],c.push(d);o?d=s.remove(t?t(e,y):n(e,y))!=p:(t?(d={},d[k]=q,d=t(e,d)===t(e,y)):d=q===n(e,y),s=s||d);C=j(e,y);C=C===p?\"\":C;h.push({id:t?t(e,y):l?z[A]:A,label:C,selected:d})}o||(r||q===null?a[\"\"].unshift({id:\"\",label:\"\",selected:!s}):s||a[\"\"].unshift({id:\"?\",label:\"\",selected:!0}));y=0;for(z=c.length;y<z;y++){d=c[y];h=a[d];if(w.length<=y)q={element:E.clone().attr(\"label\",d),label:h.label},v=[q],w.push(v),f.append(q.element);else if(v=w[y],q=v[0],q.label!=d)q.element.attr(\"label\",\nq.label=d);C=null;A=0;for(B=h.length;A<B;A++)if(d=h[A],s=v[A+1]){C=s.element;if(s.label!==d.label)C.text(s.label=d.label);if(s.id!==d.id)C.val(s.id=d.id);if(C[0].selected!==d.selected)C.prop(\"selected\",s.selected=d.selected)}else d.id===\"\"&&r?D=r:(D=x.clone()).val(d.id).attr(\"selected\",d.selected).text(d.label),v.push({element:D,label:d.label,id:d.id,selected:d.selected}),C?C.after(D):q.element.append(D),C=D;for(A++;v.length>A;)v.pop().element.remove()}for(;w.length>y;)w.pop()[0].element.remove()}\nvar h;if(!(h=q.match(d)))throw Error(\"Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_ (track by _expr_)?' but got '\"+q+\"'.\");var j=c(h[2]||h[1]),k=h[4]||h[6],l=h[5],m=c(h[3]||\"\"),n=c(h[2]?h[1]:k),u=c(h[7]),t=h[8]?c(h[8]):null,w=[[{element:f,label:\"\"}]];r&&(a(r)(e),r.removeClass(\"ng-scope\"),r.remove());f.html(\"\");f.bind(\"change\",function(){e.$apply(function(){var a,c=u(e)||[],d={},h,i,j,m,q,r;if(o){i=[];m=0;for(r=w.length;m<r;m++){a=w[m];j=1;for(q=a.length;j<\nq;j++)if((h=a[j].element)[0].selected){h=h.val();l&&(d[l]=h);if(t)for(var s=0;s<c.length;s++){if(d[k]=c[s],t(e,d)==h)break}else d[k]=c[h];i.push(n(e,d))}}}else if(h=f.val(),h==\"?\")i=p;else if(h==\"\")i=null;else if(t)for(s=0;s<c.length;s++){if(d[k]=c[s],t(e,d)==h){i=n(e,d);break}}else d[k]=c[h],l&&(d[l]=h),i=n(e,d);g.$setViewValue(i)})});g.$render=i;e.$watch(i)}if(h[1]){for(var l=h[0],u=h[1],o=f.multiple,q=f.ngOptions,r=!1,t,x=w(T.createElement(\"option\")),E=w(T.createElement(\"optgroup\")),v=x.clone(),\nh=0,A=i.children(),G=A.length;h<G;h++)if(A[h].value==\"\"){t=r=A.eq(h);break}l.init(u,r,v);if(o&&(f.required||f.ngRequired)){var D=function(a){u.$setValidity(\"required\",!f.required||a&&a.length);return a};u.$parsers.push(D);u.$formatters.unshift(D);f.$observe(\"required\",function(){D(u.$viewValue)})}q?k(e,i,u):o?m(e,i,u):j(e,i,u,l)}}}}],de=[\"$interpolate\",function(a){var c={addOption:q,removeOption:q};return{restrict:\"E\",priority:100,compile:function(d,e){if(C(e.value)){var g=a(d.text(),!0);g||e.$set(\"value\",\nd.text())}return function(a,d,e){var j=d.parent(),m=j.data(\"$selectController\")||j.parent().data(\"$selectController\");m&&m.databound?d.prop(\"selected\",!1):m=c;g?a.$watch(g,function(a,c){e.$set(\"value\",a);a!==c&&m.removeOption(c);m.addOption(a)}):m.addOption(e.value);d.bind(\"$destroy\",function(){m.removeOption(e.value)})}}}}],ee=S({restrict:\"E\",terminal:!0});(ga=M.jQuery)?(w=ga,t(ga.fn,{scope:Ba.scope,controller:Ba.controller,injector:Ba.injector,inheritedData:Ba.inheritedData}),db(\"remove\",!0),db(\"empty\"),\ndb(\"html\")):w=R;Ha.element=w;(function(a){t(a,{bootstrap:xb,copy:V,extend:t,equals:ia,element:w,forEach:n,injector:yb,noop:q,bind:$a,toJson:ha,fromJson:ub,identity:qa,isUndefined:C,isDefined:B,isString:E,isFunction:H,isObject:L,isNumber:Ya,isElement:oc,isArray:F,version:pd,isDate:ra,lowercase:I,uppercase:oa,callbacks:{counter:0},noConflict:lc});Aa=tc(M);try{Aa(\"ngLocale\")}catch(c){Aa(\"ngLocale\",[]).provider(\"$locale\",fd)}Aa(\"ng\",[\"ngLocale\"],[\"$provide\",function(a){a.provider(\"$compile\",Jb).directive({a:rd,\ninput:ic,textarea:ic,form:sd,script:ae,select:ce,style:ee,option:de,ngBind:Dd,ngBindHtmlUnsafe:Fd,ngBindTemplate:Ed,ngClass:Gd,ngClassEven:Id,ngClassOdd:Hd,ngCsp:Ld,ngCloak:Jd,ngController:Kd,ngForm:td,ngHide:Ud,ngIf:Nd,ngInclude:Od,ngInit:Pd,ngNonBindable:Qd,ngPluralize:Rd,ngRepeat:Sd,ngShow:Td,ngSubmit:Md,ngStyle:Vd,ngSwitch:Wd,ngSwitchWhen:Xd,ngSwitchDefault:Yd,ngOptions:be,ngView:$d,ngTransclude:Zd,ngModel:yd,ngList:Ad,ngChange:zd,required:jc,ngRequired:jc,ngValue:Cd}).directive(pb).directive(kc);\na.provider({$anchorScroll:Cc,$animation:Ib,$animator:qd,$browser:Ec,$cacheFactory:Fc,$controller:Jc,$document:Kc,$exceptionHandler:Lc,$filter:Zb,$interpolate:Mc,$http:bd,$httpBackend:cd,$location:Nc,$log:Oc,$parse:Sc,$route:Vc,$routeParams:Wc,$rootScope:Xc,$q:Tc,$sniffer:Yc,$templateCache:Gc,$timeout:gd,$window:Zc})}])})(Ha);w(T).ready(function(){rc(T,xb)})})(window,document);angular.element(document).find(\"head\").append('<style type=\"text/css\">@charset \"UTF-8\";[ng\\\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\\\:form{display:block;}</style>');\n"
  },
  {
    "path": "assets/client/static/js/base64.js",
    "content": "/*\nCopyright (c) 2008 Fred Palmer fred.palmer_at_gmail.com\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation\nfiles (the \"Software\"), to deal in the Software without\nrestriction, including without limitation the rights to use,\ncopy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\nOF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n*/\nfunction StringBuffer()\n{ \n    this.buffer = []; \n} \n\nStringBuffer.prototype.append = function append(string)\n{ \n    this.buffer.push(string); \n    return this; \n}; \n\nStringBuffer.prototype.toString = function toString()\n{ \n    return this.buffer.join(\"\"); \n}; \n\nwindow.Base64 =\n{\n    codex : \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\",\n\n    encode : function (input)\n    {\n        var output = new StringBuffer();\n\n        var enumerator = new Utf8EncodeEnumerator(input);\n        while (enumerator.moveNext())\n        {\n            var chr1 = enumerator.current;\n\n            enumerator.moveNext();\n            var chr2 = enumerator.current;\n\n            enumerator.moveNext();\n            var chr3 = enumerator.current;\n\n            var enc1 = chr1 >> 2;\n            var enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);\n            var enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);\n            var enc4 = chr3 & 63;\n\n            if (isNaN(chr2))\n            {\n                enc3 = enc4 = 64;\n            }\n            else if (isNaN(chr3))\n            {\n                enc4 = 64;\n            }\n\n            output.append(this.codex.charAt(enc1) + this.codex.charAt(enc2) + this.codex.charAt(enc3) + this.codex.charAt(enc4));\n        }\n\n        return output.toString();\n    },\n\n    decode : function (input)\n    {\n        var output = new StringBuffer();\n        var outputBytes = [];\n\n        var enumerator = new Base64DecodeEnumerator(input);\n        while (enumerator.moveNext())\n        {\n            var charCode = enumerator.current;\n            outputBytes.push(charCode);\n\n            if (charCode < 128)\n                output.append(String.fromCharCode(charCode));\n            else if ((charCode > 191) && (charCode < 224))\n            {\n                enumerator.moveNext();\n                var charCode2 = enumerator.current;\n                outputBytes.push(charCode2);\n\n                output.append(String.fromCharCode(((charCode & 31) << 6) | (charCode2 & 63)));\n            }\n            else\n            {\n                enumerator.moveNext();\n                var charCode2 = enumerator.current;\n                outputBytes.push(charCode2);\n\n                enumerator.moveNext();\n                var charCode3 = enumerator.current;\n                outputBytes.push(charCode3);\n\n                output.append(String.fromCharCode(((charCode & 15) << 12) | ((charCode2 & 63) << 6) | (charCode3 & 63)));\n            }\n        }\n\n        return {\n            \"bytes\": outputBytes,\n            \"text\": output.toString()\n        };\n    }\n};\n\nfunction Utf8EncodeEnumerator(input)\n{\n    this._input = input;\n    this._index = -1;\n    this._buffer = [];\n}\n\nUtf8EncodeEnumerator.prototype =\n{\n    current: Number.NaN,\n\n    moveNext: function()\n    {\n        if (this._buffer.length > 0)\n        {\n            this.current = this._buffer.shift();\n            return true;\n        }\n        else if (this._index >= (this._input.length - 1))\n        {\n            this.current = Number.NaN;\n            return false;\n        }\n        else\n        {\n            var charCode = this._input.charCodeAt(++this._index);\n\n            // \"\\r\\n\" -> \"\\n\"\n            //\n            if ((charCode == 13) && (this._input.charCodeAt(this._index + 1) == 10))\n            {\n                charCode = 10;\n                this._index += 2;\n            }\n\n            if (charCode < 128)\n            {\n                this.current = charCode;\n            }\n            else if ((charCode > 127) && (charCode < 2048))\n            {\n                this.current = (charCode >> 6) | 192;\n                this._buffer.push((charCode & 63) | 128);\n            }\n            else\n            {\n                this.current = (charCode >> 12) | 224;\n                this._buffer.push(((charCode >> 6) & 63) | 128);\n                this._buffer.push((charCode & 63) | 128);\n            }\n\n            return true;\n        }\n    }\n}\n\nfunction Base64DecodeEnumerator(input)\n{\n    this._input = input;\n    this._index = -1;\n    this._buffer = [];\n}\n\nBase64DecodeEnumerator.prototype =\n{\n    current: 64,\n\n    moveNext: function()\n    {\n        if (this._buffer.length > 0)\n        {\n            this.current = this._buffer.shift();\n            return true;\n        }\n        else if (this._index >= (this._input.length - 1))\n        {\n            this.current = 64;\n            return false;\n        }\n        else\n        {\n            var enc1 = Base64.codex.indexOf(this._input.charAt(++this._index));\n            var enc2 = Base64.codex.indexOf(this._input.charAt(++this._index));\n            var enc3 = Base64.codex.indexOf(this._input.charAt(++this._index));\n            var enc4 = Base64.codex.indexOf(this._input.charAt(++this._index));\n\n            var chr1 = (enc1 << 2) | (enc2 >> 4);\n            var chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);\n            var chr3 = ((enc3 & 3) << 6) | enc4;\n\n            this.current = chr1;\n\n            if (enc3 != 64)\n                this._buffer.push(chr2);\n\n            if (enc4 != 64)\n                this._buffer.push(chr3);\n\n            return true;\n        }\n    }\n};\n"
  },
  {
    "path": "assets/client/static/js/jquery.timeago.js",
    "content": "/**\n * Timeago is a jQuery plugin that makes it easy to support automatically\n * updating fuzzy timestamps (e.g. \"4 minutes ago\" or \"about 1 day ago\").\n *\n * @name timeago\n * @version 1.3.0\n * @requires jQuery v1.2.3+\n * @author Ryan McGeary\n * @license MIT License - http://www.opensource.org/licenses/mit-license.php\n *\n * For usage and examples, visit:\n * http://timeago.yarp.com/\n *\n * Copyright (c) 2008-2013, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org)\n */\n\n(function (factory) {\n  if (typeof define === 'function' && define.amd) {\n    // AMD. Register as an anonymous module.\n    define(['jquery'], factory);\n  } else {\n    // Browser globals\n    factory(jQuery);\n  }\n}(function ($) {\n  $.timeago = function(timestamp) {\n    if (timestamp instanceof Date) {\n      return inWords(timestamp);\n    } else if (typeof timestamp === \"string\") {\n      return inWords($.timeago.parse(timestamp));\n    } else if (typeof timestamp === \"number\") {\n      return inWords(new Date(timestamp));\n    } else {\n      return inWords($.timeago.datetime(timestamp));\n    }\n  };\n  var $t = $.timeago;\n\n  $.extend($.timeago, {\n    settings: {\n      refreshMillis: 60000,\n      allowFuture: false,\n      localeTitle: false,\n      cutoff: 0,\n      strings: {\n        prefixAgo: null,\n        prefixFromNow: null,\n        suffixAgo: \"ago\",\n        suffixFromNow: \"from now\",\n        seconds: \"less than a minute\",\n        minute: \"about a minute\",\n        minutes: \"%d minutes\",\n        hour: \"about an hour\",\n        hours: \"about %d hours\",\n        day: \"a day\",\n        days: \"%d days\",\n        month: \"about a month\",\n        months: \"%d months\",\n        year: \"about a year\",\n        years: \"%d years\",\n        wordSeparator: \" \",\n        numbers: []\n      }\n    },\n    inWords: function(distanceMillis) {\n      var $l = this.settings.strings;\n      var prefix = $l.prefixAgo;\n      var suffix = $l.suffixAgo;\n      if (this.settings.allowFuture) {\n        if (distanceMillis < 0) {\n          prefix = $l.prefixFromNow;\n          suffix = $l.suffixFromNow;\n        }\n      }\n\n      var seconds = Math.abs(distanceMillis) / 1000;\n      var minutes = seconds / 60;\n      var hours = minutes / 60;\n      var days = hours / 24;\n      var years = days / 365;\n\n      function substitute(stringOrFunction, number) {\n        var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;\n        var value = ($l.numbers && $l.numbers[number]) || number;\n        return string.replace(/%d/i, value);\n      }\n\n      var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||\n        seconds < 90 && substitute($l.minute, 1) ||\n        minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||\n        minutes < 90 && substitute($l.hour, 1) ||\n        hours < 24 && substitute($l.hours, Math.round(hours)) ||\n        hours < 42 && substitute($l.day, 1) ||\n        days < 30 && substitute($l.days, Math.round(days)) ||\n        days < 45 && substitute($l.month, 1) ||\n        days < 365 && substitute($l.months, Math.round(days / 30)) ||\n        years < 1.5 && substitute($l.year, 1) ||\n        substitute($l.years, Math.round(years));\n\n      var separator = $l.wordSeparator || \"\";\n      if ($l.wordSeparator === undefined) { separator = \" \"; }\n      return $.trim([prefix, words, suffix].join(separator));\n    },\n    parse: function(iso8601) {\n      var s = $.trim(iso8601);\n      s = s.replace(/\\.\\d+/,\"\"); // remove milliseconds\n      s = s.replace(/-/,\"/\").replace(/-/,\"/\");\n      s = s.replace(/T/,\" \").replace(/Z/,\" UTC\");\n      s = s.replace(/([\\+\\-]\\d\\d)\\:?(\\d\\d)/,\" $1$2\"); // -04:00 -> -0400\n      return new Date(s);\n    },\n    datetime: function(elem) {\n      var iso8601 = $t.isTime(elem) ? $(elem).attr(\"datetime\") : $(elem).attr(\"title\");\n      return $t.parse(iso8601);\n    },\n    isTime: function(elem) {\n      // jQuery's `is()` doesn't play well with HTML5 in IE\n      return $(elem).get(0).tagName.toLowerCase() === \"time\"; // $(elem).is(\"time\");\n    }\n  });\n\n  // functions that can be called via $(el).timeago('action')\n  // init is default when no action is given\n  // functions are called with context of a single element\n  var functions = {\n    init: function(){\n      var refresh_el = $.proxy(refresh, this);\n      refresh_el();\n      var $s = $t.settings;\n      if ($s.refreshMillis > 0) {\n        setInterval(refresh_el, $s.refreshMillis);\n      }\n    },\n    update: function(time){\n      $(this).data('timeago', { datetime: $t.parse(time) });\n      refresh.apply(this);\n    },\n    updateFromDOM: function(){\n      $(this).data('timeago', { datetime: $t.parse( $t.isTime(this) ? $(this).attr(\"datetime\") : $(this).attr(\"title\") ) });\n      refresh.apply(this);\n    }\n  };\n\n  $.fn.timeago = function(action, options) {\n    var fn = action ? functions[action] : functions.init;\n    if(!fn){\n      throw new Error(\"Unknown function name '\"+ action +\"' for timeago\");\n    }\n    // each over objects here and call the requested function\n    this.each(function(){\n      fn.call(this, options);\n    });\n    return this;\n  };\n\n  function refresh() {\n    var data = prepareData(this);\n    var $s = $t.settings;\n\n    if (!isNaN(data.datetime)) {\n      if ( $s.cutoff == 0 || distance(data.datetime) < $s.cutoff) {\n        $(this).text(inWords(data.datetime));\n      }\n    }\n    return this;\n  }\n\n  function prepareData(element) {\n    element = $(element);\n    if (!element.data(\"timeago\")) {\n      element.data(\"timeago\", { datetime: $t.datetime(element) });\n      var text = $.trim(element.text());\n      if ($t.settings.localeTitle) {\n        element.attr(\"title\", element.data('timeago').datetime.toLocaleString());\n      } else if (text.length > 0 && !($t.isTime(element) && element.attr(\"title\"))) {\n        element.attr(\"title\", text);\n      }\n    }\n    return element.data(\"timeago\");\n  }\n\n  function inWords(date) {\n    return $t.inWords(distance(date));\n  }\n\n  function distance(date) {\n    return (new Date().getTime() - date.getTime());\n  }\n\n  // fix for IE6 suckage\n  document.createElement(\"abbr\");\n  document.createElement(\"time\");\n}));\n"
  },
  {
    "path": "assets/client/static/js/ngrok.js",
    "content": "var ngrok = angular.module(\"ngrok\", [\"ngSanitize\"]);\n\nvar hexRepr = function(bytes) {\n    var buf = [];\n    var ascii = [];\n    for (var i=0; i<bytes.length; ++i) {\n        var b = bytes[i];\n\n        if (!(i%8) && i!=0) {\n            buf.push(\"\\t\");\n            buf.push.apply(buf, ascii)\n            buf.push('\\n');\n            ascii = [];\n        }\n\n        if (b < 16) {\n            buf.push(\"0\");\n        }\n\n        if (b < 0x20 || b > 0x7e) {\n            ascii.push('.');\n        } else {\n            ascii.push(String.fromCharCode(b));\n        }\n\n        buf.push(b.toString(16));\n        buf.push(\" \");\n        ascii.push(\" \");\n    }\n\n    if (ascii.length > 0) {\n        var charsLeft = 8 - (ascii.length / 2);\n        for (i=0; i<charsLeft; ++i) {\n            buf.push(\"   \");\n        }\n        buf.push(\"\\t\");\n        buf.push.apply(buf, ascii);\n    }\n\n    return buf.join(\"\");\n}\n\nngrok.factory(\"txnSvc\", function() {\n    var processBody = function(body, binary) {\n        body.binary = binary;\n        body.isForm = body.ContentType == \"application/x-www-form-urlencoded\";\n        body.exists = body.Length > 0;\n        body.hasError = !!body.Error;\n\n        var syntaxClass = {\n            \"text/xml\":               \"xml\",\n            \"application/xml\":        \"xml\",\n            \"text/html\":              \"xml\",\n            \"text/css\":               \"css\",\n            \"application/json\":       \"json\",\n            \"text/javascript\":        \"javascript\",\n            \"application/javascript\": \"javascript\",\n        }[body.ContentType];\n\n        // decode body\n        if (binary) {\n            body.Text = \"\";\n        } else {\n            body.Text = Base64.decode(body.Text).text;\n        }\n\n        // prettify\n        var transform = {\n            \"xml\": \"xml\",\n            \"json\": \"json\"\n        }[syntaxClass];\n\n        if (!body.hasError && !!transform) {\n            try {\n                // vkbeautify does poorly at formatting html\n                if (body.ContentType != \"text/html\") {\n                    body.Text = vkbeautify[transform](body.Text);\n                }\n            } catch (e) {\n            }\n        }\n\n        if (!!syntaxClass) {\n            body.Text = hljs.highlight(syntaxClass, body.Text).value;\n        } else {\n            // highlight.js doesn't have a 'plaintext' syntax, so we'll just copy its escaping function.\n            body.Text = body.Text.replace(/&/gm, '&amp;').replace(/</gm, '&lt;').replace(/>/gm, '&gt;');\n        }\n    };\n\n    var processReq = function(req) {\n        if (!req.RawBytes) {\n            var decoded = Base64.decode(req.Raw);\n            req.RawBytes = hexRepr(decoded.bytes);\n\n            if (!req.Binary) {\n                req.RawText = decoded.text;\n            }\n        }\n\n        processBody(req.Body, req.Binary);\n    };\n\n    var processResp = function(resp) {\n        resp.statusClass = {\n            '2': \"text-info\",\n            '3': \"muted\",\n            '4': \"text-warning\",\n            '5': \"text-error\"\n        }[resp.Status[0]];\n\n        if (!resp.RawBytes) {\n            var decoded = Base64.decode(resp.Raw);\n            resp.RawBytes = hexRepr(decoded.bytes);\n\n            if (!resp.Binary) {\n                resp.RawText = decoded.text;\n            }\n        }\n\n        processBody(resp.Body, resp.Binary);\n    };\n\n    var processTxn = function(txn) {\n        processReq(txn.Req);\n        processResp(txn.Resp);\n    };\n\n    var preprocessTxn = function(txn) {\n        var toFixed = function(value, precision) {\n            var power = Math.pow(10, precision || 0);\n            return String(Math.round(value * power) / power);\n        }\n        // parse nanosecond count\n        var ns = txn.Duration;\n        var ms = ns / (1000 * 1000);\n        txn.Duration = ms;\n        if (ms > 1000) {\n            txn.Duration = toFixed(ms / 1000, 2) + \"s\";\n        } else {\n            txn.Duration = toFixed(ms, 2) + \"ms\";\n        }\n    };\n\n\n    var active;\n    var txns = window.data.Txns;\n    txns.forEach(function(t) {\n        preprocessTxn(t);\n    });\n\n    var activate = function(txn) {\n        if (!txn.processed) {\n            processTxn(txn);\n            txn.processed = true;\n        }\n        active = txn;\n    }\n\n    if (txns.length > 0) {\n        activate(txns[0]);\n    }\n\n    return {\n        add: function(txnData) {\n            txns.unshift(JSON.parse(txnData));\n            preprocessTxn(txns[0]);\n            if (!active) {\n                activate(txns[0]);\n            }\n        },\n        all: function() {\n            return txns;\n        },\n        active: function(txn) {\n            if (!txn) {\n                return active;\n            } else {\n                activate(txn);\n            }\n        },\n        isActive: function(txn) {\n            return !!active && txn.Id == active.Id;\n        }\n    };\n});\n\nngrok.directive({\n    \"keyval\": function() {\n        return {\n            scope: {\n                title: \"@\",\n                tuples: \"=\",\n            },\n            replace: true,\n            restrict: \"E\",\n            template: \"\" +\n            '<div ng-show=\"hasKeys()\">' +\n                '<h6>{{title}}</h6>' +\n                '<table class=\"table params\">' +\n                    '<tr ng-repeat=\"(key, value) in tuples\">' +\n                        '<th>{{ key }}</th>' +\n                        '<td>{{ value }}</td>' +\n                    '</tr>' +\n                '</table>' +\n            '</div>',\n            link: function($scope) {\n                $scope.hasKeys = function() {\n                    for (key in $scope.tuples) { return true; }\n                    return false;\n                };\n            }\n        };\n    },\n\n    \"tabs\": function() {\n        return {\n            scope: {\n                \"tabs\": \"@\",\n                \"btn\": \"@\",\n                \"onbtnclick\": \"&\"\n            },\n            replace: true,\n            template: '' +\n            '<ul class=\"nav nav-pills\">' +\n                '<li ng-repeat=\"tab in tabNames\" ng-class=\"{\\'active\\': isTab(tab)}\">' +\n                    '<a href=\"\" ng-click=\"setTab(tab)\">{{tab}}</a>' +\n                '</li>' +\n                '<li ng-show=\"!!btn\" class=\"pull-right\"> <button class=\"btn btn-primary\" ng-click=\"onbtnclick()\">{{btn}}</button></li>' +\n            '</ul>',\n            link: function postLink(scope, element, attrs) {\n                scope.tabNames = attrs.tabs.split(\",\");\n                scope.activeTab = scope.tabNames[0];\n                scope.setTab = function(t) {\n                    scope.activeTab = t;\n                };\n                scope.$parent.isTab = scope.isTab = function(t) {\n                    return t == scope.activeTab;\n                };\n            },\n        };\n    },\n\n    \"body\": function() {\n        return {\n            scope: {\n                \"body\": \"=\",\n                \"binary\": \"=\"\n            },\n            template: '' +\n            '<h6 ng-show=\"body.exists\">' +\n                '{{ body.Length }} bytes ' +\n                '{{ body.RawContentType }}' +\n            '</h6>' +\n'' +\n            '<div ng-show=\"!body.isForm && !body.binary\">' +\n                '<pre ng-show=\"body.exists\"><code ng-bind-html=\"body.Text\"></code></pre>' +\n            '</div>' +\n'' +\n            '<div ng-show=\"body.isForm\">' +\n                '<keyval title=\"Form Params\" tuples=\"body.Form\">' +\n            '</div>' +\n            '<div ng-show=\"body.hasError\" class=\"alert\">' +\n                '{{ body.Error }}' +\n            '</div>',\n\n            link: function($scope, $elem) {\n                $scope.$watch(function() { return $scope.body; }, function() {\n                    if ($scope.body && $scope.body.ErrorOffset > -1) {\n                        var offset = $scope.body.ErrorOffset;\n\n                        function textNodes(node) {\n                            var textNodes = [];\n\n                            function getTextNodes(node) {\n                                if (node.nodeType == 3) {\n                                    textNodes.push(node);\n                                } else {\n                                    for (var i = 0, len = node.childNodes.length; i < len; ++i) {\n                                        getTextNodes(node.childNodes[i]);\n                                    }\n                                }\n                            }\n\n                            getTextNodes(node);\n                            return textNodes;\n                        }\n\n                        var tNodes = textNodes($elem.find(\"code\").get(0));\n                        for (var i=0; i<tNodes.length; i++) {\n                            offset -= tNodes[i].nodeValue.length;\n                            if (offset < 0) {\n                                $(tNodes[i]).parent().css(\"background-color\", \"orange\");\n                                break;\n                            }\n                        }\n                    }\n                });\n            }\n        };\n    }\n});\n\nngrok.controller({\n    \"HttpTxns\": function($scope, txnSvc) {\n        $scope.tunnels = window.data.UiState.Tunnels;\n        $scope.txns = txnSvc.all();\n\n        if (!!window.WebSocket) {\n            var ws = new WebSocket(\"ws://\" + location.host + \"/_ws\");\n            ws.onopen = function() {\n                console.log(\"connected websocket for real-time updates\");\n            };\n\n            ws.onmessage = function(message) {\n                $scope.$apply(function() {\n                    txnSvc.add(message.data);\n                });\n            };\n\n            ws.onerror = function(err) {\n                console.log(\"Web socket error:\")\n                console.log(err);\n            };\n\n            ws.onclose = function(cls) {\n                console.log(\"Web socket closed:\" + cls);\n            };\n        }\n    },\n\n    \"HttpRequest\": function($scope, txnSvc) {\n        $scope.replay = function() {\n            $.ajax({\n                type: \"POST\",\n                url: \"/http/in/replay\",\n                data: { txnid: txnSvc.active().Id }\n            });\n        }\n        var setReq = function() {\n            var txn = txnSvc.active();\n            if (!!txn && txn.Req) {\n                $scope.Req = txnSvc.active().Req;\n            } else {\n                $scope.Req = null;\n            }\n        };\n        $scope.$watch(function() { return txnSvc.active() }, setReq);\n    },\n\n    \"HttpResponse\": function($scope, $element, txnSvc) {\n        var setResp = function() {\n            var txn = txnSvc.active();\n            if (!!txn && txn.Resp) {\n                $scope.Resp = txnSvc.active().Resp;\n            } else {\n                $scope.Resp = null;\n            }\n        };\n        $scope.$watch(function() { return txnSvc.active() }, setResp);\n    },\n\n    \"TxnNavItem\": function($scope, txnSvc) {\n        $scope.isActive = function() { return txnSvc.isActive($scope.txn); }\n        $scope.makeActive = function() {\n            txnSvc.active($scope.txn);\n        };\n    },\n\n    \"HttpTxn\": function($scope, txnSvc, $timeout) {\n        var setTxn = function() {\n            $scope.Txn = txnSvc.active();\n        };\n\n        $scope.ISO8601 = function(ts) {\n            if (!!ts) {\n                return new Date(ts * 1000).toISOString();\n            }\n        };\n\n        $scope.TimeFormat = function(ts) {\n            if (!!ts) {\n                return $.timeago($scope.ISO8601(ts));\n            }\n        };\n\n        $scope.$watch(function() { return txnSvc.active() }, setTxn);\n\n        // this causes angular to update the timestamps\n        setInterval(function() { $scope.$apply(function() {}); }, 30000);\n    },\n});\n"
  },
  {
    "path": "assets/client/static/js/vkbeautify.js",
    "content": "/**\r\n* vkBeautify - javascript plugin to pretty-print or minify text in XML, JSON, CSS and SQL formats.\r\n*  \r\n* Version - 0.99.00.beta \r\n* Copyright (c) 2012 Vadim Kiryukhin\r\n* vkiryukhin @ gmail.com\r\n* http://www.eslinstructor.net/vkbeautify/\r\n* \r\n* Dual licensed under the MIT and GPL licenses:\r\n*   http://www.opensource.org/licenses/mit-license.php\r\n*   http://www.gnu.org/licenses/gpl.html\r\n*\r\n*   Pretty print\r\n*\r\n*        vkbeautify.xml(text [,indent_pattern]);\r\n*        vkbeautify.json(text [,indent_pattern]);\r\n*        vkbeautify.css(text [,indent_pattern]);\r\n*        vkbeautify.sql(text [,indent_pattern]);\r\n*\r\n*        @text - String; text to beatufy;\r\n*        @indent_pattern - Integer | String;\r\n*                Integer:  number of white spaces;\r\n*                String:   character string to visualize indentation ( can also be a set of white spaces )\r\n*   Minify\r\n*\r\n*        vkbeautify.xmlmin(text [,preserve_comments]);\r\n*        vkbeautify.jsonmin(text);\r\n*        vkbeautify.cssmin(text [,preserve_comments]);\r\n*        vkbeautify.sqlmin(text);\r\n*\r\n*        @text - String; text to minify;\r\n*        @preserve_comments - Bool; [optional];\r\n*                Set this flag to true to prevent removing comments from @text ( minxml and mincss functions only. )\r\n*\r\n*   Examples:\r\n*        vkbeautify.xml(text); // pretty print XML\r\n*        vkbeautify.json(text, 4 ); // pretty print JSON\r\n*        vkbeautify.css(text, '. . . .'); // pretty print CSS\r\n*        vkbeautify.sql(text, '----'); // pretty print SQL\r\n*\r\n*        vkbeautify.xmlmin(text, true);// minify XML, preserve comments\r\n*        vkbeautify.jsonmin(text);// minify JSON\r\n*        vkbeautify.cssmin(text);// minify CSS, remove comments ( default )\r\n*        vkbeautify.sqlmin(text);// minify SQL\r\n*\r\n*/\r\n\r\n(function() {\r\n\r\nfunction createShiftArr(step) {\r\n\r\n\tvar space = '    ';\r\n\t\r\n\tif ( isNaN(parseInt(step)) ) {  // argument is string\r\n\t\tspace = step;\r\n\t} else { // argument is integer\r\n\t\tswitch(step) {\r\n\t\t\tcase 1: space = ' '; break;\r\n\t\t\tcase 2: space = '  '; break;\r\n\t\t\tcase 3: space = '   '; break;\r\n\t\t\tcase 4: space = '    '; break;\r\n\t\t\tcase 5: space = '     '; break;\r\n\t\t\tcase 6: space = '      '; break;\r\n\t\t\tcase 7: space = '       '; break;\r\n\t\t\tcase 8: space = '        '; break;\r\n\t\t\tcase 9: space = '         '; break;\r\n\t\t\tcase 10: space = '          '; break;\r\n\t\t\tcase 11: space = '           '; break;\r\n\t\t\tcase 12: space = '            '; break;\r\n\t\t}\r\n\t}\r\n\r\n\tvar shift = ['\\n']; // array of shifts\r\n\tfor(ix=0;ix<100;ix++){\r\n\t\tshift.push(shift[ix]+space); \r\n\t}\r\n\treturn shift;\r\n}\r\n\r\nfunction vkbeautify(){\r\n\tthis.step = '    '; // 4 spaces\r\n\tthis.shift = createShiftArr(this.step);\r\n};\r\n\r\nvkbeautify.prototype.xml = function(text,step) {\r\n\r\n\tvar ar = text.replace(/>\\s{0,}</g,\"><\")\r\n\t\t\t\t .replace(/</g,\"~::~<\")\r\n\t\t\t\t .replace(/\\s*xmlns\\:/g,\"~::~xmlns:\")\r\n\t\t\t\t .replace(/\\s*xmlns\\=/g,\"~::~xmlns=\")\r\n\t\t\t\t .split('~::~'),\r\n\t\tlen = ar.length,\r\n\t\tinComment = false,\r\n\t\tdeep = 0,\r\n\t\tstr = '',\r\n\t\tix = 0,\r\n\t\tshift = step ? createShiftArr(step) : this.shift;\r\n\r\n\t\tfor(ix=0;ix<len;ix++) {\r\n\t\t\t// start comment or <![CDATA[...]]> or <!DOCTYPE //\r\n\t\t\tif(ar[ix].search(/<!/) > -1) { \r\n\t\t\t\tstr += shift[deep]+ar[ix];\r\n\t\t\t\tinComment = true; \r\n\t\t\t\t// end comment  or <![CDATA[...]]> //\r\n\t\t\t\tif(ar[ix].search(/-->/) > -1 || ar[ix].search(/\\]>/) > -1 || ar[ix].search(/!DOCTYPE/) > -1 ) { \r\n\t\t\t\t\tinComment = false; \r\n\t\t\t\t}\r\n\t\t\t} else \r\n\t\t\t// end comment  or <![CDATA[...]]> //\r\n\t\t\tif(ar[ix].search(/-->/) > -1 || ar[ix].search(/\\]>/) > -1) { \r\n\t\t\t\tstr += ar[ix];\r\n\t\t\t\tinComment = false; \r\n\t\t\t} else \r\n\t\t\t// <elm></elm> //\r\n\t\t\tif( /^<\\w/.exec(ar[ix-1]) && /^<\\/\\w/.exec(ar[ix]) &&\r\n\t\t\t\t/^<[\\w:\\-\\.\\,]+/.exec(ar[ix-1]) == /^<\\/[\\w:\\-\\.\\,]+/.exec(ar[ix])[0].replace('/','')) { \r\n\t\t\t\tstr += ar[ix];\r\n\t\t\t\tif(!inComment) deep--;\r\n\t\t\t} else\r\n\t\t\t // <elm> //\r\n\t\t\tif(ar[ix].search(/<\\w/) > -1 && ar[ix].search(/<\\//) == -1 && ar[ix].search(/\\/>/) == -1 ) {\r\n\t\t\t\tstr = !inComment ? str += shift[deep++]+ar[ix] : str += ar[ix];\r\n\t\t\t} else \r\n\t\t\t // <elm>...</elm> //\r\n\t\t\tif(ar[ix].search(/<\\w/) > -1 && ar[ix].search(/<\\//) > -1) {\r\n\t\t\t\tstr = !inComment ? str += shift[deep]+ar[ix] : str += ar[ix];\r\n\t\t\t} else \r\n\t\t\t// </elm> //\r\n\t\t\tif(ar[ix].search(/<\\//) > -1) { \r\n\t\t\t\tstr = !inComment ? str += shift[--deep]+ar[ix] : str += ar[ix];\r\n\t\t\t} else \r\n\t\t\t// <elm/> //\r\n\t\t\tif(ar[ix].search(/\\/>/) > -1 ) { \r\n\t\t\t\tstr = !inComment ? str += shift[deep]+ar[ix] : str += ar[ix];\r\n\t\t\t} else \r\n\t\t\t// <? xml ... ?> //\r\n\t\t\tif(ar[ix].search(/<\\?/) > -1) { \r\n\t\t\t\tstr += shift[deep]+ar[ix];\r\n\t\t\t} else \r\n\t\t\t// xmlns //\r\n\t\t\tif( ar[ix].search(/xmlns\\:/) > -1  || ar[ix].search(/xmlns\\=/) > -1) { \r\n\t\t\t\tstr += shift[deep]+ar[ix];\r\n\t\t\t} \r\n\t\t\t\r\n\t\t\telse {\r\n\t\t\t\tstr += ar[ix];\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\treturn  (str[0] == '\\n') ? str.slice(1) : str;\r\n}\r\n\r\nvkbeautify.prototype.json = function(text,step) {\r\n\r\n\tvar step = step ? step : this.step;\r\n\t\r\n\tif (typeof JSON === 'undefined' ) return text; \r\n\t\r\n\tif ( typeof text === \"string\" ) return JSON.stringify(JSON.parse(text), null, step);\r\n\tif ( typeof text === \"object\" ) return JSON.stringify(text, null, step);\r\n\t\t\r\n\treturn text; // text is not string nor object\r\n}\r\n\r\nvkbeautify.prototype.css = function(text, step) {\r\n\r\n\tvar ar = text.replace(/\\s{1,}/g,' ')\r\n\t\t\t\t.replace(/\\{/g,\"{~::~\")\r\n\t\t\t\t.replace(/\\}/g,\"~::~}~::~\")\r\n\t\t\t\t.replace(/\\;/g,\";~::~\")\r\n\t\t\t\t.replace(/\\/\\*/g,\"~::~/*\")\r\n\t\t\t\t.replace(/\\*\\//g,\"*/~::~\")\r\n\t\t\t\t.replace(/~::~\\s{0,}~::~/g,\"~::~\")\r\n\t\t\t\t.split('~::~'),\r\n\t\tlen = ar.length,\r\n\t\tdeep = 0,\r\n\t\tstr = '',\r\n\t\tix = 0,\r\n\t\tshift = step ? createShiftArr(step) : this.shift;\r\n\t\t\r\n\t\tfor(ix=0;ix<len;ix++) {\r\n\r\n\t\t\tif( /\\{/.exec(ar[ix]))  { \r\n\t\t\t\tstr += shift[deep++]+ar[ix];\r\n\t\t\t} else \r\n\t\t\tif( /\\}/.exec(ar[ix]))  { \r\n\t\t\t\tstr += shift[--deep]+ar[ix];\r\n\t\t\t} else\r\n\t\t\tif( /\\*\\\\/.exec(ar[ix]))  { \r\n\t\t\t\tstr += shift[deep]+ar[ix];\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tstr += shift[deep]+ar[ix];\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn str.replace(/^\\n{1,}/,'');\r\n}\r\n\r\n//----------------------------------------------------------------------------\r\n\r\nfunction isSubquery(str, parenthesisLevel) {\r\n\treturn  parenthesisLevel - (str.replace(/\\(/g,'').length - str.replace(/\\)/g,'').length )\r\n}\r\n\r\nfunction split_sql(str, tab) {\r\n\r\n\treturn str.replace(/\\s{1,}/g,\" \")\r\n\r\n\t\t\t\t.replace(/ AND /ig,\"~::~\"+tab+tab+\"AND \")\r\n\t\t\t\t.replace(/ BETWEEN /ig,\"~::~\"+tab+\"BETWEEN \")\r\n\t\t\t\t.replace(/ CASE /ig,\"~::~\"+tab+\"CASE \")\r\n\t\t\t\t.replace(/ ELSE /ig,\"~::~\"+tab+\"ELSE \")\r\n\t\t\t\t.replace(/ END /ig,\"~::~\"+tab+\"END \")\r\n\t\t\t\t.replace(/ FROM /ig,\"~::~FROM \")\r\n\t\t\t\t.replace(/ GROUP\\s{1,}BY/ig,\"~::~GROUP BY \")\r\n\t\t\t\t.replace(/ HAVING /ig,\"~::~HAVING \")\r\n\t\t\t\t//.replace(/ SET /ig,\" SET~::~\")\r\n\t\t\t\t.replace(/ IN /ig,\" IN \")\r\n\t\t\t\t\r\n\t\t\t\t.replace(/ JOIN /ig,\"~::~JOIN \")\r\n\t\t\t\t.replace(/ CROSS~::~{1,}JOIN /ig,\"~::~CROSS JOIN \")\r\n\t\t\t\t.replace(/ INNER~::~{1,}JOIN /ig,\"~::~INNER JOIN \")\r\n\t\t\t\t.replace(/ LEFT~::~{1,}JOIN /ig,\"~::~LEFT JOIN \")\r\n\t\t\t\t.replace(/ RIGHT~::~{1,}JOIN /ig,\"~::~RIGHT JOIN \")\r\n\t\t\t\t\r\n\t\t\t\t.replace(/ ON /ig,\"~::~\"+tab+\"ON \")\r\n\t\t\t\t.replace(/ OR /ig,\"~::~\"+tab+tab+\"OR \")\r\n\t\t\t\t.replace(/ ORDER\\s{1,}BY/ig,\"~::~ORDER BY \")\r\n\t\t\t\t.replace(/ OVER /ig,\"~::~\"+tab+\"OVER \")\r\n\r\n\t\t\t\t.replace(/\\(\\s{0,}SELECT /ig,\"~::~(SELECT \")\r\n\t\t\t\t.replace(/\\)\\s{0,}SELECT /ig,\")~::~SELECT \")\r\n\t\t\t\t\r\n\t\t\t\t.replace(/ THEN /ig,\" THEN~::~\"+tab+\"\")\r\n\t\t\t\t.replace(/ UNION /ig,\"~::~UNION~::~\")\r\n\t\t\t\t.replace(/ USING /ig,\"~::~USING \")\r\n\t\t\t\t.replace(/ WHEN /ig,\"~::~\"+tab+\"WHEN \")\r\n\t\t\t\t.replace(/ WHERE /ig,\"~::~WHERE \")\r\n\t\t\t\t.replace(/ WITH /ig,\"~::~WITH \")\r\n\t\t\t\t\r\n\t\t\t\t//.replace(/\\,\\s{0,}\\(/ig,\",~::~( \")\r\n\t\t\t\t//.replace(/\\,/ig,\",~::~\"+tab+tab+\"\")\r\n\r\n\t\t\t\t.replace(/ ALL /ig,\" ALL \")\r\n\t\t\t\t.replace(/ AS /ig,\" AS \")\r\n\t\t\t\t.replace(/ ASC /ig,\" ASC \")\t\r\n\t\t\t\t.replace(/ DESC /ig,\" DESC \")\t\r\n\t\t\t\t.replace(/ DISTINCT /ig,\" DISTINCT \")\r\n\t\t\t\t.replace(/ EXISTS /ig,\" EXISTS \")\r\n\t\t\t\t.replace(/ NOT /ig,\" NOT \")\r\n\t\t\t\t.replace(/ NULL /ig,\" NULL \")\r\n\t\t\t\t.replace(/ LIKE /ig,\" LIKE \")\r\n\t\t\t\t.replace(/\\s{0,}SELECT /ig,\"SELECT \")\r\n\t\t\t\t.replace(/\\s{0,}UPDATE /ig,\"UPDATE \")\r\n\t\t\t\t.replace(/ SET /ig,\" SET \")\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t.replace(/~::~{1,}/g,\"~::~\")\r\n\t\t\t\t.split('~::~');\r\n}\r\n\r\nvkbeautify.prototype.sql = function(text,step) {\r\n\r\n\tvar ar_by_quote = text.replace(/\\s{1,}/g,\" \")\r\n\t\t\t\t\t\t\t.replace(/\\'/ig,\"~::~\\'\")\r\n\t\t\t\t\t\t\t.split('~::~'),\r\n\t\tlen = ar_by_quote.length,\r\n\t\tar = [],\r\n\t\tdeep = 0,\r\n\t\ttab = this.step,//+this.step,\r\n\t\tinComment = true,\r\n\t\tinQuote = false,\r\n\t\tparenthesisLevel = 0,\r\n\t\tstr = '',\r\n\t\tix = 0,\r\n\t\tshift = step ? createShiftArr(step) : this.shift;;\r\n\r\n\t\tfor(ix=0;ix<len;ix++) {\r\n\t\t\tif(ix%2) {\r\n\t\t\t\tar = ar.concat(ar_by_quote[ix]);\r\n\t\t\t} else {\r\n\t\t\t\tar = ar.concat(split_sql(ar_by_quote[ix], tab) );\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tlen = ar.length;\r\n\t\tfor(ix=0;ix<len;ix++) {\r\n\t\t\t\r\n\t\t\tparenthesisLevel = isSubquery(ar[ix], parenthesisLevel);\r\n\t\t\t\r\n\t\t\tif( /\\s{0,}\\s{0,}SELECT\\s{0,}/.exec(ar[ix]))  { \r\n\t\t\t\tar[ix] = ar[ix].replace(/\\,/g,\",\\n\"+tab+tab+\"\")\r\n\t\t\t} \r\n\t\t\t\r\n\t\t\tif( /\\s{0,}\\s{0,}SET\\s{0,}/.exec(ar[ix]))  { \r\n\t\t\t\tar[ix] = ar[ix].replace(/\\,/g,\",\\n\"+tab+tab+\"\")\r\n\t\t\t} \r\n\t\t\t\r\n\t\t\tif( /\\s{0,}\\(\\s{0,}SELECT\\s{0,}/.exec(ar[ix]))  { \r\n\t\t\t\tdeep++;\r\n\t\t\t\tstr += shift[deep]+ar[ix];\r\n\t\t\t} else \r\n\t\t\tif( /\\'/.exec(ar[ix]) )  { \r\n\t\t\t\tif(parenthesisLevel<1 && deep) {\r\n\t\t\t\t\tdeep--;\r\n\t\t\t\t}\r\n\t\t\t\tstr += ar[ix];\r\n\t\t\t}\r\n\t\t\telse  { \r\n\t\t\t\tstr += shift[deep]+ar[ix];\r\n\t\t\t\tif(parenthesisLevel<1 && deep) {\r\n\t\t\t\t\tdeep--;\r\n\t\t\t\t}\r\n\t\t\t} \r\n\t\t\tvar junk = 0;\r\n\t\t}\r\n\r\n\t\tstr = str.replace(/^\\n{1,}/,'').replace(/\\n{1,}/g,\"\\n\");\r\n\t\treturn str;\r\n}\r\n\r\n\r\nvkbeautify.prototype.xmlmin = function(text, preserveComments) {\r\n\r\n\tvar str = preserveComments ? text\r\n\t\t\t\t\t\t\t   : text.replace(/\\<![ \\r\\n\\t]*(--([^\\-]|[\\r\\n]|-[^\\-])*--[ \\r\\n\\t]*)\\>/g,\"\")\r\n\t\t\t\t\t\t\t\t\t .replace(/[ \\r\\n\\t]{1,}xmlns/g, ' xmlns');\r\n\treturn  str.replace(/>\\s{0,}</g,\"><\"); \r\n}\r\n\r\nvkbeautify.prototype.jsonmin = function(text) {\r\n\r\n\tif (typeof JSON === 'undefined' ) return text; \r\n\t\r\n\treturn JSON.stringify(JSON.parse(text), null, 0); \r\n\t\t\t\t\r\n}\r\n\r\nvkbeautify.prototype.cssmin = function(text, preserveComments) {\r\n\t\r\n\tvar str = preserveComments ? text\r\n\t\t\t\t\t\t\t   : text.replace(/\\/\\*([^*]|[\\r\\n]|(\\*+([^*/]|[\\r\\n])))*\\*+\\//g,\"\") ;\r\n\r\n\treturn str.replace(/\\s{1,}/g,' ')\r\n\t\t\t  .replace(/\\{\\s{1,}/g,\"{\")\r\n\t\t\t  .replace(/\\}\\s{1,}/g,\"}\")\r\n\t\t\t  .replace(/\\;\\s{1,}/g,\";\")\r\n\t\t\t  .replace(/\\/\\*\\s{1,}/g,\"/*\")\r\n\t\t\t  .replace(/\\*\\/\\s{1,}/g,\"*/\");\r\n}\r\n\r\nvkbeautify.prototype.sqlmin = function(text) {\r\n\treturn text.replace(/\\s{1,}/g,\" \").replace(/\\s{1,}\\(/,\"(\").replace(/\\s{1,}\\)/,\")\");\r\n}\r\n\r\nwindow.vkbeautify = new vkbeautify();\r\n\r\n})();\r\n\r\n"
  },
  {
    "path": "assets/client/tls/ngrokroot.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU\nMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs\nIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290\nMB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux\nFDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h\nbCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v\ndDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt\nH7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9\nuMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX\nmk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX\na0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN\nE0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0\nWicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD\nVR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0\nJvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU\ncnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx\nIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN\nAQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH\nYINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5\n6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC\nNr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX\nc4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a\nmnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "assets/client/tls/snakeoilca.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFFDCCAvwCCQCkbN0RG/o15DANBgkqhkiG9w0BAQUFADBMMQswCQYDVQQGEwJV\nUzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEChMJbmdyb2suY29tMRQwEgYD\nVQQDFAsqLm5ncm9rLmNvbTAeFw0xMzA2MDMwMzUxNTZaFw0yMzA2MDEwMzUxNTZa\nMEwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRIwEAYDVQQKEwlu\nZ3Jvay5jb20xFDASBgNVBAMUCyoubmdyb2suY29tMIICIjANBgkqhkiG9w0BAQEF\nAAOCAg8AMIICCgKCAgEA6QryXeKl8AWWa9uG2UbSOpooH74zLkXs3FZfk9gKvqki\nzXXQCRmtU6Dyn0+OuS3sE/rRmZAsSjkQG/YDtdE/SgL4dV6S62qiQngPokjR0USh\nPC4Hwb8TjM9W5Cd+owVzMQ0vl0AYhQk8Yc/0vX+zDOwmRWGjNKPq422usF9CJFc/\n8QY+ODJDHun8VVAkq3XfcPXgytHIqxvSJnYgDouFCA+GTsKp/65S5cigSlIrQZbH\n775cTWhCjvYnq6gzyrk3RiGdb1IGuIJftMJxuJyJVbfTFtqgMGTmjHZxiLvM7dz7\nj/bmrz4PvnhbQSZZLhsvP1o8mxnoNMpo/To5tHp/Ts6b5FQNL7FHpmOVLAoQ3FdX\nVryTjoSjiE3JLDGZINQ9MFEPgPzR8mrzqFo/6e7uB4AYlKoQo01Kzx+YmVwRKEtr\nVCTRZRcl66+gMkcX0ryoVnggjIWWu4d8uAh3jf++Kd0Djb/l7DCPpEgSJwZYTjCL\nZ6SxiBwQ4o9dEQadMGgk3tlDFCBsrHoq7NyzvXHP0BF2HKb8KREBEKCIuQj9RChW\ng07zmOpjngWs0CXaYly+TDP+5DZCMGD9kmXkQY9q/zqqvMt+T+/TBK9lwUsoi2Uc\nv93wS+TNu06aRopqPo9YZr38ka3xKPiO964pk2BoFN57g767G8k9TbhkBxitvFUC\nAwEAATANBgkqhkiG9w0BAQUFAAOCAgEARf/hVYntzUwFUgQrWD0l/UaCBgrlvxVC\nyUa8Isj3vezAhFSyZntEL+ELFv8vvQbtBGHH/tCn76WuqjwOjVL23yxkaJsrnR9+\nTRNFnVeB8157+IF6HKzLCL/HIAiQ0kw/2OSLD0lZnAkg24A0/9SHcpI5GA0WlThE\n4GqgcUiN9m+mL8jWG3gj+SXC7IcVS3vAvS1J7Kz0NzTh1dYkQNWJlauO2Qn95T99\nplkPPh87yZO9a9bxpX9PUJkTJzOwUkZISRZEbTA0CfspUpq/phzuTViy7o2fr+To\nxVVa2aKT912vlQadWw5oqEK7xyxTqsYwV7CUljtCnhpS7wOZhwzI3qUk9HKH0Rt9\n/HQsANuSikZosNvdM3/hv3c5DRUOwKiKdbgZCyqQf8XSeJRM1iQvcHo8U8kEJEEt\ndmftn+0gH3RPsV028+7XD6wraqfc7dzNzLnq2rDLSAfG3T7pp0n19JyUieWsTR45\npGcwNpXDk+weqQKknwbNoha3o51Xq/1nhRtqrUPIEgOnBWBCV8vkhXMh9Vbe+oAm\nLmweN1Mr6MjrHWddVn+JGcB5p+AMWr8zE+bhpPCAnupFR98z3fbOleMCWq6Q+hMP\nMUThqJROHamHRIJ3Iz8whIrj7EKkDBKLEfE1ExS3B9VQ+YdHy6sjexdmCIQgElq4\n4SMuY/JkZlo=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "assets/server/tls/snakeoil.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFDDCCAvQCAQEwDQYJKoZIhvcNAQEFBQAwTDELMAkGA1UEBhMCVVMxEzARBgNV\nBAgTCkNhbGlmb3JuaWExEjAQBgNVBAoTCW5ncm9rLmNvbTEUMBIGA1UEAxQLKi5u\nZ3Jvay5jb20wHhcNMTMwNjAzMDQyMzAwWhcNMjMwNjAxMDQyMzAwWjBMMQswCQYD\nVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEChMJbmdyb2suY29t\nMRQwEgYDVQQDFAsqLm5ncm9rLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC\nAgoCggIBANouXC6wJacPIFYEiKIGGhliShxsy181LJ064ZTD3ARMTi9ONOsRkHXT\noM0CV1ZNDniPp3JILiGL52FPp0hdJegQ+pzz/gDjX0h9MCWexM4sqLpY0TfEqk9l\nHWKPJg+ODK8wa1/wuQOK++m/XY3wcbZgLj23EHcGybl/xGJkaeaqGNvrJo/4RgSL\n/jTpp0dDWKUmKBpTH4PuD6bkl8qKQAP1eul6ZS+Gr/llFYJWqwgd3SD3Wh/61Y4n\n6sDusem2NQltnlFwe5VS1LfopVtO3e2N5fveEUdbg2YDs7evNYVK+1i0kjo0dD3D\ntbmhe13OQ/baufxcHc1xjtGsJ57ws8nePL4eo+vFuD5Qjc9ESfzunSem0hC5AJgF\nrsWzIYsr7UsiuKG4gQdD17L+DogVCqRcdYXugrPEJONi8YG3eRRP/Q9klX4PWy7O\nZj7KX/xBrvS5GeDNqrDVsPL0W24FJ//Sus7BSF0fc5DSYqtD4Zgf1GRi0+xSa3m9\nAgJwWOHm6iC0mHwddozPLd+CP/M7PYzPRDiWY/ehoCFiqRAglYO0mhKyJvoTOjq6\nTBYYp/8LcbqfqUkI6DSnZ2fNdXKDyirMSOT4K1ug1MHaq8obucLqx+5Hie5d6CKR\ngnN15sJwGKIy27fg6LAzi1GWuCzZ1kHqjuQh3oVfuBKnFfsdLUnFAgMBAAEwDQYJ\nKoZIhvcNAQEFBQADggIBADael2+SR8LC5xNCBcu4eqmil30Whb7qqr16EHU/MSQb\nnmFMxBqTa2B8RDZIIkb7LukH8rsAdU8Bzkc2yRdjoAfEMMcJA/fMpwuaXI5cuaKV\nidZFpNUyR+K5UG/CntcCvwzZp4//g+LVK9qPDZ2BJmA/PMR6OphRwRwG+ruSLUCi\nywgFFhNlPMpPZ49vsFm/Q0A4JmLpZXaARt53zNbNiHT1FgTP/9L1HIpkbfoFQT6R\npB/VYe8O+GmrwaL3l+L8aO07JRM+u0OKNPxOgxWgE7UngiWulXpnIHAXYxTVMQ8V\n1IYutv9bYJ/+TAy9Kxzc4Q33K+5qvS58GCAjOF10NnHkiTQPbhUWISoj21eZJ1hb\nZ4BbC5z/Y8zNbMao0ACF0QmlnXlt0YrkjkcZQuqerzpC6YMw32vsemREc55X9UQM\nmVbnDah4xyPM+yNIg/uKHVTYJ6+4TttWEp1JGRaP2EOYr0yGe4XFFrKIPH2DlApM\nnNfqR+Mfx9WZS3n6FuWPGWUDBye2fdxOjW9Jwc/+JDR3BsS65LlzPrs6C8TcNnnD\nEdWJ4klYzWkuSdUiV1EXbsB1sSIKmUud2f4vJuOqlBsgS8/mTxjk123uXaN9zaN6\nA9cMWQI2MbobK0HErkk0QyNTtVTKumEEko/c2ktsn8lrdJcKqCeQ9EKwp6r0Lmtp\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "assets/server/tls/snakeoil.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIJKAIBAAKCAgEA2i5cLrAlpw8gVgSIogYaGWJKHGzLXzUsnTrhlMPcBExOL040\n6xGQddOgzQJXVk0OeI+nckguIYvnYU+nSF0l6BD6nPP+AONfSH0wJZ7EziyouljR\nN8SqT2UdYo8mD44MrzBrX/C5A4r76b9djfBxtmAuPbcQdwbJuX/EYmRp5qoY2+sm\nj/hGBIv+NOmnR0NYpSYoGlMfg+4PpuSXyopAA/V66XplL4av+WUVglarCB3dIPda\nH/rVjifqwO6x6bY1CW2eUXB7lVLUt+ilW07d7Y3l+94RR1uDZgOzt681hUr7WLSS\nOjR0PcO1uaF7Xc5D9tq5/FwdzXGO0awnnvCzyd48vh6j68W4PlCNz0RJ/O6dJ6bS\nELkAmAWuxbMhiyvtSyK4obiBB0PXsv4OiBUKpFx1he6Cs8Qk42Lxgbd5FE/9D2SV\nfg9bLs5mPspf/EGu9LkZ4M2qsNWw8vRbbgUn/9K6zsFIXR9zkNJiq0PhmB/UZGLT\n7FJreb0CAnBY4ebqILSYfB12jM8t34I/8zs9jM9EOJZj96GgIWKpECCVg7SaErIm\n+hM6OrpMFhin/wtxup+pSQjoNKdnZ811coPKKsxI5PgrW6DUwdqryhu5wurH7keJ\n7l3oIpGCc3XmwnAYojLbt+DosDOLUZa4LNnWQeqO5CHehV+4EqcV+x0tScUCAwEA\nAQKCAgBb6Ru8L0gtUBn3IoHMf3WPK/C8eLhTqzrYIW3WFYwh42MsWm3AeO26NSSQ\nOGRCXsOx1hJb+jw0tZMLU1rNCTBmyoBIjiB6j04cY2Bc+L0/fWC236ODMr3sJFR0\nqIkIFHcTdfpFuEq4S1xD4/GtUZUVlv7j0LKG8b0Y/9HjARn7qbw/KJheHeChGbhE\n4gkt5Bj7uU87h7jHAwpk6/dlw0ekY00b/guSMdL/5K1i8s+p46q7sHeu8SP1dqtW\nCze3lKJTDnKbLB9jkDk8IC1IgbjL0fMIX0w4Gz0HRJf40T5ioGuxup+/FUnCmyd6\nw6QMqE/JNesTfFqxqRzZBwTJ1+xkXkSFmFhZ9McfxmEtozau0LH6TtkGmHvfmn6/\nI8ImlIRyHGOUVHsEAR8c7aD9dag0LbXmjyolfin8Dow130/G3hTXYnDETx66k1RT\nBh6kDEYvuxvFqkXYKRAu5u11vQA9udYqN4i+htQ2OOYOEKNX8Kxy562rXgd85MyT\neTL4Dmh/6J7VtWG4nN2G8pnj5Lg99Q8fzEFQmFLFcK0kJwHu04pJaqrHL+uHHd5T\nKOfPAr0pUbeu2sVd9chgDhADUE1/8YPhSCsHudM4No4tDCZVEqKJ1pGLCpUw3H1v\nh0aA/B582p2CGFpGoInKfFKvUbYt2neDZZQygmYCUHywqNxQAQKCAQEA/H0MCJYV\nVRRLufZkz3fpC+wT+coqbguwrT2bpVBuNdvyDKYIkbcSDtIVNsO/1p1/CGormDll\nk1jNFhR+lqBB8QQfb1ZetPohxzPNncPiJypb9Uivu7GG1MNUHJUecFt8uITcY93s\nUgRBVsCpLgUP7SF6m1Mpu2BaXQ/sEM8dYCSmt4Fax7LW00cIpLleZmCXoc/jsTUM\nixYTM5r5UWXBhl196Lfu+WjTmoxGgFeTwF8x1wuo7mQvPfripvUX/bjGR4o5JQOw\n1I0XK13yrhFkEAIV61nHBGiyhmJShPaSI2uDudx0ZkfuUIM1UFvqeqWsjATaLmOW\nX7lE0qNg75lsRQKCAQEA3Tcqgviss9a4Owr0BagleObUnTMFzk8bgpemNdzi47eG\n7C572Wx8Gs6XgNbO28lpE0PD7KU/Rvt36wP9mzGiP0M3Zr4h81AfLvftoMMs6ytk\nwHJOdtPUCZqvnblvKOI409nGnoUD+3nfH/qqHk4ZL3fn4vHbxwModZfOXRrH2hJr\nEVqAnnexi9S0JbJJxdcSlBMI5vQMKGLCj52C2mfcF+XRMuF88bwfJ7Xcb3yhyv8i\npXHHZZ9QKE7eG76UO+EGKz8gn3wWKuh5HXp3HoyKidgzXtPIbu8fd4UzXl/BTMFc\njWAG0Ycsm5dTobQ0Yo0Xeju5Hs5QCtItY7OAFsH/gQKCAQBBCTC9UXNjO9wZpYbo\nDdoAkSnAELwHJom2xgS+e044H1Rkv6u7ZO2I1cJTHe7fKChdkYNzLW2lm50QD+1f\nfR4fJ9G1CwlQEpH6zrQq7Bbnwbh4IOXrMdoqGbojtqFljZs9qDNgofxKUABIiU3K\npdEpYpNDSROZyULdb8l9tuu5JRewcuhgQgel2kk2rOzM8Bp+up7KuYBmnyQJCeUo\ne05y/sf81sv+gGrpBzLtwiEzzxF2c/FqnnGwxFv3Z3BrkVm5ebgoeZ/l0AXkzMlC\n3wXoPbFJsxFZaGJ7zP22dBDGgN4oVMnCwsp3AKUN8u8d8mjUlDdi9ZH5TC6XFzBT\n5zAFAoIBAFCjlXmc0MfV096iBYYyX0aNTp/nQ4yLRcn7IfmshYDhG+vonfkKFMto\n1819gHaaGxWMtFUFf+WOMY6YK9Bw7WYGSKHJWXLqmBN1CUh7HVq0vMtyX6vtV/QQ\nUUg7movau0BuuHp8npEDQhTUOUNG0ON+4CbYZ3dKbWtAZVeHNacG48S1qwEZPL1u\nUiUTstTNq9YSgkI+YFgweCAGGPcouRB1FCdqDzPHkcvV/X8efZQUITsSGM+wnXW0\nGj8e38ZcJvWI04mPoD0P9WaLh/S44p+REljU9tGJlXzqL2mNmlcyfVyDzrh+gAJP\nzYq6uAXczNwf/UF/j6oCJ82aV2z0VwECggEBAJga2Kb1ovNLMQ8Vum1tE8BOku9D\nhTVpz+sFugZuY5Dzl9sPGT3UXGjZZ8pGEGpLPQK2YP5yc+ehCng3ePtXV0sPUR5W\n17jibM2JYFkR9RJCCA8c++2OwxZTVJcSDUkHG7cpl5QLbzOJAWG78EziiNqZEyC0\nRsYKPad6e0enYtPvYlnCYr77Y4KN6mU5Ot3OzqGeP4++dUMznL21vb2wmO6xHLR0\nMwa6w7fIXYNAOuCk0GMeYzSHZT7ZYytUUhiiTVbHZuazJR6JyR7yf3dnjkPfRVHe\nqLsKIVNbyWayop61fNZWfN4FgLMmUDzCNNDz9sFX09Jm1QaEo+Dun7Dw8Ss=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "contrib/com.ngrok.client.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- \nThis is an example launchd script for MacOS. It was written under 10.9.1. \nYou'll have to modify some arguments below to get this working on your box.\nUnfortunetly I was unable to get any environment variables working in the launchd script.\n-->\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>KeepAlive</key>\n\t<true/>\n\t<key>Label</key>\n\t<string>com.ngrok.client</string>\n\t<key>ProgramArguments</key>\n\t<array>\n\t\t<string>/usr/local/bin/ngrok</string>\n\t\t<string>-log</string>\n\t\t<string>stdout</string>\n\t\t<string>-subdomain</string>\n\t\t<string>mySubDomain</string>\n\t\t<string>80</string>\n\t</array>\n\t<key>StandardOutPath</key>\n\t<string>/tmp/ngrok.log</string>\n\t<key>StandardErrorPath</key>\n\t<string>/tmp/ngrok.log</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "docs/CHANGELOG.md",
    "content": "# Changelog\n## 1.7 - 6/6/2014\n- IMPROVEMENT: Print a better help message when run without any arguments\n- IMPROVEMENT: Display useful help message and instructions when double-clicked from explorer on Windows\n- IMPROVEMENT: ngrok now uses the specified server_addr to set the SNI header instead of forcing ngrokd.ngrok.com\n- IMPROVEMENT: ngrok now uses equinox.io for automatic updates with greater speed and safety\n- IMPROVEMENT: Many documentation improvements\n- IMPROVEMENT: Added example plist file for autostart on OS X\n- BUGFIX: Fixed an issue where ngrok could crash when parsing some websocket requests\n- BUGFIX: Fixed an issue where the web UI would truncate the raw request to 8192 bytes\n- BUGFIX: Fixed an issue where ngrok could not replay requests where the request was larger than 8192 bytes\n- BUGFIX: Fixed an issue where the web UI would not update in realtime when not accessed over localhost\n- BUGFIX: Fixed an unlikely race condition in ngrokd when loading the tunnel URL cache\n- BUGFIX: Check for a valid server address without trying to resolve for less confusing errors\n\n## 1.6 -  10/25/2013\n- BUGFIX: Fixed a goroutine/memory leak in ngrok/proto's parsing of http traffic\n- IMPROVEMENT: The web inspection API can now be disabled again by setting inspect_addr: disabled in the config file\n\n## 1.5 - 10/20/2013\n- FEATURE: Added support a \"remote_port\" configuration parameter that lets you request a specific remote port for TCP tunnels\n- IMPROVEMENT: Upload instructions on crash reports are displayed after the dump where it is more likely to be seen\n- IMPROVEMENT: Improvements to ngrok's logging for easier debugging\n- IMPROVEMENT: Batch metric reporting to Keen to not be limited by the speed of their API at high request loads\n- IMPROVEMENT: Added additional safety to ensure the server doesn't crash on panics()\n- BUGFIX: Fixed an issue with prefetching tunnel connections that could hang tunnel connections when behind an aggressive NAT\n- BUGFIX: Fixed a race condition where ngrokd could send back a different message instead of AuthResp first\n- BUGFIX: Fixed an issue where under some circumstances, reconnecting would fail and tell the client the tunnels were still in use\n- BUGFIX: Fixed an issue where a race-condition with handling pings could cause a tunnel to hang forever and stop handling requests\n\n## 1.4 - 09/27/2013\n- BUGFIX: Fixed an issue where long URL paths were not truncated in the terminal UI\n- BUGFIX: Fixed an issue where long URL paths ruined the web UI's formatting\n- BUGFIX: Fixed an issue where authtokens would not be remembered if an existing configuration file didn't exist\n\n## 0.23 - 09/06/2013\n- BUGFIX: Fixed a bug which caused some important HTTP headers to be omitted from request introspection and replay\n\n## 0.22 - 09/04/2013\n- FEATURE: ngrok now tunnels websocket requests\n\n## 0.21 - 08/17/2013\n- IMPROVEMENT: The ngrok web ui can now be disabled with -webport=-1\n\n## 0.20 - 08/17/2013\n- BUGFIX: Fixed a bug where ngrok would not stop its autoupdate loop even after it should stop\n\n## 0.19 - 08/17/2013\n- BUGFIX: Fixed a bug where ngrok's would loop infinitely trying to checking for updates after the second update check\n- BUGFIX: Fixed a race condition in ngrokd's metrics logging immediately after start up\n\n## 0.18 - 08/15/2013\n- BUGFIX: Fixed a bug where ngrok would compare the Host header for virtual hosting using case-sensitive comparisons\n- BUGFIX: Fixed a bug where ngrok would not include the port number in the virtual host when not serving on port 80\n- BUGFIX: Fixed a bug where ngrok would crash when trying to replay a request\n- IMPROVEMENT: ngrok can now indicate manual updates again\n- IMPROVEMENT: ngrok can now supports update channels\n- IMPROVEMENT: ngrok can now detect some updates that will fail before downloading\n\n## 0.17 - 07/30/2013\n- BUGFIX: Fixed an issue where ngrok's registry cache would return a URL from a different protocol\n\n## 0.16 - 07/30/2013\n- BUGFIX: Fixed an issue where ngrok would crash when parsing bad XML that wasn't a syntax error\n- BUGFIX: Fixed an issue where ngrok would crash when parsing bad JSON that wasn't a syntax error\n- BUGFIX: Fixed an issue where the web ui would sometimes not update the request body when changing requests\n- BUGFIX: Fixed an issue where ngrokd's registry cache would not load from file\n- BUGFIX: Fixed an issue where ngrokd's registry cache would not save to file\n- BUGFIX: Fixed an issue where ngrok would refuse requests with an Authorization header if no HTTP auth was specified.\n- BUGFIX: Fixed a bug where ngrok would fail to cross-compile in you hadn't compiled natively first\n- IMPROVEMENT: ngrok's registry cache now handles and attempts to restore TCP URLs\n- IMPROVEMENT: Added simple Travis CI integration to make sure ngrok compiles\n\n## 0.15 - 07/27/2013\n- FEATURE: ngrok can now update itself automatically\n\n## 0.14 - 07/03/2013\n- BUGFIX: Fix an issue where ngrok could never save/load the authtoken file on linux\n- BUGFIX: Fix an issue where ngrok wouldn't emit log messages while loading authtokens\n\n## 0.13 - 07/02/2013\n- FEATURE: -hostname switch on client allows you to run tunnels over custom domains (requires you CNAME your DNS)\n- IMPROVEMENT: ngrok client UI now shows the client IP address for a request\n- IMPROVEMENT: ngrok client UI now shows how long ago a request was made (uservoice request 4127487)\n- IMPROVEMENT: ngrokd now uses and LRU cache for tunnel affinity data\n- IMPROVEMENT: ngrokd can now save and restore its tunnel affinity cache to a file to preserve across restarts\n\n## 0.12 - 06/30/2013\n- IMPROVEMENT: Improved developer documentation\n- IMPROVEMENT: Simplified build process with custom version of go-bindata that compiles assets into binary releases\n- BUGFIX: GitHub issue #4: Raw/Binary requests bodies are no longer truncated at 8192 bytes.\n"
  },
  {
    "path": "docs/DEVELOPMENT.md",
    "content": "# Developer's guide to ngrok\n\n\n## Components\nThe ngrok project is composed of two components, the ngrok client (ngrok) and the ngrok server (ngrokd).\nThe ngrok client is the more complicated piece because it has UIs for displaying saved requests and responses.\n\n## Compiling\n\n    git clone git@github.com:inconshreveable/ngrok.git\n    cd ngrok && make\n    bin/ngrok [LOCAL PORT]\n\nThere are Makefile targets for compiling just the client or server.\n\n    make client\n    make server\n\n**NB: You must compile with Go 1.1+! You must have Mercurial SCM Installed.**\n\n### Compiling release versions\nBoth the client and the server contain static asset files.\nThese include TLS/SSL certificates and the html/css/js for the client's web interface.\nThe release versions embed all of this data into the binaries themselves, whereas the debug versions read these files from the filesystem.\n\n*You should always develop on debug versions so that you don't have to recompile when testing changes in the static assets.*\n\nThere are Makefile targets for compiling the client and server for releases:\n\n    make release-client\n    make release-server\n    make release-all\n\n\n## Developing locally\nThe strategy I use for developing on ngrok is to do the following:\n\nAdd the following lines to /etc/hosts:\n\n    127.0.0.1 ngrok.me\n    127.0.0.1 test.ngrok.me\n\nRun ngrokd with the following options:\n\n    ./bin/ngrokd -domain ngrok.me\n\nCreate an ngrok configuration file, \"debug.yml\" with the following contents:\n\n    server_addr: ngrok.me:4443\n    tunnels:\n      test:\n        proto:\n          http: 8080\n\n\nThen run ngrok with either of these commands:\n\n    ./bin/ngrok -config=debug.yml -log=ngrok.log start test\n    ./bin/ngrok -config=debug.yml -log=ngrok.log -subdomain=test 8080\n\nThis will get you setup with an ngrok client talking to an ngrok server all locally under your control. Happy hacking!\n\n\n## Network protocol and tunneling\nAt a high level, ngrok's tunneling works as follows:\n\n### Connection Setup and Authentication\n1. The client initiates a long-lived TCP connection to the server over which they will pass JSON instruction messages. This connection is called the *Control Connection*.\n1. After the connection is established, the client sends an *Auth* message with authentication and version information.\n1. The server validates the client's *Auth* message and sends an *AuthResp* message indicating either success or failure.\n\n### Tunnel creation\n1. The client may then ask the server to create tunnels for it by sending *ReqTunnel* messages. \n1. When the server receives a *ReqTunnel* message, it will send 1 or more *NewTunnel* messages that indicate successful tunnel creation or indicate failure.\n\n### Tunneling connections\n1. When the server receives a new public connection, it locates the appropriate tunnel by examining the HTTP host header (or the port number for TCP tunnels). This connection from the public internet is called a *Public Connection*.\n1. The server sends a *ReqProxy* message to the client over the control connection.\n1. The client initiates a new TCP connection to the server called a *Proxy Connection*.\n1. The client sends a *RegProxy* message over the proxy connection so the server can associate it to a control connection (and thus the tunnels it's responsible for).\n1. The server sends a *StartProxy* message over the proxy connection with metadata information about the connection (the client IP and name of the tunnel).\n1. The server begins copying the traffic byte-for-byte from the public connection to the proxy connection and vice-versa.\n1. The client opens a connection to the local address configured for that tunnel. This is called the *Private Connection*.\n1. The client begins copying the traffic byte-for-byte from the proxied connection to the private connection and vice-versa.\n\n### Detecting dead tunnels\n1. In order to determine whether a tunnel is still alive, the client periodically sends Ping messages over the control connection to the server, which replies with Pong messages.\n1. When a tunnel is detected to be dead, the server will clean up all of that tunnel's state and the client will attempt to reconnect and establish a new tunnel.\n\n### Wire format\nMessages are sent over the wire as netstrings of the form:\n\n    <message length><message payload>\n\nThe message length is sent as a 64-bit little endian integer.\n\n### Code\nThe definitions and shared protocol routines lives under _src/ngrok/msg_\n\n#### src/ngrok/msg/msg.go\nAll of the different message types (Auth, AuthResp, ReqTunnel, RegProxy, StartProxy, etc) are defined here and their fields documented. This is a good place to go to understand exactly what messages are sent between the client and server.\n    \n## ngrokd - the server\n### Code\nCode for the server lives under src/ngrok/server\n\n### Entry point\nThe ngrokd entry point is in _src/ngrok/server/main.go_.\nThere is a stub at _src/ngrok/main/ngrokd/ngrokd.go_ for the purposes of creating a properly named binary and being in its own \"main\" package to comply with go's build system.\n\n## ngrok - the client\n### Code\nCode for the client lives under src/ngrok/client\n\n### Entry point\nThe ngrok entry point is in _src/ngrok/client/main.go_.\nThere is a stub at _src/ngrok/main/ngrok/ngrok.go_ for the purposes of creating a properly named binary and being in its own \"main\" package to comply with go's build system.\n\n## Static assets\nThe html and javascript code for the ngrok web interface as well as other static assets like TLS/SSL certificates live under the top-level _assets_ directory.\n\n## Beyond\nMore documentation can be found in the comments of the code itself.\n"
  },
  {
    "path": "docs/SELFHOSTING.md",
    "content": "# How to run your own ngrokd server\n\nRunning your own ngrok server is really easy! The instructions below will guide you along your way!\n\n## 1. Get an SSL certificate\nngrok provides secure tunnels via TLS, so you'll need an SSL certificate. Assuming you want to create\ntunnels on *.example.com, buy a wildcard SSL certificate for *.example.com. Note that if you\ndon't need to run https tunnels that you don't need a wildcard certificate. (In fact, you can\njust use a self-signed cert at that point, see the section on that later in the document).\n\n## 2. Modify your DNS\nYou need to use the DNS management tools given to you by your provider to create an A\nrecord which points *.example.com to the IP address of the server where you will run ngrokd.\n\n## 3. Compile it\nYou can compile an ngrokd server with the following command:\n\n\tmake release-server\n\nMake sure you compile it with the GOOS/GOARCH environment variables set to the platform of\nyour target server. Then copy the binary over to your server.\n\n## 4. Run the server\nYou'll run the server with the following command.\n\n\n\t./ngrokd -tlsKey=\"/path/to/tls.key\" -tlsCrt=\"/path/to/tls.crt\" -domain=\"example.com\"\n\n### Specifying your TLS certificate and key\nngrok only makes TLS-encrypted connections. When you run ngrokd, you'll need to instruct it\nwhere to find your TLS certificate and private key. Specify the paths with the following switches:\n\n\t-tlsKey=\"/path/to/tls.key\" -tlsCrt=\"/path/to/tls.crt\"\n\n### Setting the server's domain\nWhen you run your own ngrokd server, you need to tell ngrokd the domain it's running on so that it\nknows what URLs to issue to clients.\n\n\t-domain=\"example.com\"\n\n## 5. Configure the client\nIn order to connect with a client, you'll need to set two options in ngrok's configuration file.\nThe ngrok configuration file is a simple YAML file that is read from ~/.ngrok by default. You may specify\na custom configuration file path with the -config switch. Your config file must contain the following two\noptions.\n\n\tserver_addr: example.com:4443\n\ttrust_host_root_certs: true\n\nSubstitute the address of your ngrokd server for \"example.com:4443\". The \"trust_host_root_certs\" parameter instructs\nngrok to trust the root certificates on your computer when establishing TLS connections to the server. By default, ngrok\nonly trusts the root certificate for ngrok.com.\n\n## 6. Connect with a client\nThen, just run ngrok as usual to connect securely to your own ngrokd server!\n\n\tngrok 80\n\n# ngrokd with a self-signed SSL certificate\nIt's possible to run ngrokd with a a self-signed certificate, but you'll need to recompile ngrok with your signing CA.\nIf you do choose to use a self-signed cert, please note that you must either remove the configuration value for\ntrust_host_root_certs or set it to false:\n\n    trust_host_root_certs: false\n\nSpecial thanks @kk86bioinfo, @lyoshenka and everyone in the thread https://github.com/inconshreveable/ngrok/issues/84 for help in writing up instructions on how to do it:\n\nhttps://gist.github.com/lyoshenka/002b7fbd801d0fd21f2f\nhttps://github.com/inconshreveable/ngrok/issues/84\n\n"
  },
  {
    "path": "src/ngrok/cache/lru.go",
    "content": "// Copyright 2012, Google Inc. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// The implementation borrows heavily from SmallLRUCache (originally by Nathan\n// Schrenk). The object maintains a doubly-linked list of elements in the\n// When an element is accessed it is promoted to the head of the list, and when\n// space is needed the element at the tail of the list (the least recently used\n// element) is evicted.\npackage cache\n\nimport (\n\t\"container/list\"\n\t\"encoding/gob\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype LRUCache struct {\n\tmu sync.Mutex\n\n\t// list & table of *entry objects\n\tlist  *list.List\n\ttable map[string]*list.Element\n\n\t// Our current size, in bytes. Obviously a gross simplification and low-grade\n\t// approximation.\n\tsize uint64\n\n\t// How many bytes we are limiting the cache to.\n\tcapacity uint64\n}\n\n// Values that go into LRUCache need to satisfy this interface.\ntype Value interface {\n\tSize() int\n}\n\ntype Item struct {\n\tKey   string\n\tValue Value\n}\n\ntype entry struct {\n\tkey           string\n\tvalue         Value\n\tsize          int\n\ttime_accessed time.Time\n}\n\nfunc NewLRUCache(capacity uint64) *LRUCache {\n\treturn &LRUCache{\n\t\tlist:     list.New(),\n\t\ttable:    make(map[string]*list.Element),\n\t\tcapacity: capacity,\n\t}\n}\n\nfunc (lru *LRUCache) Get(key string) (v Value, ok bool) {\n\tlru.mu.Lock()\n\tdefer lru.mu.Unlock()\n\n\telement := lru.table[key]\n\tif element == nil {\n\t\treturn nil, false\n\t}\n\tlru.moveToFront(element)\n\treturn element.Value.(*entry).value, true\n}\n\nfunc (lru *LRUCache) Set(key string, value Value) {\n\tlru.mu.Lock()\n\tdefer lru.mu.Unlock()\n\n\tif element := lru.table[key]; element != nil {\n\t\tlru.updateInplace(element, value)\n\t} else {\n\t\tlru.addNew(key, value)\n\t}\n}\n\nfunc (lru *LRUCache) SetIfAbsent(key string, value Value) {\n\tlru.mu.Lock()\n\tdefer lru.mu.Unlock()\n\n\tif element := lru.table[key]; element != nil {\n\t\tlru.moveToFront(element)\n\t} else {\n\t\tlru.addNew(key, value)\n\t}\n}\n\nfunc (lru *LRUCache) Delete(key string) bool {\n\tlru.mu.Lock()\n\tdefer lru.mu.Unlock()\n\n\telement := lru.table[key]\n\tif element == nil {\n\t\treturn false\n\t}\n\n\tlru.list.Remove(element)\n\tdelete(lru.table, key)\n\tlru.size -= uint64(element.Value.(*entry).size)\n\treturn true\n}\n\nfunc (lru *LRUCache) Clear() {\n\tlru.mu.Lock()\n\tdefer lru.mu.Unlock()\n\n\tlru.list.Init()\n\tlru.table = make(map[string]*list.Element)\n\tlru.size = 0\n}\n\nfunc (lru *LRUCache) SetCapacity(capacity uint64) {\n\tlru.mu.Lock()\n\tdefer lru.mu.Unlock()\n\n\tlru.capacity = capacity\n\tlru.checkCapacity()\n}\n\nfunc (lru *LRUCache) Stats() (length, size, capacity uint64, oldest time.Time) {\n\tlru.mu.Lock()\n\tdefer lru.mu.Unlock()\n\tif lastElem := lru.list.Back(); lastElem != nil {\n\t\toldest = lastElem.Value.(*entry).time_accessed\n\t}\n\treturn uint64(lru.list.Len()), lru.size, lru.capacity, oldest\n}\n\nfunc (lru *LRUCache) StatsJSON() string {\n\tif lru == nil {\n\t\treturn \"{}\"\n\t}\n\tl, s, c, o := lru.Stats()\n\treturn fmt.Sprintf(\"{\\\"Length\\\": %v, \\\"Size\\\": %v, \\\"Capacity\\\": %v, \\\"OldestAccess\\\": \\\"%v\\\"}\", l, s, c, o)\n}\n\nfunc (lru *LRUCache) Keys() []string {\n\tlru.mu.Lock()\n\tdefer lru.mu.Unlock()\n\n\tkeys := make([]string, 0, lru.list.Len())\n\tfor e := lru.list.Front(); e != nil; e = e.Next() {\n\t\tkeys = append(keys, e.Value.(*entry).key)\n\t}\n\treturn keys\n}\n\nfunc (lru *LRUCache) Items() []Item {\n\tlru.mu.Lock()\n\tdefer lru.mu.Unlock()\n\n\titems := make([]Item, 0, lru.list.Len())\n\tfor e := lru.list.Front(); e != nil; e = e.Next() {\n\t\tv := e.Value.(*entry)\n\t\titems = append(items, Item{Key: v.key, Value: v.value})\n\t}\n\treturn items\n}\n\nfunc (lru *LRUCache) SaveItems(w io.Writer) error {\n\titems := lru.Items()\n\tencoder := gob.NewEncoder(w)\n\treturn encoder.Encode(items)\n}\n\nfunc (lru *LRUCache) SaveItemsToFile(path string) error {\n\tif wr, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil {\n\t\treturn err\n\t} else {\n\t\tdefer wr.Close()\n\t\treturn lru.SaveItems(wr)\n\t}\n}\n\nfunc (lru *LRUCache) LoadItems(r io.Reader) error {\n\titems := make([]Item, 0)\n\tdecoder := gob.NewDecoder(r)\n\tif err := decoder.Decode(&items); err != nil {\n\t\treturn err\n\t}\n\n\tlru.mu.Lock()\n\tdefer lru.mu.Unlock()\n\tfor _, item := range items {\n\t\t// XXX: copied from Set()\n\t\tif element := lru.table[item.Key]; element != nil {\n\t\t\tlru.updateInplace(element, item.Value)\n\t\t} else {\n\t\t\tlru.addNew(item.Key, item.Value)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (lru *LRUCache) LoadItemsFromFile(path string) error {\n\tif rd, err := os.Open(path); err != nil {\n\t\treturn err\n\t} else {\n\t\tdefer rd.Close()\n\t\treturn lru.LoadItems(rd)\n\t}\n}\n\nfunc (lru *LRUCache) updateInplace(element *list.Element, value Value) {\n\tvalueSize := value.Size()\n\tsizeDiff := valueSize - element.Value.(*entry).size\n\telement.Value.(*entry).value = value\n\telement.Value.(*entry).size = valueSize\n\tlru.size += uint64(sizeDiff)\n\tlru.moveToFront(element)\n\tlru.checkCapacity()\n}\n\nfunc (lru *LRUCache) moveToFront(element *list.Element) {\n\tlru.list.MoveToFront(element)\n\telement.Value.(*entry).time_accessed = time.Now()\n}\n\nfunc (lru *LRUCache) addNew(key string, value Value) {\n\tnewEntry := &entry{key, value, value.Size(), time.Now()}\n\telement := lru.list.PushFront(newEntry)\n\tlru.table[key] = element\n\tlru.size += uint64(newEntry.size)\n\tlru.checkCapacity()\n}\n\nfunc (lru *LRUCache) checkCapacity() {\n\t// Partially duplicated from Delete\n\tfor lru.size > lru.capacity {\n\t\tdelElem := lru.list.Back()\n\t\tdelValue := delElem.Value.(*entry)\n\t\tlru.list.Remove(delElem)\n\t\tdelete(lru.table, delValue.key)\n\t\tlru.size -= uint64(delValue.size)\n\t}\n}\n"
  },
  {
    "path": "src/ngrok/client/cli.go",
    "content": "package client\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"ngrok/version\"\n\t\"os\"\n)\n\nconst usage1 string = `Usage: %s [OPTIONS] <local port or address>\nOptions:\n`\n\nconst usage2 string = `\nExamples:\n\tngrok 80\n\tngrok -subdomain=example 8080\n\tngrok -proto=tcp 22\n\tngrok -hostname=\"example.com\" -httpauth=\"user:password\" 10.0.0.1\n\n\nAdvanced usage: ngrok [OPTIONS] <command> [command args] [...]\nCommands:\n\tngrok start [tunnel] [...]    Start tunnels by name from config file\n\tngork start-all               Start all tunnels defined in config file\n\tngrok list                    List tunnel names from config file\n\tngrok help                    Print help\n\tngrok version                 Print ngrok version\n\nExamples:\n\tngrok start www api blog pubsub\n\tngrok -log=stdout -config=ngrok.yml start ssh\n\tngrok start-all\n\tngrok version\n\n`\n\ntype Options struct {\n\tconfig    string\n\tlogto     string\n\tloglevel  string\n\tauthtoken string\n\thttpauth  string\n\thostname  string\n\tprotocol  string\n\tsubdomain string\n\tcommand   string\n\targs      []string\n}\n\nfunc ParseArgs() (opts *Options, err error) {\n\tflag.Usage = func() {\n\t\tfmt.Fprintf(os.Stderr, usage1, os.Args[0])\n\t\tflag.PrintDefaults()\n\t\tfmt.Fprintf(os.Stderr, usage2)\n\t}\n\n\tconfig := flag.String(\n\t\t\"config\",\n\t\t\"\",\n\t\t\"Path to ngrok configuration file. (default: $HOME/.ngrok)\")\n\n\tlogto := flag.String(\n\t\t\"log\",\n\t\t\"none\",\n\t\t\"Write log messages to this file. 'stdout' and 'none' have special meanings\")\n\n\tloglevel := flag.String(\n\t\t\"log-level\",\n\t\t\"DEBUG\",\n\t\t\"The level of messages to log. One of: DEBUG, INFO, WARNING, ERROR\")\n\n\tauthtoken := flag.String(\n\t\t\"authtoken\",\n\t\t\"\",\n\t\t\"Authentication token for identifying an ngrok.com account\")\n\n\thttpauth := flag.String(\n\t\t\"httpauth\",\n\t\t\"\",\n\t\t\"username:password HTTP basic auth creds protecting the public tunnel endpoint\")\n\n\tsubdomain := flag.String(\n\t\t\"subdomain\",\n\t\t\"\",\n\t\t\"Request a custom subdomain from the ngrok server. (HTTP only)\")\n\n\thostname := flag.String(\n\t\t\"hostname\",\n\t\t\"\",\n\t\t\"Request a custom hostname from the ngrok server. (HTTP only) (requires CNAME of your DNS)\")\n\n\tprotocol := flag.String(\n\t\t\"proto\",\n\t\t\"http+https\",\n\t\t\"The protocol of the traffic over the tunnel {'http', 'https', 'tcp'} (default: 'http+https')\")\n\n\tflag.Parse()\n\n\topts = &Options{\n\t\tconfig:    *config,\n\t\tlogto:     *logto,\n\t\tloglevel:  *loglevel,\n\t\thttpauth:  *httpauth,\n\t\tsubdomain: *subdomain,\n\t\tprotocol:  *protocol,\n\t\tauthtoken: *authtoken,\n\t\thostname:  *hostname,\n\t\tcommand:   flag.Arg(0),\n\t}\n\n\tswitch opts.command {\n\tcase \"list\":\n\t\topts.args = flag.Args()[1:]\n\tcase \"start\":\n\t\topts.args = flag.Args()[1:]\n\tcase \"start-all\":\n\t\topts.args = flag.Args()[1:]\n\tcase \"version\":\n\t\tfmt.Println(version.MajorMinor())\n\t\tos.Exit(0)\n\tcase \"help\":\n\t\tflag.Usage()\n\t\tos.Exit(0)\n\tcase \"\":\n\t\terr = fmt.Errorf(\"Error: Specify a local port to tunnel to, or \" +\n\t\t\t\"an ngrok command.\\n\\nExample: To expose port 80, run \" +\n\t\t\t\"'ngrok 80'\")\n\t\treturn\n\n\tdefault:\n\t\tif len(flag.Args()) > 1 {\n\t\t\terr = fmt.Errorf(\"You may only specify one port to tunnel to on the command line, got %d: %v\",\n\t\t\t\tlen(flag.Args()),\n\t\t\t\tflag.Args())\n\t\t\treturn\n\t\t}\n\n\t\topts.command = \"default\"\n\t\topts.args = flag.Args()\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "src/ngrok/client/config.go",
    "content": "package client\n\nimport (\n\t\"fmt\"\n\t\"gopkg.in/yaml.v1\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"net/url\"\n\t\"ngrok/log\"\n\t\"os\"\n\t\"os/user\"\n\t\"path\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype Configuration struct {\n\tHttpProxy          string                          `yaml:\"http_proxy,omitempty\"`\n\tServerAddr         string                          `yaml:\"server_addr,omitempty\"`\n\tInspectAddr        string                          `yaml:\"inspect_addr,omitempty\"`\n\tTrustHostRootCerts bool                            `yaml:\"trust_host_root_certs,omitempty\"`\n\tAuthToken          string                          `yaml:\"auth_token,omitempty\"`\n\tTunnels            map[string]*TunnelConfiguration `yaml:\"tunnels,omitempty\"`\n\tLogTo              string                          `yaml:\"-\"`\n\tPath               string                          `yaml:\"-\"`\n}\n\ntype TunnelConfiguration struct {\n\tSubdomain  string            `yaml:\"subdomain,omitempty\"`\n\tHostname   string            `yaml:\"hostname,omitempty\"`\n\tProtocols  map[string]string `yaml:\"proto,omitempty\"`\n\tHttpAuth   string            `yaml:\"auth,omitempty\"`\n\tRemotePort uint16            `yaml:\"remote_port,omitempty\"`\n}\n\nfunc LoadConfiguration(opts *Options) (config *Configuration, err error) {\n\tconfigPath := opts.config\n\tif configPath == \"\" {\n\t\tconfigPath = defaultPath()\n\t}\n\n\tlog.Info(\"Reading configuration file %s\", configPath)\n\tconfigBuf, err := ioutil.ReadFile(configPath)\n\tif err != nil {\n\t\t// failure to read a configuration file is only a fatal error if\n\t\t// the user specified one explicitly\n\t\tif opts.config != \"\" {\n\t\t\terr = fmt.Errorf(\"Failed to read configuration file %s: %v\", configPath, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\t// deserialize/parse the config\n\tconfig = new(Configuration)\n\tif err = yaml.Unmarshal(configBuf, &config); err != nil {\n\t\terr = fmt.Errorf(\"Error parsing configuration file %s: %v\", configPath, err)\n\t\treturn\n\t}\n\n\t// try to parse the old .ngrok format for backwards compatibility\n\tmatched := false\n\tcontent := strings.TrimSpace(string(configBuf))\n\tif matched, err = regexp.MatchString(\"^[0-9a-zA-Z_\\\\-!]+$\", content); err != nil {\n\t\treturn\n\t} else if matched {\n\t\tconfig = &Configuration{AuthToken: content}\n\t}\n\n\t// set configuration defaults\n\tif config.ServerAddr == \"\" {\n\t\tconfig.ServerAddr = defaultServerAddr\n\t}\n\n\tif config.InspectAddr == \"\" {\n\t\tconfig.InspectAddr = defaultInspectAddr\n\t}\n\n\tif config.HttpProxy == \"\" {\n\t\tconfig.HttpProxy = os.Getenv(\"http_proxy\")\n\t}\n\n\t// validate and normalize configuration\n\tif config.InspectAddr != \"disabled\" {\n\t\tif config.InspectAddr, err = normalizeAddress(config.InspectAddr, \"inspect_addr\"); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif config.ServerAddr, err = normalizeAddress(config.ServerAddr, \"server_addr\"); err != nil {\n\t\treturn\n\t}\n\n\tif config.HttpProxy != \"\" {\n\t\tvar proxyUrl *url.URL\n\t\tif proxyUrl, err = url.Parse(config.HttpProxy); err != nil {\n\t\t\treturn\n\t\t} else {\n\t\t\tif proxyUrl.Scheme != \"http\" && proxyUrl.Scheme != \"https\" {\n\t\t\t\terr = fmt.Errorf(\"Proxy url scheme must be 'http' or 'https', got %v\", proxyUrl.Scheme)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tfor name, t := range config.Tunnels {\n\t\tif t == nil || t.Protocols == nil || len(t.Protocols) == 0 {\n\t\t\terr = fmt.Errorf(\"Tunnel %s does not specify any protocols to tunnel.\", name)\n\t\t\treturn\n\t\t}\n\n\t\tfor k, addr := range t.Protocols {\n\t\t\ttunnelName := fmt.Sprintf(\"for tunnel %s[%s]\", name, k)\n\t\t\tif t.Protocols[k], err = normalizeAddress(addr, tunnelName); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err = validateProtocol(k, tunnelName); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\t// use the name of the tunnel as the subdomain if none is specified\n\t\tif t.Hostname == \"\" && t.Subdomain == \"\" {\n\t\t\t// XXX: a crude heuristic, really we should be checking if the last part\n\t\t\t// is a TLD\n\t\t\tif len(strings.Split(name, \".\")) > 1 {\n\t\t\t\tt.Hostname = name\n\t\t\t} else {\n\t\t\t\tt.Subdomain = name\n\t\t\t}\n\t\t}\n\t}\n\n\t// override configuration with command-line options\n\tconfig.LogTo = opts.logto\n\tconfig.Path = configPath\n\tif opts.authtoken != \"\" {\n\t\tconfig.AuthToken = opts.authtoken\n\t}\n\n\tswitch opts.command {\n\t// start a single tunnel, the default, simple ngrok behavior\n\tcase \"default\":\n\t\tconfig.Tunnels = make(map[string]*TunnelConfiguration)\n\t\tconfig.Tunnels[\"default\"] = &TunnelConfiguration{\n\t\t\tSubdomain: opts.subdomain,\n\t\t\tHostname:  opts.hostname,\n\t\t\tHttpAuth:  opts.httpauth,\n\t\t\tProtocols: make(map[string]string),\n\t\t}\n\n\t\tfor _, proto := range strings.Split(opts.protocol, \"+\") {\n\t\t\tif err = validateProtocol(proto, \"default\"); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif config.Tunnels[\"default\"].Protocols[proto], err = normalizeAddress(opts.args[0], \"\"); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t// list tunnels\n\tcase \"list\":\n\t\tfor name, _ := range config.Tunnels {\n\t\t\tfmt.Println(name)\n\t\t}\n\t\tos.Exit(0)\n\n\t// start tunnels\n\tcase \"start\":\n\t\tif len(opts.args) == 0 {\n\t\t\terr = fmt.Errorf(\"You must specify at least one tunnel to start\")\n\t\t\treturn\n\t\t}\n\n\t\trequestedTunnels := make(map[string]bool)\n\t\tfor _, arg := range opts.args {\n\t\t\trequestedTunnels[arg] = true\n\n\t\t\tif _, ok := config.Tunnels[arg]; !ok {\n\t\t\t\terr = fmt.Errorf(\"Requested to start tunnel %s which is not defined in the config file.\", arg)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tfor name, _ := range config.Tunnels {\n\t\t\tif !requestedTunnels[name] {\n\t\t\t\tdelete(config.Tunnels, name)\n\t\t\t}\n\t\t}\n\n\tcase \"start-all\":\n\t\treturn\n\n\tdefault:\n\t\terr = fmt.Errorf(\"Unknown command: %s\", opts.command)\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc defaultPath() string {\n\tuser, err := user.Current()\n\n\t// user.Current() does not work on linux when cross compiling because\n\t// it requires CGO; use os.Getenv(\"HOME\") hack until we compile natively\n\thomeDir := os.Getenv(\"HOME\")\n\tif err != nil {\n\t\tlog.Warn(\"Failed to get user's home directory: %s. Using $HOME: %s\", err.Error(), homeDir)\n\t} else {\n\t\thomeDir = user.HomeDir\n\t}\n\n\treturn path.Join(homeDir, \".ngrok\")\n}\n\nfunc normalizeAddress(addr string, propName string) (string, error) {\n\t// normalize port to address\n\tif _, err := strconv.Atoi(addr); err == nil {\n\t\taddr = \":\" + addr\n\t}\n\n\thost, port, err := net.SplitHostPort(addr)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"Invalid address %s '%s': %s\", propName, addr, err.Error())\n\t}\n\n\tif host == \"\" {\n\t\thost = \"127.0.0.1\"\n\t}\n\n\treturn fmt.Sprintf(\"%s:%s\", host, port), nil\n}\n\nfunc validateProtocol(proto, propName string) (err error) {\n\tswitch proto {\n\tcase \"http\", \"https\", \"http+https\", \"tcp\":\n\tdefault:\n\t\terr = fmt.Errorf(\"Invalid protocol for %s: %s\", propName, proto)\n\t}\n\n\treturn\n}\n\nfunc SaveAuthToken(configPath, authtoken string) (err error) {\n\t// empty configuration by default for the case that we can't read it\n\tc := new(Configuration)\n\n\t// read the configuration\n\toldConfigBytes, err := ioutil.ReadFile(configPath)\n\tif err == nil {\n\t\t// unmarshal if we successfully read the configuration file\n\t\tif err = yaml.Unmarshal(oldConfigBytes, c); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\t// no need to save, the authtoken is already the correct value\n\tif c.AuthToken == authtoken {\n\t\treturn\n\t}\n\n\t// update auth token\n\tc.AuthToken = authtoken\n\n\t// rewrite configuration\n\tnewConfigBytes, err := yaml.Marshal(c)\n\tif err != nil {\n\t\treturn\n\t}\n\n\terr = ioutil.WriteFile(configPath, newConfigBytes, 0600)\n\treturn\n}\n"
  },
  {
    "path": "src/ngrok/client/controller.go",
    "content": "package client\n\nimport (\n\t\"fmt\"\n\t\"ngrok/client/mvc\"\n\t\"ngrok/client/views/term\"\n\t\"ngrok/client/views/web\"\n\t\"ngrok/log\"\n\t\"ngrok/proto\"\n\t\"ngrok/util\"\n\t\"sync\"\n)\n\ntype command interface{}\n\ntype cmdQuit struct {\n\t// display this message after quit\n\tmessage string\n}\n\ntype cmdPlayRequest struct {\n\t// the tunnel to play this request over\n\ttunnel mvc.Tunnel\n\n\t// the bytes of the request to issue\n\tpayload []byte\n}\n\n// The MVC Controller\ntype Controller struct {\n\t// Controller logger\n\tlog.Logger\n\n\t// the model sends updates through this broadcast channel\n\tupdates *util.Broadcast\n\n\t// the model\n\tmodel mvc.Model\n\n\t// the views\n\tviews []mvc.View\n\n\t// internal structure to issue commands to the controller\n\tcmds chan command\n\n\t// internal structure to synchronize access to State object\n\tstate chan mvc.State\n\n\t// options\n\tconfig *Configuration\n}\n\n// public interface\nfunc NewController() *Controller {\n\tctl := &Controller{\n\t\tLogger:  log.NewPrefixLogger(\"controller\"),\n\t\tupdates: util.NewBroadcast(),\n\t\tcmds:    make(chan command),\n\t\tviews:   make([]mvc.View, 0),\n\t\tstate:   make(chan mvc.State),\n\t}\n\n\treturn ctl\n}\n\nfunc (ctl *Controller) State() mvc.State {\n\treturn <-ctl.state\n}\n\nfunc (ctl *Controller) Update(state mvc.State) {\n\tctl.updates.In() <- state\n}\n\nfunc (ctl *Controller) Updates() *util.Broadcast {\n\treturn ctl.updates\n}\n\nfunc (ctl *Controller) Shutdown(message string) {\n\tctl.cmds <- cmdQuit{message: message}\n}\n\nfunc (ctl *Controller) PlayRequest(tunnel mvc.Tunnel, payload []byte) {\n\tctl.cmds <- cmdPlayRequest{tunnel: tunnel, payload: payload}\n}\n\nfunc (ctl *Controller) Go(fn func()) {\n\tgo func() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terr := util.MakePanicTrace(r)\n\t\t\t\tctl.Error(err)\n\t\t\t\tctl.Shutdown(err)\n\t\t\t}\n\t\t}()\n\n\t\tfn()\n\t}()\n}\n\n// private functions\nfunc (ctl *Controller) doShutdown() {\n\tctl.Info(\"Shutting down\")\n\n\tvar wg sync.WaitGroup\n\n\t// wait for all of the views, plus the model\n\twg.Add(len(ctl.views) + 1)\n\n\tfor _, v := range ctl.views {\n\t\tvClosure := v\n\t\tctl.Go(func() {\n\t\t\tvClosure.Shutdown()\n\t\t\twg.Done()\n\t\t})\n\t}\n\n\tctl.Go(func() {\n\t\tctl.model.Shutdown()\n\t\twg.Done()\n\t})\n\n\twg.Wait()\n}\n\nfunc (ctl *Controller) AddView(v mvc.View) {\n\tctl.views = append(ctl.views, v)\n}\n\nfunc (ctl *Controller) GetWebInspectAddr() string {\n\treturn ctl.config.InspectAddr\n}\n\nfunc (ctl *Controller) SetupModel(config *Configuration) *ClientModel {\n\tmodel := newClientModel(config, ctl)\n\tctl.model = model\n\treturn model\n}\n\nfunc (ctl *Controller) GetModel() *ClientModel {\n\treturn ctl.model.(*ClientModel)\n}\n\nfunc (ctl *Controller) Run(config *Configuration) {\n\t// Save the configuration\n\tctl.config = config\n\n\tvar model *ClientModel\n\n\tif ctl.model == nil {\n\t\tmodel = ctl.SetupModel(config)\n\t} else {\n\t\tmodel = ctl.model.(*ClientModel)\n\t}\n\n\t// init the model\n\tvar state mvc.State = model\n\n\t// init web ui\n\tvar webView *web.WebView\n\tif config.InspectAddr != \"disabled\" {\n\t\twebView = web.NewWebView(ctl, config.InspectAddr)\n\t\tctl.AddView(webView)\n\t}\n\n\t// init term ui\n\tvar termView *term.TermView\n\tif config.LogTo != \"stdout\" {\n\t\ttermView = term.NewTermView(ctl)\n\t\tctl.AddView(termView)\n\t}\n\n\tfor _, protocol := range model.GetProtocols() {\n\t\tswitch p := protocol.(type) {\n\t\tcase *proto.Http:\n\t\t\tif termView != nil {\n\t\t\t\tctl.AddView(termView.NewHttpView(p))\n\t\t\t}\n\n\t\t\tif webView != nil {\n\t\t\t\tctl.AddView(webView.NewHttpView(p))\n\t\t\t}\n\t\tdefault:\n\t\t}\n\t}\n\n\tctl.Go(func() { autoUpdate(state, config.AuthToken) })\n\tctl.Go(ctl.model.Run)\n\n\tupdates := ctl.updates.Reg()\n\tdefer ctl.updates.UnReg(updates)\n\n\tdone := make(chan int)\n\tfor {\n\t\tselect {\n\t\tcase obj := <-ctl.cmds:\n\t\t\tswitch cmd := obj.(type) {\n\t\t\tcase cmdQuit:\n\t\t\t\tmsg := cmd.message\n\t\t\t\tgo func() {\n\t\t\t\t\tctl.doShutdown()\n\t\t\t\t\tfmt.Println(msg)\n\t\t\t\t\tdone <- 1\n\t\t\t\t}()\n\n\t\t\tcase cmdPlayRequest:\n\t\t\t\tctl.Go(func() { ctl.model.PlayRequest(cmd.tunnel, cmd.payload) })\n\t\t\t}\n\n\t\tcase obj := <-updates:\n\t\t\tstate = obj.(mvc.State)\n\n\t\tcase ctl.state <- state:\n\t\tcase <-done:\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/ngrok/client/debug.go",
    "content": "// +build !release\n\npackage client\n\nvar (\n\trootCrtPaths = []string{\"assets/client/tls/ngrokroot.crt\", \"assets/client/tls/snakeoilca.crt\"}\n)\n\nfunc useInsecureSkipVerify() bool {\n\treturn true\n}\n"
  },
  {
    "path": "src/ngrok/client/main.go",
    "content": "package client\n\nimport (\n\t\"fmt\"\n\t\"github.com/inconshreveable/mousetrap\"\n\t\"math/rand\"\n\t\"ngrok/log\"\n\t\"ngrok/util\"\n\t\"os\"\n\t\"runtime\"\n\t\"time\"\n)\n\nfunc init() {\n\tif runtime.GOOS == \"windows\" {\n\t\tif mousetrap.StartedByExplorer() {\n\t\t\tfmt.Println(\"Don't double-click ngrok!\")\n\t\t\tfmt.Println(\"You need to open cmd.exe and run it from the command line!\")\n\t\t\ttime.Sleep(5 * time.Second)\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n}\n\nfunc Main() {\n\t// parse options\n\topts, err := ParseArgs()\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(1)\n\t}\n\n\t// set up logging\n\tlog.LogTo(opts.logto, opts.loglevel)\n\n\t// read configuration file\n\tconfig, err := LoadConfiguration(opts)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(1)\n\t}\n\n\t// seed random number generator\n\tseed, err := util.RandomSeed()\n\tif err != nil {\n\t\tfmt.Printf(\"Couldn't securely seed the random number generator!\")\n\t\tos.Exit(1)\n\t}\n\trand.Seed(seed)\n\n\tNewController().Run(config)\n}\n"
  },
  {
    "path": "src/ngrok/client/metrics.go",
    "content": "package client\n\nimport (\n\tmetrics \"github.com/rcrowley/go-metrics\"\n)\n\nconst (\n\tsampleSize  int     = 1028\n\tsampleAlpha float64 = 0.015\n)\n\ntype ClientMetrics struct {\n\t// metrics\n\tconnGauge       metrics.Gauge\n\tconnMeter       metrics.Meter\n\tconnTimer       metrics.Timer\n\tproxySetupTimer metrics.Timer\n\tbytesIn         metrics.Histogram\n\tbytesOut        metrics.Histogram\n\tbytesInCount    metrics.Counter\n\tbytesOutCount   metrics.Counter\n}\n\nfunc NewClientMetrics() *ClientMetrics {\n\treturn &ClientMetrics{\n\t\tconnGauge:       metrics.NewGauge(),\n\t\tconnMeter:       metrics.NewMeter(),\n\t\tconnTimer:       metrics.NewTimer(),\n\t\tproxySetupTimer: metrics.NewTimer(),\n\t\tbytesIn:         metrics.NewHistogram(metrics.NewExpDecaySample(sampleSize, sampleAlpha)),\n\t\tbytesOut:        metrics.NewHistogram(metrics.NewExpDecaySample(sampleSize, sampleAlpha)),\n\t\tbytesInCount:    metrics.NewCounter(),\n\t\tbytesOutCount:   metrics.NewCounter(),\n\t}\n}\n"
  },
  {
    "path": "src/ngrok/client/model.go",
    "content": "package client\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\tmetrics \"github.com/rcrowley/go-metrics\"\n\t\"io/ioutil\"\n\t\"math\"\n\t\"net\"\n\t\"ngrok/client/mvc\"\n\t\"ngrok/conn\"\n\t\"ngrok/log\"\n\t\"ngrok/msg\"\n\t\"ngrok/proto\"\n\t\"ngrok/util\"\n\t\"ngrok/version\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\nconst (\n\tdefaultServerAddr   = \"ngrokd.ngrok.com:443\"\n\tdefaultInspectAddr  = \"127.0.0.1:4040\"\n\tpingInterval        = 20 * time.Second\n\tmaxPongLatency      = 15 * time.Second\n\tupdateCheckInterval = 6 * time.Hour\n\tBadGateway          = `<html>\n<body style=\"background-color: #97a8b9\">\n    <div style=\"margin:auto; width:400px;padding: 20px 60px; background-color: #D3D3D3; border: 5px solid maroon;\">\n        <h2>Tunnel %s unavailable</h2>\n        <p>Unable to initiate connection to <strong>%s</strong>. A web server must be running on port <strong>%s</strong> to complete the tunnel.</p>\n`\n)\n\ntype ClientModel struct {\n\tlog.Logger\n\n\tid            string\n\ttunnels       map[string]mvc.Tunnel\n\tserverVersion string\n\tmetrics       *ClientMetrics\n\tupdateStatus  mvc.UpdateStatus\n\tconnStatus    mvc.ConnStatus\n\tprotoMap      map[string]proto.Protocol\n\tprotocols     []proto.Protocol\n\tctl           mvc.Controller\n\tserverAddr    string\n\tproxyUrl      string\n\tauthToken     string\n\ttlsConfig     *tls.Config\n\ttunnelConfig  map[string]*TunnelConfiguration\n\tconfigPath    string\n}\n\nfunc newClientModel(config *Configuration, ctl mvc.Controller) *ClientModel {\n\tprotoMap := make(map[string]proto.Protocol)\n\tprotoMap[\"http\"] = proto.NewHttp()\n\tprotoMap[\"https\"] = protoMap[\"http\"]\n\tprotoMap[\"tcp\"] = proto.NewTcp()\n\tprotocols := []proto.Protocol{protoMap[\"http\"], protoMap[\"tcp\"]}\n\n\tm := &ClientModel{\n\t\tLogger: log.NewPrefixLogger(\"client\"),\n\n\t\t// server address\n\t\tserverAddr: config.ServerAddr,\n\n\t\t// proxy address\n\t\tproxyUrl: config.HttpProxy,\n\n\t\t// auth token\n\t\tauthToken: config.AuthToken,\n\n\t\t// connection status\n\t\tconnStatus: mvc.ConnConnecting,\n\n\t\t// update status\n\t\tupdateStatus: mvc.UpdateNone,\n\n\t\t// metrics\n\t\tmetrics: NewClientMetrics(),\n\n\t\t// protocols\n\t\tprotoMap: protoMap,\n\n\t\t// protocol list\n\t\tprotocols: protocols,\n\n\t\t// open tunnels\n\t\ttunnels: make(map[string]mvc.Tunnel),\n\n\t\t// controller\n\t\tctl: ctl,\n\n\t\t// tunnel configuration\n\t\ttunnelConfig: config.Tunnels,\n\n\t\t// config path\n\t\tconfigPath: config.Path,\n\t}\n\n\t// configure TLS\n\tif config.TrustHostRootCerts {\n\t\tm.Info(\"Trusting host's root certificates\")\n\t\tm.tlsConfig = &tls.Config{}\n\t} else {\n\t\tm.Info(\"Trusting root CAs: %v\", rootCrtPaths)\n\t\tvar err error\n\t\tif m.tlsConfig, err = LoadTLSConfig(rootCrtPaths); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\t// configure TLS SNI\n\tm.tlsConfig.ServerName = serverName(m.serverAddr)\n\tm.tlsConfig.InsecureSkipVerify = useInsecureSkipVerify()\n\n\treturn m\n}\n\n// server name in release builds is the host part of the server address\nfunc serverName(addr string) string {\n\thost, _, err := net.SplitHostPort(addr)\n\n\t// should never panic because the config parser calls SplitHostPort first\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn host\n}\n\n// mvc.State interface\nfunc (c ClientModel) GetProtocols() []proto.Protocol { return c.protocols }\nfunc (c ClientModel) GetClientVersion() string       { return version.MajorMinor() }\nfunc (c ClientModel) GetServerVersion() string       { return c.serverVersion }\nfunc (c ClientModel) GetTunnels() []mvc.Tunnel {\n\ttunnels := make([]mvc.Tunnel, 0)\n\tfor _, t := range c.tunnels {\n\t\ttunnels = append(tunnels, t)\n\t}\n\treturn tunnels\n}\nfunc (c ClientModel) GetConnStatus() mvc.ConnStatus     { return c.connStatus }\nfunc (c ClientModel) GetUpdateStatus() mvc.UpdateStatus { return c.updateStatus }\n\nfunc (c ClientModel) GetConnectionMetrics() (metrics.Meter, metrics.Timer) {\n\treturn c.metrics.connMeter, c.metrics.connTimer\n}\n\nfunc (c ClientModel) GetBytesInMetrics() (metrics.Counter, metrics.Histogram) {\n\treturn c.metrics.bytesInCount, c.metrics.bytesIn\n}\n\nfunc (c ClientModel) GetBytesOutMetrics() (metrics.Counter, metrics.Histogram) {\n\treturn c.metrics.bytesOutCount, c.metrics.bytesOut\n}\nfunc (c ClientModel) SetUpdateStatus(updateStatus mvc.UpdateStatus) {\n\tc.updateStatus = updateStatus\n\tc.update()\n}\n\n// mvc.Model interface\nfunc (c *ClientModel) PlayRequest(tunnel mvc.Tunnel, payload []byte) {\n\tvar localConn conn.Conn\n\tlocalConn, err := conn.Dial(tunnel.LocalAddr, \"prv\", nil)\n\tif err != nil {\n\t\tc.Warn(\"Failed to open private leg to %s: %v\", tunnel.LocalAddr, err)\n\t\treturn\n\t}\n\n\tdefer localConn.Close()\n\tlocalConn = tunnel.Protocol.WrapConn(localConn, mvc.ConnectionContext{Tunnel: tunnel, ClientAddr: \"127.0.0.1\"})\n\tlocalConn.Write(payload)\n\tioutil.ReadAll(localConn)\n}\n\nfunc (c *ClientModel) Shutdown() {\n}\n\nfunc (c *ClientModel) update() {\n\tc.ctl.Update(c)\n}\n\nfunc (c *ClientModel) Run() {\n\t// how long we should wait before we reconnect\n\tmaxWait := 30 * time.Second\n\twait := 1 * time.Second\n\n\tfor {\n\t\t// run the control channel\n\t\tc.control()\n\n\t\t// control only returns when a failure has occurred, so we're going to try to reconnect\n\t\tif c.connStatus == mvc.ConnOnline {\n\t\t\twait = 1 * time.Second\n\t\t}\n\n\t\tlog.Info(\"Waiting %d seconds before reconnecting\", int(wait.Seconds()))\n\t\ttime.Sleep(wait)\n\t\t// exponentially increase wait time\n\t\twait = 2 * wait\n\t\twait = time.Duration(math.Min(float64(wait), float64(maxWait)))\n\t\tc.connStatus = mvc.ConnReconnecting\n\t\tc.update()\n\t}\n}\n\n// Establishes and manages a tunnel control connection with the server\nfunc (c *ClientModel) control() {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tlog.Error(\"control recovering from failure %v\", r)\n\t\t}\n\t}()\n\n\t// establish control channel\n\tvar (\n\t\tctlConn conn.Conn\n\t\terr     error\n\t)\n\tif c.proxyUrl == \"\" {\n\t\t// simple non-proxied case, just connect to the server\n\t\tctlConn, err = conn.Dial(c.serverAddr, \"ctl\", c.tlsConfig)\n\t} else {\n\t\tctlConn, err = conn.DialHttpProxy(c.proxyUrl, c.serverAddr, \"ctl\", c.tlsConfig)\n\t}\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer ctlConn.Close()\n\n\t// authenticate with the server\n\tauth := &msg.Auth{\n\t\tClientId:  c.id,\n\t\tOS:        runtime.GOOS,\n\t\tArch:      runtime.GOARCH,\n\t\tVersion:   version.Proto,\n\t\tMmVersion: version.MajorMinor(),\n\t\tUser:      c.authToken,\n\t}\n\n\tif err = msg.WriteMsg(ctlConn, auth); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// wait for the server to authenticate us\n\tvar authResp msg.AuthResp\n\tif err = msg.ReadMsgInto(ctlConn, &authResp); err != nil {\n\t\tpanic(err)\n\t}\n\n\tif authResp.Error != \"\" {\n\t\temsg := fmt.Sprintf(\"Failed to authenticate to server: %s\", authResp.Error)\n\t\tc.ctl.Shutdown(emsg)\n\t\treturn\n\t}\n\n\tc.id = authResp.ClientId\n\tc.serverVersion = authResp.MmVersion\n\tc.Info(\"Authenticated with server, client id: %v\", c.id)\n\tc.update()\n\tif err = SaveAuthToken(c.configPath, c.authToken); err != nil {\n\t\tc.Error(\"Failed to save auth token: %v\", err)\n\t}\n\n\t// request tunnels\n\treqIdToTunnelConfig := make(map[string]*TunnelConfiguration)\n\tfor _, config := range c.tunnelConfig {\n\t\t// create the protocol list to ask for\n\t\tvar protocols []string\n\t\tfor proto, _ := range config.Protocols {\n\t\t\tprotocols = append(protocols, proto)\n\t\t}\n\n\t\treqTunnel := &msg.ReqTunnel{\n\t\t\tReqId:      util.RandId(8),\n\t\t\tProtocol:   strings.Join(protocols, \"+\"),\n\t\t\tHostname:   config.Hostname,\n\t\t\tSubdomain:  config.Subdomain,\n\t\t\tHttpAuth:   config.HttpAuth,\n\t\t\tRemotePort: config.RemotePort,\n\t\t}\n\n\t\t// send the tunnel request\n\t\tif err = msg.WriteMsg(ctlConn, reqTunnel); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\t// save request id association so we know which local address\n\t\t// to proxy to later\n\t\treqIdToTunnelConfig[reqTunnel.ReqId] = config\n\t}\n\n\t// start the heartbeat\n\tlastPong := time.Now().UnixNano()\n\tc.ctl.Go(func() { c.heartbeat(&lastPong, ctlConn) })\n\n\t// main control loop\n\tfor {\n\t\tvar rawMsg msg.Message\n\t\tif rawMsg, err = msg.ReadMsg(ctlConn); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tswitch m := rawMsg.(type) {\n\t\tcase *msg.ReqProxy:\n\t\t\tc.ctl.Go(c.proxy)\n\n\t\tcase *msg.Pong:\n\t\t\tatomic.StoreInt64(&lastPong, time.Now().UnixNano())\n\n\t\tcase *msg.NewTunnel:\n\t\t\tif m.Error != \"\" {\n\t\t\t\temsg := fmt.Sprintf(\"Server failed to allocate tunnel: %s\", m.Error)\n\t\t\t\tc.Error(emsg)\n\t\t\t\tc.ctl.Shutdown(emsg)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttunnel := mvc.Tunnel{\n\t\t\t\tPublicUrl: m.Url,\n\t\t\t\tLocalAddr: reqIdToTunnelConfig[m.ReqId].Protocols[m.Protocol],\n\t\t\t\tProtocol:  c.protoMap[m.Protocol],\n\t\t\t}\n\n\t\t\tc.tunnels[tunnel.PublicUrl] = tunnel\n\t\t\tc.connStatus = mvc.ConnOnline\n\t\t\tc.Info(\"Tunnel established at %v\", tunnel.PublicUrl)\n\t\t\tc.update()\n\n\t\tdefault:\n\t\t\tctlConn.Warn(\"Ignoring unknown control message %v \", m)\n\t\t}\n\t}\n}\n\n// Establishes and manages a tunnel proxy connection with the server\nfunc (c *ClientModel) proxy() {\n\tvar (\n\t\tremoteConn conn.Conn\n\t\terr        error\n\t)\n\n\tif c.proxyUrl == \"\" {\n\t\tremoteConn, err = conn.Dial(c.serverAddr, \"pxy\", c.tlsConfig)\n\t} else {\n\t\tremoteConn, err = conn.DialHttpProxy(c.proxyUrl, c.serverAddr, \"pxy\", c.tlsConfig)\n\t}\n\n\tif err != nil {\n\t\tlog.Error(\"Failed to establish proxy connection: %v\", err)\n\t\treturn\n\t}\n\tdefer remoteConn.Close()\n\n\terr = msg.WriteMsg(remoteConn, &msg.RegProxy{ClientId: c.id})\n\tif err != nil {\n\t\tremoteConn.Error(\"Failed to write RegProxy: %v\", err)\n\t\treturn\n\t}\n\n\t// wait for the server to ack our register\n\tvar startPxy msg.StartProxy\n\tif err = msg.ReadMsgInto(remoteConn, &startPxy); err != nil {\n\t\tremoteConn.Error(\"Server failed to write StartProxy: %v\", err)\n\t\treturn\n\t}\n\n\ttunnel, ok := c.tunnels[startPxy.Url]\n\tif !ok {\n\t\tremoteConn.Error(\"Couldn't find tunnel for proxy: %s\", startPxy.Url)\n\t\treturn\n\t}\n\n\t// start up the private connection\n\tstart := time.Now()\n\tlocalConn, err := conn.Dial(tunnel.LocalAddr, \"prv\", nil)\n\tif err != nil {\n\t\tremoteConn.Warn(\"Failed to open private leg %s: %v\", tunnel.LocalAddr, err)\n\n\t\tif tunnel.Protocol.GetName() == \"http\" {\n\t\t\t// try to be helpful when you're in HTTP mode and a human might see the output\n\t\t\tbadGatewayBody := fmt.Sprintf(BadGateway, tunnel.PublicUrl, tunnel.LocalAddr, tunnel.LocalAddr)\n\t\t\tremoteConn.Write([]byte(fmt.Sprintf(`HTTP/1.0 502 Bad Gateway\nContent-Type: text/html\nContent-Length: %d\n\n%s`, len(badGatewayBody), badGatewayBody)))\n\t\t}\n\t\treturn\n\t}\n\tdefer localConn.Close()\n\n\tm := c.metrics\n\tm.proxySetupTimer.Update(time.Since(start))\n\tm.connMeter.Mark(1)\n\tc.update()\n\tm.connTimer.Time(func() {\n\t\tlocalConn := tunnel.Protocol.WrapConn(localConn, mvc.ConnectionContext{Tunnel: tunnel, ClientAddr: startPxy.ClientAddr})\n\t\tbytesIn, bytesOut := conn.Join(localConn, remoteConn)\n\t\tm.bytesIn.Update(bytesIn)\n\t\tm.bytesOut.Update(bytesOut)\n\t\tm.bytesInCount.Inc(bytesIn)\n\t\tm.bytesOutCount.Inc(bytesOut)\n\t})\n\tc.update()\n}\n\n// Hearbeating to ensure our connection ngrokd is still live\nfunc (c *ClientModel) heartbeat(lastPongAddr *int64, conn conn.Conn) {\n\tlastPing := time.Unix(atomic.LoadInt64(lastPongAddr)-1, 0)\n\tping := time.NewTicker(pingInterval)\n\tpongCheck := time.NewTicker(time.Second)\n\n\tdefer func() {\n\t\tconn.Close()\n\t\tping.Stop()\n\t\tpongCheck.Stop()\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase <-pongCheck.C:\n\t\t\tlastPong := time.Unix(0, atomic.LoadInt64(lastPongAddr))\n\t\t\tneedPong := lastPong.Sub(lastPing) < 0\n\t\t\tpongLatency := time.Since(lastPing)\n\n\t\t\tif needPong && pongLatency > maxPongLatency {\n\t\t\t\tc.Info(\"Last ping: %v, Last pong: %v\", lastPing, lastPong)\n\t\t\t\tc.Info(\"Connection stale, haven't gotten PongMsg in %d seconds\", int(pongLatency.Seconds()))\n\t\t\t\treturn\n\t\t\t}\n\n\t\tcase <-ping.C:\n\t\t\terr := msg.WriteMsg(conn, &msg.Ping{})\n\t\t\tif err != nil {\n\t\t\t\tconn.Debug(\"Got error %v when writing PingMsg\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlastPing = time.Now()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/ngrok/client/mvc/controller.go",
    "content": "package mvc\n\nimport (\n\t\"ngrok/util\"\n)\n\ntype Controller interface {\n\t// how the model communicates that it has changed state\n\tUpdate(State)\n\n\t// instructs the controller to shut the app down\n\tShutdown(message string)\n\n\t// PlayRequest instructs the model to play requests\n\tPlayRequest(tunnel Tunnel, payload []byte)\n\n\t// A channel of updates\n\tUpdates() *util.Broadcast\n\n\t// returns the current state\n\tState() State\n\n\t// safe wrapper for running go-routines\n\tGo(fn func())\n\n\t// the address where the web inspection interface is running\n\tGetWebInspectAddr() string\n}\n"
  },
  {
    "path": "src/ngrok/client/mvc/model.go",
    "content": "package mvc\n\ntype Model interface {\n\tRun()\n\n\tShutdown()\n\n\tPlayRequest(tunnel Tunnel, payload []byte)\n}\n"
  },
  {
    "path": "src/ngrok/client/mvc/state.go",
    "content": "package mvc\n\nimport (\n\tmetrics \"github.com/rcrowley/go-metrics\"\n\t\"ngrok/proto\"\n)\n\ntype UpdateStatus int\n\nconst (\n\tUpdateNone = -1 * iota\n\tUpdateInstalling\n\tUpdateReady\n\tUpdateAvailable\n)\n\ntype ConnStatus int\n\nconst (\n\tConnConnecting = iota\n\tConnReconnecting\n\tConnOnline\n)\n\ntype Tunnel struct {\n\tPublicUrl string\n\tProtocol  proto.Protocol\n\tLocalAddr string\n}\n\ntype ConnectionContext struct {\n\tTunnel     Tunnel\n\tClientAddr string\n}\n\ntype State interface {\n\tGetClientVersion() string\n\tGetServerVersion() string\n\tGetTunnels() []Tunnel\n\tGetProtocols() []proto.Protocol\n\tGetUpdateStatus() UpdateStatus\n\tGetConnStatus() ConnStatus\n\tGetConnectionMetrics() (metrics.Meter, metrics.Timer)\n\tGetBytesInMetrics() (metrics.Counter, metrics.Histogram)\n\tGetBytesOutMetrics() (metrics.Counter, metrics.Histogram)\n\tSetUpdateStatus(UpdateStatus)\n}\n"
  },
  {
    "path": "src/ngrok/client/mvc/view.go",
    "content": "package mvc\n\ntype View interface {\n\tShutdown()\n}\n"
  },
  {
    "path": "src/ngrok/client/release.go",
    "content": "// +build release\n\npackage client\n\nvar (\n\trootCrtPaths = []string{\"assets/client/tls/ngrokroot.crt\"}\n)\n\nfunc useInsecureSkipVerify() bool {\n\treturn false\n}\n"
  },
  {
    "path": "src/ngrok/client/tls.go",
    "content": "package client\n\nimport (\n\t_ \"crypto/sha512\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"ngrok/client/assets\"\n)\n\nfunc LoadTLSConfig(rootCertPaths []string) (*tls.Config, error) {\n\tpool := x509.NewCertPool()\n\n\tfor _, certPath := range rootCertPaths {\n\t\trootCrt, err := assets.Asset(certPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tpemBlock, _ := pem.Decode(rootCrt)\n\t\tif pemBlock == nil {\n\t\t\treturn nil, fmt.Errorf(\"Bad PEM data\")\n\t\t}\n\n\t\tcerts, err := x509.ParseCertificates(pemBlock.Bytes)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tpool.AddCert(certs[0])\n\t}\n\n\treturn &tls.Config{RootCAs: pool}, nil\n}\n"
  },
  {
    "path": "src/ngrok/client/update_debug.go",
    "content": "// +build !release,!autoupdate\n\npackage client\n\nimport (\n\t\"ngrok/client/mvc\"\n)\n\n// no auto-updating in debug mode\nfunc autoUpdate(state mvc.State, token string) {\n}\n"
  },
  {
    "path": "src/ngrok/client/update_release.go",
    "content": "// +build release autoupdate\n\npackage client\n\nimport (\n\t\"ngrok/client/mvc\"\n\t\"ngrok/log\"\n\t\"ngrok/version\"\n\t\"time\"\n\n\t\"gopkg.in/inconshreveable/go-update.v0\"\n\t\"gopkg.in/inconshreveable/go-update.v0/check\"\n)\n\nconst (\n\tappId          = \"ap_pJSFC5wQYkAyI0FIVwKYs9h1hW\"\n\tupdateEndpoint = \"https://api.equinox.io/1/Updates\"\n)\n\nconst publicKey = `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Gx8r9no1QBtCruJW2tu\n082MJJ5ZA7k803GisR2c6WglPOD1b/+kUg+dx5Y0TKXz+uNlR3GrCxLh8WkoA95M\nT38CQldIjoVN/bWP6jzFxL+6BRoKy5L1TcaIf3xb9B8OhwEq60cvFy7BBrLKEHJN\nua/D1S5axgNOAJ8tQ2w8gISICd84ng+U9tNMqIcEjUN89h3Z4zablfNIfVkbqbSR\nfnkR9boUaMr6S1w8OeInjWdiab9sUr87GmEo/3tVxrHVCzHB8pzzoZceCkjgI551\nd/hHfAl567YhlkQMNz8dawxBjQwCHHekgC8gAvTO7kmXkAm6YAbpa9kjwgnorPEP\nywIDAQAB\n-----END PUBLIC KEY-----`\n\nfunc autoUpdate(s mvc.State, token string) {\n\tup, err := update.New().VerifySignatureWithPEM([]byte(publicKey))\n\tif err != nil {\n\t\tlog.Error(\"Failed to create update with signature: %v\", err)\n\t\treturn\n\t}\n\n\tupdate := func() (tryAgain bool) {\n\t\tlog.Info(\"Checking for update\")\n\t\tparams := check.Params{\n\t\t\tAppId:      appId,\n\t\t\tAppVersion: version.MajorMinor(),\n\t\t\tUserId:     token,\n\t\t}\n\n\t\tresult, err := params.CheckForUpdate(updateEndpoint, up)\n\t\tif err == check.NoUpdateAvailable {\n\t\t\tlog.Info(\"No update available\")\n\t\t\treturn true\n\t\t} else if err != nil {\n\t\t\tlog.Error(\"Error while checking for update: %v\", err)\n\t\t\treturn true\n\t\t}\n\n\t\tif result.Initiative == check.INITIATIVE_AUTO {\n\t\t\tif err := up.CanUpdate(); err != nil {\n\t\t\t\tlog.Error(\"Can't update: insufficient permissions: %v\", err)\n\t\t\t\t// tell the user to update manually\n\t\t\t\ts.SetUpdateStatus(mvc.UpdateAvailable)\n\t\t\t} else {\n\t\t\t\tapplyUpdate(s, result)\n\t\t\t}\n\t\t} else if result.Initiative == check.INITIATIVE_MANUAL {\n\t\t\t// this is the way the server tells us to update manually\n\t\t\tlog.Info(\"Server wants us to update manually\")\n\t\t\ts.SetUpdateStatus(mvc.UpdateAvailable)\n\t\t} else {\n\t\t\tlog.Info(\"Update available, but ignoring\")\n\t\t}\n\n\t\t// stop trying after a single download attempt\n\t\t// XXX: improve this so the we can:\n\t\t// 1. safely update multiple times\n\t\t// 2. only retry after temporary errors\n\t\treturn false\n\t}\n\n\t// try to update immediately and then at a set interval\n\tfor {\n\t\tif tryAgain := update(); !tryAgain {\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(updateCheckInterval)\n\t}\n}\n\nfunc applyUpdate(s mvc.State, result *check.Result) {\n\terr, errRecover := result.Update()\n\tif err == nil {\n\t\tlog.Info(\"Update ready!\")\n\t\ts.SetUpdateStatus(mvc.UpdateReady)\n\t\treturn\n\t}\n\n\tlog.Error(\"Error while updating ngrok: %v\", err)\n\tif errRecover != nil {\n\t\tlog.Error(\"Error while recovering from failed ngrok update, your binary may be missing: %v\", errRecover.Error())\n\t}\n\n\t// tell the user to update manually\n\ts.SetUpdateStatus(mvc.UpdateAvailable)\n}\n"
  },
  {
    "path": "src/ngrok/client/views/term/area.go",
    "content": "// shared internal functions for handling output to the terminal\npackage term\n\nimport (\n\t\"fmt\"\n\ttermbox \"github.com/nsf/termbox-go\"\n)\n\nconst (\n\tfgColor = termbox.ColorWhite\n\tbgColor = termbox.ColorDefault\n)\n\ntype area struct {\n\t// top-left corner\n\tx, y int\n\n\t// size of the area\n\tw, h int\n\n\t// default colors\n\tfgColor, bgColor termbox.Attribute\n}\n\nfunc NewArea(x, y, w, h int) *area {\n\treturn &area{x, y, w, h, fgColor, bgColor}\n}\n\nfunc (a *area) Clear() {\n\tfor i := 0; i < a.w; i++ {\n\t\tfor j := 0; j < a.h; j++ {\n\t\t\ttermbox.SetCell(a.x+i, a.y+j, ' ', a.fgColor, a.bgColor)\n\t\t}\n\t}\n}\n\nfunc (a *area) APrintf(fg termbox.Attribute, x, y int, arg0 string, args ...interface{}) {\n\ts := fmt.Sprintf(arg0, args...)\n\tfor i, ch := range s {\n\t\ttermbox.SetCell(a.x+x+i, a.y+y, ch, fg, bgColor)\n\t}\n}\n\nfunc (a *area) Printf(x, y int, arg0 string, args ...interface{}) {\n\ta.APrintf(a.fgColor, x, y, arg0, args...)\n}\n"
  },
  {
    "path": "src/ngrok/client/views/term/http.go",
    "content": "package term\n\nimport (\n\ttermbox \"github.com/nsf/termbox-go\"\n\t\"ngrok/client/mvc\"\n\t\"ngrok/log\"\n\t\"ngrok/proto\"\n\t\"ngrok/util\"\n\t\"unicode/utf8\"\n)\n\nconst (\n\tsize          = 10\n\tpathMaxLength = 25\n)\n\ntype HttpView struct {\n\tlog.Logger\n\t*area\n\n\thttpProto    *proto.Http\n\tHttpRequests *util.Ring\n\tshutdown     chan int\n\ttermView     *TermView\n}\n\nfunc colorFor(status string) termbox.Attribute {\n\tswitch status[0] {\n\tcase '3':\n\t\treturn termbox.ColorCyan\n\tcase '4':\n\t\treturn termbox.ColorYellow\n\tcase '5':\n\t\treturn termbox.ColorRed\n\tdefault:\n\t}\n\treturn termbox.ColorWhite\n}\n\nfunc newTermHttpView(ctl mvc.Controller, termView *TermView, proto *proto.Http, x, y int) *HttpView {\n\tv := &HttpView{\n\t\thttpProto:    proto,\n\t\tHttpRequests: util.NewRing(size),\n\t\tarea:         NewArea(x, y, 70, size+5),\n\t\tshutdown:     make(chan int),\n\t\ttermView:     termView,\n\t\tLogger:       log.NewPrefixLogger(\"view\", \"term\", \"http\"),\n\t}\n\tctl.Go(v.Run)\n\treturn v\n}\n\nfunc (v *HttpView) Run() {\n\tupdates := v.httpProto.Txns.Reg()\n\n\tfor {\n\t\tselect {\n\t\tcase txn := <-updates:\n\t\t\tv.Debug(\"Got HTTP update\")\n\t\t\tif txn.(*proto.HttpTxn).Resp == nil {\n\t\t\t\tv.HttpRequests.Add(txn)\n\t\t\t}\n\t\t\tv.Render()\n\t\t}\n\t}\n}\n\nfunc (v *HttpView) Render() {\n\tv.Clear()\n\tv.Printf(0, 0, \"HTTP Requests\")\n\tv.Printf(0, 1, \"-------------\")\n\tfor i, obj := range v.HttpRequests.Slice() {\n\t\ttxn := obj.(*proto.HttpTxn)\n\t\tpath := truncatePath(txn.Req.URL.Path)\n\t\tv.Printf(0, 3+i, \"%s %v\", txn.Req.Method, path)\n\t\tif txn.Resp != nil {\n\t\t\tv.APrintf(colorFor(txn.Resp.Status), 30, 3+i, \"%s\", txn.Resp.Status)\n\t\t}\n\t}\n\tv.termView.Flush()\n}\n\nfunc (v *HttpView) Shutdown() {\n\tclose(v.shutdown)\n}\n\nfunc truncatePath(path string) string {\n\t// Truncate all long strings based on rune count\n\tif utf8.RuneCountInString(path) > pathMaxLength {\n\t\tpath = string([]rune(path)[:pathMaxLength])\n\t}\n\n\t// By this point, len(path) should be < pathMaxLength if we're dealing with single-byte runes.\n\t// Otherwise, we have a multi-byte string and need to calculate the size of each rune and\n\t// truncate manually.\n\t//\n\t// This is a workaround for a bug in termbox-go. Remove it when this issue is fixed:\n\t// https://github.com/nsf/termbox-go/pull/21\n\tif len(path) > pathMaxLength {\n\t\tout := make([]byte, pathMaxLength, pathMaxLength)\n\t\tlength := 0\n\t\tfor {\n\t\t\tr, size := utf8.DecodeRuneInString(path[length:])\n\t\t\tif r == utf8.RuneError && size == 1 {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// utf8.EncodeRune expects there to be enough room to store the full size of the rune\n\t\t\tif length+size <= pathMaxLength {\n\t\t\t\tutf8.EncodeRune(out[length:], r)\n\t\t\t\tlength += size\n\t\t\t} else {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tpath = string(out[:length])\n\t}\n\treturn path\n}\n"
  },
  {
    "path": "src/ngrok/client/views/term/view.go",
    "content": "// interactive terminal interface for local clients\npackage term\n\nimport (\n\ttermbox \"github.com/nsf/termbox-go\"\n\t\"ngrok/client/mvc\"\n\t\"ngrok/log\"\n\t\"ngrok/proto\"\n\t\"ngrok/util\"\n\t\"time\"\n)\n\ntype TermView struct {\n\tctl      mvc.Controller\n\tupdates  chan interface{}\n\tflush    chan int\n\tshutdown chan int\n\tredraw   *util.Broadcast\n\tsubviews []mvc.View\n\tlog.Logger\n\t*area\n}\n\nfunc NewTermView(ctl mvc.Controller) *TermView {\n\t// initialize terminal display\n\ttermbox.Init()\n\n\tw, _ := termbox.Size()\n\n\tv := &TermView{\n\t\tctl:      ctl,\n\t\tupdates:  ctl.Updates().Reg(),\n\t\tredraw:   util.NewBroadcast(),\n\t\tflush:    make(chan int),\n\t\tshutdown: make(chan int),\n\t\tLogger:   log.NewPrefixLogger(\"view\", \"term\"),\n\t\tarea:     NewArea(0, 0, w, 10),\n\t}\n\n\tctl.Go(v.run)\n\tctl.Go(v.input)\n\n\treturn v\n}\n\nfunc connStatusRepr(status mvc.ConnStatus) (string, termbox.Attribute) {\n\tswitch status {\n\tcase mvc.ConnConnecting:\n\t\treturn \"connecting\", termbox.ColorCyan\n\tcase mvc.ConnReconnecting:\n\t\treturn \"reconnecting\", termbox.ColorRed\n\tcase mvc.ConnOnline:\n\t\treturn \"online\", termbox.ColorGreen\n\t}\n\treturn \"unknown\", termbox.ColorWhite\n}\n\nfunc (v *TermView) draw() {\n\tstate := v.ctl.State()\n\n\tv.Clear()\n\n\t// quit instructions\n\tquitMsg := \"(Ctrl+C to quit)\"\n\tv.Printf(v.w-len(quitMsg), 0, quitMsg)\n\n\t// new version message\n\tupdateStatus := state.GetUpdateStatus()\n\tvar updateMsg string\n\tswitch updateStatus {\n\tcase mvc.UpdateNone:\n\t\tupdateMsg = \"\"\n\tcase mvc.UpdateInstalling:\n\t\tupdateMsg = \"ngrok is updating\"\n\tcase mvc.UpdateReady:\n\t\tupdateMsg = \"ngrok has updated: restart ngrok for the new version\"\n\tcase mvc.UpdateAvailable:\n\t\tupdateMsg = \"new version available at https://ngrok.com\"\n\tdefault:\n\t\tpct := float64(updateStatus) / 100.0\n\t\tconst barLength = 25\n\t\tfull := int(barLength * pct)\n\t\tbar := make([]byte, barLength+2)\n\t\tbar[0] = '['\n\t\tbar[barLength+1] = ']'\n\t\tfor i := 0; i < 25; i++ {\n\t\t\tif i <= full {\n\t\t\t\tbar[i+1] = '#'\n\t\t\t} else {\n\t\t\t\tbar[i+1] = ' '\n\t\t\t}\n\t\t}\n\t\tupdateMsg = \"Downloading update: \" + string(bar)\n\t}\n\n\tif updateMsg != \"\" {\n\t\tv.APrintf(termbox.ColorYellow, 30, 0, updateMsg)\n\t}\n\n\tv.APrintf(termbox.ColorBlue|termbox.AttrBold, 0, 0, \"ngrok\")\n\tstatusStr, statusColor := connStatusRepr(state.GetConnStatus())\n\tv.APrintf(statusColor, 0, 2, \"%-30s%s\", \"Tunnel Status\", statusStr)\n\n\tv.Printf(0, 3, \"%-30s%s/%s\", \"Version\", state.GetClientVersion(), state.GetServerVersion())\n\tvar i int = 4\n\tfor _, t := range state.GetTunnels() {\n\t\tv.Printf(0, i, \"%-30s%s -> %s\", \"Forwarding\", t.PublicUrl, t.LocalAddr)\n\t\ti++\n\t}\n\tv.Printf(0, i+0, \"%-30s%s\", \"Web Interface\", v.ctl.GetWebInspectAddr())\n\n\tconnMeter, connTimer := state.GetConnectionMetrics()\n\tv.Printf(0, i+1, \"%-30s%d\", \"# Conn\", connMeter.Count())\n\n\tmsec := float64(time.Millisecond)\n\tv.Printf(0, i+2, \"%-30s%.2fms\", \"Avg Conn Time\", connTimer.Mean()/msec)\n\n\ttermbox.Flush()\n}\n\nfunc (v *TermView) run() {\n\tdefer close(v.shutdown)\n\tdefer termbox.Close()\n\n\tredraw := v.redraw.Reg()\n\tdefer v.redraw.UnReg(redraw)\n\n\tv.draw()\n\tfor {\n\t\tv.Debug(\"Waiting for update\")\n\t\tselect {\n\t\tcase <-v.flush:\n\t\t\ttermbox.Flush()\n\n\t\tcase <-v.updates:\n\t\t\tv.draw()\n\n\t\tcase <-redraw:\n\t\t\tv.draw()\n\n\t\tcase <-v.shutdown:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (v *TermView) Shutdown() {\n\tv.shutdown <- 1\n\t<-v.shutdown\n}\n\nfunc (v *TermView) Flush() {\n\tv.flush <- 1\n}\n\nfunc (v *TermView) NewHttpView(p *proto.Http) *HttpView {\n\treturn newTermHttpView(v.ctl, v, p, 0, 12)\n}\n\nfunc (v *TermView) input() {\n\tfor {\n\t\tev := termbox.PollEvent()\n\t\tswitch ev.Type {\n\t\tcase termbox.EventKey:\n\t\t\tswitch ev.Key {\n\t\t\tcase termbox.KeyCtrlC:\n\t\t\t\tv.Info(\"Got quit command\")\n\t\t\t\tv.ctl.Shutdown(\"\")\n\t\t\t}\n\n\t\tcase termbox.EventResize:\n\t\t\tv.Info(\"Resize event, redrawing\")\n\t\t\tv.redraw.In() <- 1\n\n\t\tcase termbox.EventError:\n\t\t\tpanic(ev.Err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/ngrok/client/views/web/http.go",
    "content": "// interactive web user interface\npackage web\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"encoding/xml\"\n\t\"html/template\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n\t\"ngrok/client/assets\"\n\t\"ngrok/client/mvc\"\n\t\"ngrok/log\"\n\t\"ngrok/proto\"\n\t\"ngrok/util\"\n\t\"strings\"\n\t\"unicode/utf8\"\n)\n\ntype SerializedTxn struct {\n\tId             string\n\tDuration       int64\n\tStart          int64\n\tConnCtx        mvc.ConnectionContext\n\t*proto.HttpTxn `json:\"-\"`\n\tReq            SerializedRequest\n\tResp           SerializedResponse\n}\n\ntype SerializedBody struct {\n\tRawContentType string\n\tContentType    string\n\tText           string\n\tLength         int\n\tError          string\n\tErrorOffset    int\n\tForm           url.Values\n}\n\ntype SerializedRequest struct {\n\tRaw        string\n\tMethodPath string\n\tParams     url.Values\n\tHeader     http.Header\n\tBody       SerializedBody\n\tBinary     bool\n}\n\ntype SerializedResponse struct {\n\tRaw    string\n\tStatus string\n\tHeader http.Header\n\tBody   SerializedBody\n\tBinary bool\n}\n\ntype WebHttpView struct {\n\tlog.Logger\n\n\twebview      *WebView\n\tctl          mvc.Controller\n\thttpProto    *proto.Http\n\tstate        chan SerializedUiState\n\tHttpRequests *util.Ring\n\tidToTxn      map[string]*SerializedTxn\n}\n\ntype SerializedUiState struct {\n\tTunnels []mvc.Tunnel\n}\n\ntype SerializedPayload struct {\n\tTxns    []interface{}\n\tUiState SerializedUiState\n}\n\nfunc newWebHttpView(ctl mvc.Controller, wv *WebView, proto *proto.Http) *WebHttpView {\n\twhv := &WebHttpView{\n\t\tLogger:       log.NewPrefixLogger(\"view\", \"web\", \"http\"),\n\t\twebview:      wv,\n\t\tctl:          ctl,\n\t\thttpProto:    proto,\n\t\tidToTxn:      make(map[string]*SerializedTxn),\n\t\tHttpRequests: util.NewRing(20),\n\t}\n\tctl.Go(whv.updateHttp)\n\twhv.register()\n\treturn whv\n}\n\ntype XMLDoc struct {\n\tdata []byte `xml:\",innerxml\"`\n}\n\nfunc makeBody(h http.Header, body []byte) SerializedBody {\n\tb := SerializedBody{\n\t\tLength:      len(body),\n\t\tText:        base64.StdEncoding.EncodeToString(body),\n\t\tErrorOffset: -1,\n\t}\n\n\t// some errors like XML errors only give a line number\n\t// and not an exact offset\n\toffsetForLine := func(line int) int {\n\t\tlines := strings.SplitAfterN(b.Text, \"\\n\", line)\n\t\treturn b.Length - len(lines[len(lines)-1])\n\t}\n\n\tvar err error\n\tb.RawContentType = h.Get(\"Content-Type\")\n\tif b.RawContentType != \"\" {\n\t\tb.ContentType = strings.TrimSpace(strings.Split(b.RawContentType, \";\")[0])\n\t\tswitch b.ContentType {\n\t\tcase \"application/xml\", \"text/xml\":\n\t\t\terr = xml.Unmarshal(body, new(XMLDoc))\n\t\t\tif err != nil {\n\t\t\t\tif syntaxError, ok := err.(*xml.SyntaxError); ok {\n\t\t\t\t\t// xml syntax errors only give us a line number, so we\n\t\t\t\t\t// count to find an offset\n\t\t\t\t\tb.ErrorOffset = offsetForLine(syntaxError.Line)\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase \"application/json\":\n\t\t\terr = json.Unmarshal(body, new(json.RawMessage))\n\t\t\tif err != nil {\n\t\t\t\tif syntaxError, ok := err.(*json.SyntaxError); ok {\n\t\t\t\t\tb.ErrorOffset = int(syntaxError.Offset)\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase \"application/x-www-form-urlencoded\":\n\t\t\tb.Form, err = url.ParseQuery(string(body))\n\t\t}\n\t}\n\n\tif err != nil {\n\t\tb.Error = err.Error()\n\t}\n\n\treturn b\n}\n\nfunc (whv *WebHttpView) updateHttp() {\n\t// open channels for incoming http state changes\n\t// and broadcasts\n\ttxnUpdates := whv.httpProto.Txns.Reg()\n\tfor txn := range txnUpdates {\n\t\t// XXX: it's not safe for proto.Http and this code\n\t\t// to be accessing txn and txn.(req/resp) without synchronization\n\t\thtxn := txn.(*proto.HttpTxn)\n\n\t\t// we haven't processed this transaction yet if we haven't set the\n\t\t// user data\n\t\tif htxn.UserCtx == nil {\n\t\t\trawReq, err := proto.DumpRequestOut(htxn.Req.Request, true)\n\t\t\tif err != nil {\n\t\t\t\twhv.Error(\"Failed to dump request: %v\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tbody := makeBody(htxn.Req.Header, htxn.Req.BodyBytes)\n\t\t\twhtxn := &SerializedTxn{\n\t\t\t\tId:      util.RandId(8),\n\t\t\t\tHttpTxn: htxn,\n\t\t\t\tReq: SerializedRequest{\n\t\t\t\t\tMethodPath: htxn.Req.Method + \" \" + htxn.Req.URL.Path,\n\t\t\t\t\tRaw:        base64.StdEncoding.EncodeToString(rawReq),\n\t\t\t\t\tParams:     htxn.Req.URL.Query(),\n\t\t\t\t\tHeader:     htxn.Req.Header,\n\t\t\t\t\tBody:       body,\n\t\t\t\t\tBinary:     !utf8.Valid(rawReq),\n\t\t\t\t},\n\t\t\t\tStart:   htxn.Start.Unix(),\n\t\t\t\tConnCtx: htxn.ConnUserCtx.(mvc.ConnectionContext),\n\t\t\t}\n\n\t\t\thtxn.UserCtx = whtxn\n\t\t\t// XXX: unsafe map access from multiple go routines\n\t\t\twhv.idToTxn[whtxn.Id] = whtxn\n\t\t\t// XXX: use return value to delete from map so we don't leak memory\n\t\t\twhv.HttpRequests.Add(whtxn)\n\t\t} else {\n\t\t\trawResp, err := httputil.DumpResponse(htxn.Resp.Response, true)\n\t\t\tif err != nil {\n\t\t\t\twhv.Error(\"Failed to dump response: %v\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttxn := htxn.UserCtx.(*SerializedTxn)\n\t\t\tbody := makeBody(htxn.Resp.Header, htxn.Resp.BodyBytes)\n\t\t\ttxn.Duration = htxn.Duration.Nanoseconds()\n\t\t\ttxn.Resp = SerializedResponse{\n\t\t\t\tStatus: htxn.Resp.Status,\n\t\t\t\tRaw:    base64.StdEncoding.EncodeToString(rawResp),\n\t\t\t\tHeader: htxn.Resp.Header,\n\t\t\t\tBody:   body,\n\t\t\t\tBinary: !utf8.Valid(rawResp),\n\t\t\t}\n\n\t\t\tpayload, err := json.Marshal(txn)\n\t\t\tif err != nil {\n\t\t\t\twhv.Error(\"Failed to serialized txn payload for websocket: %v\", err)\n\t\t\t}\n\t\t\twhv.webview.wsMessages.In() <- payload\n\t\t}\n\t}\n}\n\nfunc (whv *WebHttpView) register() {\n\thttp.HandleFunc(\"/http/in/replay\", func(w http.ResponseWriter, r *http.Request) {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terr := util.MakePanicTrace(r)\n\t\t\t\twhv.Error(\"Replay failed: %v\", err)\n\t\t\t\thttp.Error(w, err, 500)\n\t\t\t}\n\t\t}()\n\n\t\tr.ParseForm()\n\t\ttxnid := r.Form.Get(\"txnid\")\n\t\tif txn, ok := whv.idToTxn[txnid]; ok {\n\t\t\treqBytes, err := base64.StdEncoding.DecodeString(txn.Req.Raw)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\twhv.ctl.PlayRequest(txn.ConnCtx.Tunnel, reqBytes)\n\t\t\tw.Write([]byte(http.StatusText(200)))\n\t\t} else {\n\t\t\thttp.Error(w, http.StatusText(400), 400)\n\t\t}\n\t})\n\n\thttp.HandleFunc(\"/http/in\", func(w http.ResponseWriter, r *http.Request) {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terr := util.MakePanicTrace(r)\n\t\t\t\twhv.Error(\"HTTP web view failed: %v\", err)\n\t\t\t\thttp.Error(w, err, 500)\n\t\t\t}\n\t\t}()\n\n\t\tpageTmpl, err := assets.Asset(\"assets/client/page.html\")\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\ttmpl := template.Must(template.New(\"page.html\").Delims(\"{%\", \"%}\").Parse(string(pageTmpl)))\n\n\t\tpayloadData := SerializedPayload{\n\t\t\tTxns:    whv.HttpRequests.Slice(),\n\t\t\tUiState: SerializedUiState{Tunnels: whv.ctl.State().GetTunnels()},\n\t\t}\n\n\t\tpayload, err := json.Marshal(payloadData)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\t// write the response\n\t\tif err := tmpl.Execute(w, string(payload)); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t})\n}\n\nfunc (whv *WebHttpView) Shutdown() {\n}\n"
  },
  {
    "path": "src/ngrok/client/views/web/view.go",
    "content": "// interactive web user interface\npackage web\n\nimport (\n\t\"github.com/gorilla/websocket\"\n\t\"net/http\"\n\t\"ngrok/client/assets\"\n\t\"ngrok/client/mvc\"\n\t\"ngrok/log\"\n\t\"ngrok/proto\"\n\t\"ngrok/util\"\n\t\"path\"\n)\n\ntype WebView struct {\n\tlog.Logger\n\n\tctl mvc.Controller\n\n\t// messages sent over this broadcast are sent to all websocket connections\n\twsMessages *util.Broadcast\n}\n\nfunc NewWebView(ctl mvc.Controller, addr string) *WebView {\n\twv := &WebView{\n\t\tLogger:     log.NewPrefixLogger(\"view\", \"web\"),\n\t\twsMessages: util.NewBroadcast(),\n\t\tctl:        ctl,\n\t}\n\n\t// for now, always redirect to the http view\n\thttp.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\thttp.Redirect(w, r, \"/http/in\", 302)\n\t})\n\n\t// handle web socket connections\n\thttp.HandleFunc(\"/_ws\", func(w http.ResponseWriter, r *http.Request) {\n\t\tconn, err := websocket.Upgrade(w, r, nil, 1024, 1024)\n\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"Failed websocket upgrade\", 400)\n\t\t\twv.Warn(\"Failed websocket upgrade: %v\", err)\n\t\t\treturn\n\t\t}\n\n\t\tmsgs := wv.wsMessages.Reg()\n\t\tdefer wv.wsMessages.UnReg(msgs)\n\t\tfor m := range msgs {\n\t\t\terr := conn.WriteMessage(websocket.TextMessage, m.([]byte))\n\t\t\tif err != nil {\n\t\t\t\t// connection is closed\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t})\n\n\t// serve static assets\n\thttp.HandleFunc(\"/static/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tbuf, err := assets.Asset(path.Join(\"assets\", \"client\", r.URL.Path[1:]))\n\t\tif err != nil {\n\t\t\twv.Warn(\"Error serving static file: %s\", err.Error())\n\t\t\thttp.NotFound(w, r)\n\t\t\treturn\n\t\t}\n\t\tw.Write(buf)\n\t})\n\n\twv.Info(\"Serving web interface on %s\", addr)\n\twv.ctl.Go(func() { http.ListenAndServe(addr, nil) })\n\treturn wv\n}\n\nfunc (wv *WebView) NewHttpView(proto *proto.Http) *WebHttpView {\n\treturn newWebHttpView(wv.ctl, wv, proto)\n}\n\nfunc (wv *WebView) Shutdown() {\n}\n"
  },
  {
    "path": "src/ngrok/conn/conn.go",
    "content": "package conn\n\nimport (\n\t\"bufio\"\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\tvhost \"github.com/inconshreveable/go-vhost\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"ngrok/log\"\n\t\"sync\"\n)\n\ntype Conn interface {\n\tnet.Conn\n\tlog.Logger\n\tId() string\n\tSetType(string)\n\tCloseRead() error\n}\n\ntype loggedConn struct {\n\ttcp *net.TCPConn\n\tnet.Conn\n\tlog.Logger\n\tid  int32\n\ttyp string\n}\n\ntype Listener struct {\n\tnet.Addr\n\tConns chan *loggedConn\n}\n\nfunc wrapConn(conn net.Conn, typ string) *loggedConn {\n\tswitch c := conn.(type) {\n\tcase *vhost.HTTPConn:\n\t\twrapped := c.Conn.(*loggedConn)\n\t\treturn &loggedConn{wrapped.tcp, conn, wrapped.Logger, wrapped.id, wrapped.typ}\n\tcase *loggedConn:\n\t\treturn c\n\tcase *net.TCPConn:\n\t\twrapped := &loggedConn{c, conn, log.NewPrefixLogger(), rand.Int31(), typ}\n\t\twrapped.AddLogPrefix(wrapped.Id())\n\t\treturn wrapped\n\t}\n\n\treturn nil\n}\n\nfunc Listen(addr, typ string, tlsCfg *tls.Config) (l *Listener, err error) {\n\t// listen for incoming connections\n\tlistener, err := net.Listen(\"tcp\", addr)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tl = &Listener{\n\t\tAddr:  listener.Addr(),\n\t\tConns: make(chan *loggedConn),\n\t}\n\n\tgo func() {\n\t\tfor {\n\t\t\trawConn, err := listener.Accept()\n\t\t\tif err != nil {\n\t\t\t\tlog.Error(\"Failed to accept new TCP connection of type %s: %v\", typ, err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tc := wrapConn(rawConn, typ)\n\t\t\tif tlsCfg != nil {\n\t\t\t\tc.Conn = tls.Server(c.Conn, tlsCfg)\n\t\t\t}\n\t\t\tc.Info(\"New connection from %v\", c.RemoteAddr())\n\t\t\tl.Conns <- c\n\t\t}\n\t}()\n\treturn\n}\n\nfunc Wrap(conn net.Conn, typ string) *loggedConn {\n\treturn wrapConn(conn, typ)\n}\n\nfunc Dial(addr, typ string, tlsCfg *tls.Config) (conn *loggedConn, err error) {\n\tvar rawConn net.Conn\n\tif rawConn, err = net.Dial(\"tcp\", addr); err != nil {\n\t\treturn\n\t}\n\n\tconn = wrapConn(rawConn, typ)\n\tconn.Debug(\"New connection to: %v\", rawConn.RemoteAddr())\n\n\tif tlsCfg != nil {\n\t\tconn.StartTLS(tlsCfg)\n\t}\n\n\treturn\n}\n\nfunc DialHttpProxy(proxyUrl, addr, typ string, tlsCfg *tls.Config) (conn *loggedConn, err error) {\n\t// parse the proxy address\n\tvar parsedUrl *url.URL\n\tif parsedUrl, err = url.Parse(proxyUrl); err != nil {\n\t\treturn\n\t}\n\n\tvar proxyAuth string\n\tif parsedUrl.User != nil {\n\t\tproxyAuth = \"Basic \" + base64.StdEncoding.EncodeToString([]byte(parsedUrl.User.String()))\n\t}\n\n\tvar proxyTlsConfig *tls.Config\n\tswitch parsedUrl.Scheme {\n\tcase \"http\":\n\t\tproxyTlsConfig = nil\n\tcase \"https\":\n\t\tproxyTlsConfig = new(tls.Config)\n\tdefault:\n\t\terr = fmt.Errorf(\"Proxy URL scheme must be http or https, got: %s\", parsedUrl.Scheme)\n\t\treturn\n\t}\n\n\t// dial the proxy\n\tif conn, err = Dial(parsedUrl.Host, typ, proxyTlsConfig); err != nil {\n\t\treturn\n\t}\n\n\t// send an HTTP proxy CONNECT message\n\treq, err := http.NewRequest(\"CONNECT\", \"https://\"+addr, nil)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif proxyAuth != \"\" {\n\t\treq.Header.Set(\"Proxy-Authorization\", proxyAuth)\n\t}\n\treq.Header.Set(\"User-Agent\", \"Mozilla/5.0 (compatible; ngrok)\")\n\treq.Write(conn)\n\n\t// read the proxy's response\n\tresp, err := http.ReadResponse(bufio.NewReader(conn), req)\n\tif err != nil {\n\t\treturn\n\t}\n\tresp.Body.Close()\n\n\tif resp.StatusCode != 200 {\n\t\terr = fmt.Errorf(\"Non-200 response from proxy server: %s\", resp.Status)\n\t\treturn\n\t}\n\n\t// upgrade to TLS\n\tconn.StartTLS(tlsCfg)\n\n\treturn\n}\n\nfunc (c *loggedConn) StartTLS(tlsCfg *tls.Config) {\n\tc.Conn = tls.Client(c.Conn, tlsCfg)\n}\n\nfunc (c *loggedConn) Close() (err error) {\n\tif err := c.Conn.Close(); err == nil {\n\t\tc.Debug(\"Closing\")\n\t}\n\treturn\n}\n\nfunc (c *loggedConn) Id() string {\n\treturn fmt.Sprintf(\"%s:%x\", c.typ, c.id)\n}\n\nfunc (c *loggedConn) SetType(typ string) {\n\toldId := c.Id()\n\tc.typ = typ\n\tc.ClearLogPrefixes()\n\tc.AddLogPrefix(c.Id())\n\tc.Info(\"Renamed connection %s\", oldId)\n}\n\nfunc (c *loggedConn) CloseRead() error {\n\t// XXX: use CloseRead() in Conn.Join() and in Control.shutdown() for cleaner\n\t// connection termination. Unfortunately, when I've tried that, I've observed\n\t// failures where the connection was closed *before* flushing its write buffer,\n\t// set with SetLinger() set properly (which it is by default).\n\treturn c.tcp.CloseRead()\n}\n\nfunc Join(c Conn, c2 Conn) (int64, int64) {\n\tvar wait sync.WaitGroup\n\n\tpipe := func(to Conn, from Conn, bytesCopied *int64) {\n\t\tdefer to.Close()\n\t\tdefer from.Close()\n\t\tdefer wait.Done()\n\n\t\tvar err error\n\t\t*bytesCopied, err = io.Copy(to, from)\n\t\tif err != nil {\n\t\t\tfrom.Warn(\"Copied %d bytes to %s before failing with error %v\", *bytesCopied, to.Id(), err)\n\t\t} else {\n\t\t\tfrom.Debug(\"Copied %d bytes to %s\", *bytesCopied, to.Id())\n\t\t}\n\t}\n\n\twait.Add(2)\n\tvar fromBytes, toBytes int64\n\tgo pipe(c, c2, &fromBytes)\n\tgo pipe(c2, c, &toBytes)\n\tc.Info(\"Joined with connection %s\", c2.Id())\n\twait.Wait()\n\treturn fromBytes, toBytes\n}\n"
  },
  {
    "path": "src/ngrok/conn/tee.go",
    "content": "package conn\n\nimport (\n\t\"bufio\"\n\t\"io\"\n)\n\n// conn.Tee is a wraps a conn.Conn\n// causing all writes/reads to be tee'd just\n// like the unix command such that all data that\n// is read and written to the connection through its\n// interfaces will also be copied into two dedicated pipes\n// used for consuming a copy of the data stream\n//\n// this is useful for introspecting the traffic flowing\n// over a connection without having to tamper with the actual\n// code that reads and writes over the connection\n//\n// NB: the data is Tee'd into a shared-memory io.Pipe which\n// has a limited (and small) buffer. If you are not consuming from\n// the ReadBuffer() and WriteBuffer(), you are going to block\n// your application's real traffic from flowing over the connection\n\ntype Tee struct {\n\trd       io.Reader\n\twr       io.Writer\n\treadPipe struct {\n\t\trd *io.PipeReader\n\t\twr *io.PipeWriter\n\t}\n\twritePipe struct {\n\t\trd *io.PipeReader\n\t\twr *io.PipeWriter\n\t}\n\tConn\n}\n\nfunc NewTee(conn Conn) *Tee {\n\tc := &Tee{\n\t\trd:   nil,\n\t\twr:   nil,\n\t\tConn: conn,\n\t}\n\n\tc.readPipe.rd, c.readPipe.wr = io.Pipe()\n\tc.writePipe.rd, c.writePipe.wr = io.Pipe()\n\n\tc.rd = io.TeeReader(c.Conn, c.readPipe.wr)\n\tc.wr = io.MultiWriter(c.Conn, c.writePipe.wr)\n\treturn c\n}\n\nfunc (c *Tee) ReadBuffer() *bufio.Reader {\n\treturn bufio.NewReader(c.readPipe.rd)\n}\n\nfunc (c *Tee) WriteBuffer() *bufio.Reader {\n\treturn bufio.NewReader(c.writePipe.rd)\n}\n\nfunc (c *Tee) Read(b []byte) (n int, err error) {\n\tn, err = c.rd.Read(b)\n\tif err != nil {\n\t\tc.readPipe.wr.Close()\n\t}\n\treturn\n}\n\nfunc (c *Tee) ReadFrom(r io.Reader) (n int64, err error) {\n\tn, err = io.Copy(c.wr, r)\n\tif err != nil {\n\t\tc.writePipe.wr.Close()\n\t}\n\treturn\n}\n\nfunc (c *Tee) Write(b []byte) (n int, err error) {\n\tn, err = c.wr.Write(b)\n\tif err != nil {\n\t\tc.writePipe.wr.Close()\n\t}\n\treturn\n}\n"
  },
  {
    "path": "src/ngrok/log/logger.go",
    "content": "package log\n\nimport (\n\t\"fmt\"\n\tlog \"github.com/alecthomas/log4go\"\n)\n\nvar root log.Logger = make(log.Logger)\n\nfunc LogTo(target string, level_name string) {\n\tvar writer log.LogWriter = nil\n\n\tswitch target {\n\tcase \"stdout\":\n\t\twriter = log.NewConsoleLogWriter()\n\tcase \"none\":\n\t\t// no logging\n\tdefault:\n\t\twriter = log.NewFileLogWriter(target, true)\n\t}\n\n\tif writer != nil {\n\t\tvar level = log.DEBUG\n\n\t\tswitch level_name {\n\t\tcase \"FINEST\":\n\t\t\tlevel = log.FINEST\n\t\tcase \"FINE\":\n\t\t\tlevel = log.FINE\n\t\tcase \"DEBUG\":\n\t\t\tlevel = log.DEBUG\n\t\tcase \"TRACE\":\n\t\t\tlevel = log.TRACE\n\t\tcase \"INFO\":\n\t\t\tlevel = log.INFO\n\t\tcase \"WARNING\":\n\t\t\tlevel = log.WARNING\n\t\tcase \"ERROR\":\n\t\t\tlevel = log.ERROR\n\t\tcase \"CRITICAL\":\n\t\t\tlevel = log.CRITICAL\n\t\tdefault:\n\t\t\tlevel = log.DEBUG\n\t\t}\n\n\t\troot.AddFilter(\"log\", level, writer)\n\t}\n}\n\ntype Logger interface {\n\tAddLogPrefix(string)\n\tClearLogPrefixes()\n\tDebug(string, ...interface{})\n\tInfo(string, ...interface{})\n\tWarn(string, ...interface{}) error\n\tError(string, ...interface{}) error\n}\n\ntype PrefixLogger struct {\n\t*log.Logger\n\tprefix string\n}\n\nfunc NewPrefixLogger(prefixes ...string) Logger {\n\tlogger := &PrefixLogger{Logger: &root}\n\n\tfor _, p := range prefixes {\n\t\tlogger.AddLogPrefix(p)\n\t}\n\n\treturn logger\n}\n\nfunc (pl *PrefixLogger) pfx(fmtstr string) interface{} {\n\treturn fmt.Sprintf(\"%s %s\", pl.prefix, fmtstr)\n}\n\nfunc (pl *PrefixLogger) Debug(arg0 string, args ...interface{}) {\n\tpl.Logger.Debug(pl.pfx(arg0), args...)\n}\n\nfunc (pl *PrefixLogger) Info(arg0 string, args ...interface{}) {\n\tpl.Logger.Info(pl.pfx(arg0), args...)\n}\n\nfunc (pl *PrefixLogger) Warn(arg0 string, args ...interface{}) error {\n\treturn pl.Logger.Warn(pl.pfx(arg0), args...)\n}\n\nfunc (pl *PrefixLogger) Error(arg0 string, args ...interface{}) error {\n\treturn pl.Logger.Error(pl.pfx(arg0), args...)\n}\n\nfunc (pl *PrefixLogger) AddLogPrefix(prefix string) {\n\tif len(pl.prefix) > 0 {\n\t\tpl.prefix += \" \"\n\t}\n\n\tpl.prefix += \"[\" + prefix + \"]\"\n}\n\nfunc (pl *PrefixLogger) ClearLogPrefixes() {\n\tpl.prefix = \"\"\n}\n\n// we should never really use these . . . always prefer logging through a prefix logger\nfunc Debug(arg0 string, args ...interface{}) {\n\troot.Debug(arg0, args...)\n}\n\nfunc Info(arg0 string, args ...interface{}) {\n\troot.Info(arg0, args...)\n}\n\nfunc Warn(arg0 string, args ...interface{}) error {\n\treturn root.Warn(arg0, args...)\n}\n\nfunc Error(arg0 string, args ...interface{}) error {\n\treturn root.Error(arg0, args...)\n}\n"
  },
  {
    "path": "src/ngrok/main/ngrok/ngrok.go",
    "content": "package main\n\nimport (\n\t\"ngrok/client\"\n)\n\nfunc main() {\n\tclient.Main()\n}\n"
  },
  {
    "path": "src/ngrok/main/ngrokd/ngrokd.go",
    "content": "package main\n\nimport (\n\t\"ngrok/server\"\n)\n\nfunc main() {\n\tserver.Main()\n}\n"
  },
  {
    "path": "src/ngrok/msg/conn.go",
    "content": "package msg\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"ngrok/conn\"\n)\n\nfunc readMsgShared(c conn.Conn) (buffer []byte, err error) {\n\tc.Debug(\"Waiting to read message\")\n\n\tvar sz int64\n\terr = binary.Read(c, binary.LittleEndian, &sz)\n\tif err != nil {\n\t\treturn\n\t}\n\tc.Debug(\"Reading message with length: %d\", sz)\n\n\tbuffer = make([]byte, sz)\n\tn, err := c.Read(buffer)\n\tc.Debug(\"Read message %s\", buffer)\n\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif int64(n) != sz {\n\t\terr = errors.New(fmt.Sprintf(\"Expected to read %d bytes, but only read %d\", sz, n))\n\t\treturn\n\t}\n\n\treturn\n}\n\nfunc ReadMsg(c conn.Conn) (msg Message, err error) {\n\tbuffer, err := readMsgShared(c)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treturn Unpack(buffer)\n}\n\nfunc ReadMsgInto(c conn.Conn, msg Message) (err error) {\n\tbuffer, err := readMsgShared(c)\n\tif err != nil {\n\t\treturn\n\t}\n\treturn UnpackInto(buffer, msg)\n}\n\nfunc WriteMsg(c conn.Conn, msg interface{}) (err error) {\n\tbuffer, err := Pack(msg)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tc.Debug(\"Writing message: %s\", string(buffer))\n\terr = binary.Write(c, binary.LittleEndian, int64(len(buffer)))\n\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif _, err = c.Write(buffer); err != nil {\n\t\treturn\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "src/ngrok/msg/msg.go",
    "content": "package msg\n\nimport (\n\t\"encoding/json\"\n\t\"reflect\"\n)\n\nvar TypeMap map[string]reflect.Type\n\nfunc init() {\n\tTypeMap = make(map[string]reflect.Type)\n\n\tt := func(obj interface{}) reflect.Type { return reflect.TypeOf(obj).Elem() }\n\tTypeMap[\"Auth\"] = t((*Auth)(nil))\n\tTypeMap[\"AuthResp\"] = t((*AuthResp)(nil))\n\tTypeMap[\"ReqTunnel\"] = t((*ReqTunnel)(nil))\n\tTypeMap[\"NewTunnel\"] = t((*NewTunnel)(nil))\n\tTypeMap[\"RegProxy\"] = t((*RegProxy)(nil))\n\tTypeMap[\"ReqProxy\"] = t((*ReqProxy)(nil))\n\tTypeMap[\"StartProxy\"] = t((*StartProxy)(nil))\n\tTypeMap[\"Ping\"] = t((*Ping)(nil))\n\tTypeMap[\"Pong\"] = t((*Pong)(nil))\n}\n\ntype Message interface{}\n\ntype Envelope struct {\n\tType    string\n\tPayload json.RawMessage\n}\n\n// When a client opens a new control channel to the server\n// it must start by sending an Auth message.\ntype Auth struct {\n\tVersion   string // protocol version\n\tMmVersion string // major/minor software version (informational only)\n\tUser      string\n\tPassword  string\n\tOS        string\n\tArch      string\n\tClientId  string // empty for new sessions\n}\n\n// A server responds to an Auth message with an\n// AuthResp message over the control channel.\n//\n// If Error is not the empty string\n// the server has indicated it will not accept\n// the new session and will close the connection.\n//\n// The server response includes a unique ClientId\n// that is used to associate and authenticate future\n// proxy connections via the same field in RegProxy messages.\ntype AuthResp struct {\n\tVersion   string\n\tMmVersion string\n\tClientId  string\n\tError     string\n}\n\n// A client sends this message to the server over the control channel\n// to request a new tunnel be opened on the client's behalf.\n// ReqId is a random number set by the client that it can pull\n// from future NewTunnel's to correlate then to the requesting ReqTunnel.\ntype ReqTunnel struct {\n\tReqId    string\n\tProtocol string\n\n\t// http only\n\tHostname  string\n\tSubdomain string\n\tHttpAuth  string\n\n\t// tcp only\n\tRemotePort uint16\n}\n\n// When the server opens a new tunnel on behalf of\n// a client, it sends a NewTunnel message to notify the client.\n// ReqId is the ReqId from the corresponding ReqTunnel message.\n//\n// A client may receive *multiple* NewTunnel messages from a single\n// ReqTunnel. (ex. A client opens an https tunnel and the server\n// chooses to open an http tunnel of the same name as well)\ntype NewTunnel struct {\n\tReqId    string\n\tUrl      string\n\tProtocol string\n\tError    string\n}\n\n// When the server wants to initiate a new tunneled connection, it sends\n// this message over the control channel to the client. When a client receives\n// this message, it must initiate a new proxy connection to the server.\ntype ReqProxy struct {\n}\n\n// After a client receives a ReqProxy message, it opens a new\n// connection to the server and sends a RegProxy message.\ntype RegProxy struct {\n\tClientId string\n}\n\n// This message is sent by the server to the client over a *proxy* connection before it\n// begins to send the bytes of the proxied request.\ntype StartProxy struct {\n\tUrl        string // URL of the tunnel this connection connection is being proxied for\n\tClientAddr string // Network address of the client initiating the connection to the tunnel\n}\n\n// A client or server may send this message periodically over\n// the control channel to request that the remote side acknowledge\n// its connection is still alive. The remote side must respond with a Pong.\ntype Ping struct {\n}\n\n// Sent by a client or server over the control channel to indicate\n// it received a Ping.\ntype Pong struct {\n}\n"
  },
  {
    "path": "src/ngrok/msg/pack.go",
    "content": "package msg\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n)\n\nfunc unpack(buffer []byte, msgIn Message) (msg Message, err error) {\n\tvar env Envelope\n\tif err = json.Unmarshal(buffer, &env); err != nil {\n\t\treturn\n\t}\n\n\tif msgIn == nil {\n\t\tt, ok := TypeMap[env.Type]\n\n\t\tif !ok {\n\t\t\terr = errors.New(fmt.Sprintf(\"Unsupported message type %s\", env.Type))\n\t\t\treturn\n\t\t}\n\n\t\t// guess type\n\t\tmsg = reflect.New(t).Interface().(Message)\n\t} else {\n\t\tmsg = msgIn\n\t}\n\n\terr = json.Unmarshal(env.Payload, &msg)\n\treturn\n}\n\nfunc UnpackInto(buffer []byte, msg Message) (err error) {\n\t_, err = unpack(buffer, msg)\n\treturn\n}\n\nfunc Unpack(buffer []byte) (msg Message, err error) {\n\treturn unpack(buffer, nil)\n}\n\nfunc Pack(payload interface{}) ([]byte, error) {\n\treturn json.Marshal(struct {\n\t\tType    string\n\t\tPayload interface{}\n\t}{\n\t\tType:    reflect.TypeOf(payload).Elem().Name(),\n\t\tPayload: payload,\n\t})\n}\n"
  },
  {
    "path": "src/ngrok/proto/http.go",
    "content": "package proto\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n\t\"ngrok/conn\"\n\t\"ngrok/util\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tmetrics \"github.com/rcrowley/go-metrics\"\n)\n\ntype HttpRequest struct {\n\t*http.Request\n\tBodyBytes []byte\n}\n\ntype HttpResponse struct {\n\t*http.Response\n\tBodyBytes []byte\n}\n\ntype HttpTxn struct {\n\tReq         *HttpRequest\n\tResp        *HttpResponse\n\tStart       time.Time\n\tDuration    time.Duration\n\tUserCtx     interface{}\n\tConnUserCtx interface{}\n}\n\ntype Http struct {\n\tTxns     *util.Broadcast\n\treqGauge metrics.Gauge\n\treqMeter metrics.Meter\n\treqTimer metrics.Timer\n}\n\nfunc NewHttp() *Http {\n\treturn &Http{\n\t\tTxns:     util.NewBroadcast(),\n\t\treqGauge: metrics.NewGauge(),\n\t\treqMeter: metrics.NewMeter(),\n\t\treqTimer: metrics.NewTimer(),\n\t}\n}\n\nfunc extractBody(r io.Reader) ([]byte, io.ReadCloser, error) {\n\tbuf := new(bytes.Buffer)\n\t_, err := buf.ReadFrom(r)\n\treturn buf.Bytes(), ioutil.NopCloser(buf), err\n}\n\nfunc (h *Http) GetName() string { return \"http\" }\n\nfunc (h *Http) WrapConn(c conn.Conn, ctx interface{}) conn.Conn {\n\ttee := conn.NewTee(c)\n\tlastTxn := make(chan *HttpTxn)\n\tgo h.readRequests(tee, lastTxn, ctx)\n\tgo h.readResponses(tee, lastTxn)\n\treturn tee\n}\n\nfunc (h *Http) readRequests(tee *conn.Tee, lastTxn chan *HttpTxn, connCtx interface{}) {\n\tdefer close(lastTxn)\n\n\tfor {\n\t\treq, err := http.ReadRequest(tee.WriteBuffer())\n\t\tif err != nil {\n\t\t\t// no more requests to be read, we're done\n\t\t\tbreak\n\t\t}\n\n\t\t// make sure we read the body of the request so that\n\t\t// we don't block the writer\n\t\t_, err = httputil.DumpRequest(req, true)\n\n\t\th.reqMeter.Mark(1)\n\t\tif err != nil {\n\t\t\ttee.Warn(\"Failed to extract request body: %v\", err)\n\t\t}\n\n\t\t// golang's ReadRequest/DumpRequestOut is broken. Fix up the request so it works later\n\t\treq.URL.Scheme = \"http\"\n\t\treq.URL.Host = req.Host\n\n\t\ttxn := &HttpTxn{Start: time.Now(), ConnUserCtx: connCtx}\n\t\ttxn.Req = &HttpRequest{Request: req}\n\t\tif req.Body != nil {\n\t\t\ttxn.Req.BodyBytes, txn.Req.Body, err = extractBody(req.Body)\n\t\t\tif err != nil {\n\t\t\t\ttee.Warn(\"Failed to extract request body: %v\", err)\n\t\t\t}\n\t\t}\n\n\t\tlastTxn <- txn\n\t\th.Txns.In() <- txn\n\t}\n}\n\nfunc (h *Http) readResponses(tee *conn.Tee, lastTxn chan *HttpTxn) {\n\tfor txn := range lastTxn {\n\t\tresp, err := http.ReadResponse(tee.ReadBuffer(), txn.Req.Request)\n\t\ttxn.Duration = time.Since(txn.Start)\n\t\th.reqTimer.Update(txn.Duration)\n\t\tif err != nil {\n\t\t\ttee.Warn(\"Error reading response from server: %v\", err)\n\t\t\t// no more responses to be read, we're done\n\t\t\tbreak\n\t\t}\n\t\t// make sure we read the body of the response so that\n\t\t// we don't block the reader\n\t\t_, _ = httputil.DumpResponse(resp, true)\n\n\t\ttxn.Resp = &HttpResponse{Response: resp}\n\t\t// apparently, Body can be nil in some cases\n\t\tif resp.Body != nil {\n\t\t\ttxn.Resp.BodyBytes, txn.Resp.Body, err = extractBody(resp.Body)\n\t\t\tif err != nil {\n\t\t\t\ttee.Warn(\"Failed to extract response body: %v\", err)\n\t\t\t}\n\t\t}\n\n\t\th.Txns.In() <- txn\n\n\t\t// XXX: remove web socket shim in favor of a real websocket protocol analyzer\n\t\tif txn.Req.Header.Get(\"Upgrade\") == \"websocket\" {\n\t\t\ttee.Info(\"Upgrading to websocket\")\n\t\t\tvar wg sync.WaitGroup\n\n\t\t\t// shim for websockets\n\t\t\t// in order for websockets to work, we need to continue reading all of the\n\t\t\t// the bytes in the analyzer so that the joined connections will continue\n\t\t\t// sending bytes to each other\n\t\t\twg.Add(2)\n\t\t\tgo func() {\n\t\t\t\tioutil.ReadAll(tee.WriteBuffer())\n\t\t\t\twg.Done()\n\t\t\t}()\n\n\t\t\tgo func() {\n\t\t\t\tioutil.ReadAll(tee.ReadBuffer())\n\t\t\t\twg.Done()\n\t\t\t}()\n\n\t\t\twg.Wait()\n\t\t\tbreak\n\t\t}\n\t}\n}\n\n// we have to vendor DumpRequestOut because it's broken and the fix won't be in until at least 1.4\n// XXX: remove this all in favor of actually parsing the HTTP traffic ourselves for more transparent\n// replay and inspection, regardless of when it gets fixed in stdlib\n\n// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n// One of the copies, say from b to r2, could be avoided by using a more\n// elaborate trick where the other copy is made during Request/Response.Write.\n// This would complicate things too much, given that these functions are for\n// debugging only.\nfunc drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {\n\tvar buf bytes.Buffer\n\tif _, err = buf.ReadFrom(b); err != nil {\n\t\treturn nil, nil, err\n\t}\n\tif err = b.Close(); err != nil {\n\t\treturn nil, nil, err\n\t}\n\treturn ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil\n}\n\n// dumpConn is a net.Conn which writes to Writer and reads from Reader\ntype dumpConn struct {\n\tio.Writer\n\tio.Reader\n}\n\nfunc (c *dumpConn) Close() error                       { return nil }\nfunc (c *dumpConn) LocalAddr() net.Addr                { return nil }\nfunc (c *dumpConn) RemoteAddr() net.Addr               { return nil }\nfunc (c *dumpConn) SetDeadline(t time.Time) error      { return nil }\nfunc (c *dumpConn) SetReadDeadline(t time.Time) error  { return nil }\nfunc (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil }\n\ntype neverEnding byte\n\nfunc (b neverEnding) Read(p []byte) (n int, err error) {\n\tfor i := range p {\n\t\tp[i] = byte(b)\n\t}\n\treturn len(p), nil\n}\n\n// DumpRequestOut is like DumpRequest but includes\n// headers that the standard http.Transport adds,\n// such as User-Agent.\nfunc DumpRequestOut(req *http.Request, body bool) ([]byte, error) {\n\tsave := req.Body\n\tdummyBody := false\n\tif !body || req.Body == nil {\n\t\treq.Body = nil\n\t\tif req.ContentLength != 0 {\n\t\t\treq.Body = ioutil.NopCloser(io.LimitReader(neverEnding('x'), req.ContentLength))\n\t\t\tdummyBody = true\n\t\t}\n\t} else {\n\t\tvar err error\n\t\tsave, req.Body, err = drainBody(req.Body)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// Since we're using the actual Transport code to write the request,\n\t// switch to http so the Transport doesn't try to do an SSL\n\t// negotiation with our dumpConn and its bytes.Buffer & pipe.\n\t// The wire format for https and http are the same, anyway.\n\treqSend := req\n\tif req.URL.Scheme == \"https\" {\n\t\treqSend = new(http.Request)\n\t\t*reqSend = *req\n\t\treqSend.URL = new(url.URL)\n\t\t*reqSend.URL = *req.URL\n\t\treqSend.URL.Scheme = \"http\"\n\t}\n\n\t// Use the actual Transport code to record what we would send\n\t// on the wire, but not using TCP.  Use a Transport with a\n\t// custom dialer that returns a fake net.Conn that waits\n\t// for the full input (and recording it), and then responds\n\t// with a dummy response.\n\tvar buf bytes.Buffer // records the output\n\tpr, pw := io.Pipe()\n\tdr := &delegateReader{c: make(chan io.Reader)}\n\t// Wait for the request before replying with a dummy response:\n\tgo func() {\n\t\treq, _ := http.ReadRequest(bufio.NewReader(pr))\n\t\t// THIS IS THE PART THAT'S BROKEN IN THE STDLIB (as of Go 1.3)\n\t\tif req != nil && req.Body != nil {\n\t\t\tioutil.ReadAll(req.Body)\n\t\t}\n\t\tdr.c <- strings.NewReader(\"HTTP/1.1 204 No Content\\r\\n\\r\\n\")\n\t}()\n\n\tt := &http.Transport{\n\t\tDial: func(net, addr string) (net.Conn, error) {\n\t\t\treturn &dumpConn{io.MultiWriter(&buf, pw), dr}, nil\n\t\t},\n\t}\n\tdefer t.CloseIdleConnections()\n\n\t_, err := t.RoundTrip(reqSend)\n\n\treq.Body = save\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdump := buf.Bytes()\n\n\t// If we used a dummy body above, remove it now.\n\t// TODO: if the req.ContentLength is large, we allocate memory\n\t// unnecessarily just to slice it off here.  But this is just\n\t// a debug function, so this is acceptable for now. We could\n\t// discard the body earlier if this matters.\n\tif dummyBody {\n\t\tif i := bytes.Index(dump, []byte(\"\\r\\n\\r\\n\")); i >= 0 {\n\t\t\tdump = dump[:i+4]\n\t\t}\n\t}\n\treturn dump, nil\n}\n\n// delegateReader is a reader that delegates to another reader,\n// once it arrives on a channel.\ntype delegateReader struct {\n\tc chan io.Reader\n\tr io.Reader // nil until received from c\n}\n\nfunc (r *delegateReader) Read(p []byte) (int, error) {\n\tif r.r == nil {\n\t\tr.r = <-r.c\n\t}\n\treturn r.r.Read(p)\n}\n\n// Return value if nonempty, def otherwise.\nfunc valueOrDefault(value, def string) string {\n\tif value != \"\" {\n\t\treturn value\n\t}\n\treturn def\n}\n\nvar reqWriteExcludeHeaderDump = map[string]bool{\n\t\"Host\":              true, // not in Header map anyway\n\t\"Content-Length\":    true,\n\t\"Transfer-Encoding\": true,\n\t\"Trailer\":           true,\n}\n"
  },
  {
    "path": "src/ngrok/proto/interface.go",
    "content": "package proto\n\nimport (\n\t\"ngrok/conn\"\n)\n\ntype Protocol interface {\n\tGetName() string\n\tWrapConn(conn.Conn, interface{}) conn.Conn\n}\n"
  },
  {
    "path": "src/ngrok/proto/tcp.go",
    "content": "package proto\n\nimport (\n\t\"ngrok/conn\"\n)\n\ntype Tcp struct{}\n\nfunc NewTcp() *Tcp {\n\treturn new(Tcp)\n}\n\nfunc (h *Tcp) GetName() string { return \"tcp\" }\n\nfunc (h *Tcp) WrapConn(c conn.Conn, ctx interface{}) conn.Conn {\n\treturn c\n}\n"
  },
  {
    "path": "src/ngrok/server/cli.go",
    "content": "package server\n\nimport (\n\t\"flag\"\n)\n\ntype Options struct {\n\thttpAddr   string\n\thttpsAddr  string\n\ttunnelAddr string\n\tdomain     string\n\ttlsCrt     string\n\ttlsKey     string\n\tlogto      string\n\tloglevel   string\n}\n\nfunc parseArgs() *Options {\n\thttpAddr := flag.String(\"httpAddr\", \":80\", \"Public address for HTTP connections, empty string to disable\")\n\thttpsAddr := flag.String(\"httpsAddr\", \":443\", \"Public address listening for HTTPS connections, emptry string to disable\")\n\ttunnelAddr := flag.String(\"tunnelAddr\", \":4443\", \"Public address listening for ngrok client\")\n\tdomain := flag.String(\"domain\", \"ngrok.com\", \"Domain where the tunnels are hosted\")\n\ttlsCrt := flag.String(\"tlsCrt\", \"\", \"Path to a TLS certificate file\")\n\ttlsKey := flag.String(\"tlsKey\", \"\", \"Path to a TLS key file\")\n\tlogto := flag.String(\"log\", \"stdout\", \"Write log messages to this file. 'stdout' and 'none' have special meanings\")\n\tloglevel := flag.String(\"log-level\", \"DEBUG\", \"The level of messages to log. One of: DEBUG, INFO, WARNING, ERROR\")\n\tflag.Parse()\n\n\treturn &Options{\n\t\thttpAddr:   *httpAddr,\n\t\thttpsAddr:  *httpsAddr,\n\t\ttunnelAddr: *tunnelAddr,\n\t\tdomain:     *domain,\n\t\ttlsCrt:     *tlsCrt,\n\t\ttlsKey:     *tlsKey,\n\t\tlogto:      *logto,\n\t\tloglevel:   *loglevel,\n\t}\n}\n"
  },
  {
    "path": "src/ngrok/server/control.go",
    "content": "package server\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"ngrok/conn\"\n\t\"ngrok/msg\"\n\t\"ngrok/util\"\n\t\"ngrok/version\"\n\t\"runtime/debug\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst (\n\tpingTimeoutInterval = 30 * time.Second\n\tconnReapInterval    = 10 * time.Second\n\tcontrolWriteTimeout = 10 * time.Second\n\tproxyStaleDuration  = 60 * time.Second\n\tproxyMaxPoolSize    = 10\n)\n\ntype Control struct {\n\t// auth message\n\tauth *msg.Auth\n\n\t// actual connection\n\tconn conn.Conn\n\n\t// put a message in this channel to send it over\n\t// conn to the client\n\tout chan (msg.Message)\n\n\t// read from this channel to get the next message sent\n\t// to us over conn by the client\n\tin chan (msg.Message)\n\n\t// the last time we received a ping from the client - for heartbeats\n\tlastPing time.Time\n\n\t// all of the tunnels this control connection handles\n\ttunnels []*Tunnel\n\n\t// proxy connections\n\tproxies chan conn.Conn\n\n\t// identifier\n\tid string\n\n\t// synchronizer for controlled shutdown of writer()\n\twriterShutdown *util.Shutdown\n\n\t// synchronizer for controlled shutdown of reader()\n\treaderShutdown *util.Shutdown\n\n\t// synchronizer for controlled shutdown of manager()\n\tmanagerShutdown *util.Shutdown\n\n\t// synchronizer for controller shutdown of entire Control\n\tshutdown *util.Shutdown\n}\n\nfunc NewControl(ctlConn conn.Conn, authMsg *msg.Auth) {\n\tvar err error\n\n\t// create the object\n\tc := &Control{\n\t\tauth:            authMsg,\n\t\tconn:            ctlConn,\n\t\tout:             make(chan msg.Message),\n\t\tin:              make(chan msg.Message),\n\t\tproxies:         make(chan conn.Conn, 10),\n\t\tlastPing:        time.Now(),\n\t\twriterShutdown:  util.NewShutdown(),\n\t\treaderShutdown:  util.NewShutdown(),\n\t\tmanagerShutdown: util.NewShutdown(),\n\t\tshutdown:        util.NewShutdown(),\n\t}\n\n\tfailAuth := func(e error) {\n\t\t_ = msg.WriteMsg(ctlConn, &msg.AuthResp{Error: e.Error()})\n\t\tctlConn.Close()\n\t}\n\n\t// register the clientid\n\tc.id = authMsg.ClientId\n\tif c.id == \"\" {\n\t\t// it's a new session, assign an ID\n\t\tif c.id, err = util.SecureRandId(16); err != nil {\n\t\t\tfailAuth(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\t// set logging prefix\n\tctlConn.SetType(\"ctl\")\n\tctlConn.AddLogPrefix(c.id)\n\n\tif authMsg.Version != version.Proto {\n\t\tfailAuth(fmt.Errorf(\"Incompatible versions. Server %s, client %s. Download a new version at http://ngrok.com\", version.MajorMinor(), authMsg.Version))\n\t\treturn\n\t}\n\n\t// register the control\n\tif replaced := controlRegistry.Add(c.id, c); replaced != nil {\n\t\treplaced.shutdown.WaitComplete()\n\t}\n\n\t// start the writer first so that the following messages get sent\n\tgo c.writer()\n\n\t// Respond to authentication\n\tc.out <- &msg.AuthResp{\n\t\tVersion:   version.Proto,\n\t\tMmVersion: version.MajorMinor(),\n\t\tClientId:  c.id,\n\t}\n\n\t// As a performance optimization, ask for a proxy connection up front\n\tc.out <- &msg.ReqProxy{}\n\n\t// manage the connection\n\tgo c.manager()\n\tgo c.reader()\n\tgo c.stopper()\n}\n\n// Register a new tunnel on this control connection\nfunc (c *Control) registerTunnel(rawTunnelReq *msg.ReqTunnel) {\n\tfor _, proto := range strings.Split(rawTunnelReq.Protocol, \"+\") {\n\t\ttunnelReq := *rawTunnelReq\n\t\ttunnelReq.Protocol = proto\n\n\t\tc.conn.Debug(\"Registering new tunnel\")\n\t\tt, err := NewTunnel(&tunnelReq, c)\n\t\tif err != nil {\n\t\t\tc.out <- &msg.NewTunnel{Error: err.Error()}\n\t\t\tif len(c.tunnels) == 0 {\n\t\t\t\tc.shutdown.Begin()\n\t\t\t}\n\n\t\t\t// we're done\n\t\t\treturn\n\t\t}\n\n\t\t// add it to the list of tunnels\n\t\tc.tunnels = append(c.tunnels, t)\n\n\t\t// acknowledge success\n\t\tc.out <- &msg.NewTunnel{\n\t\t\tUrl:      t.url,\n\t\t\tProtocol: proto,\n\t\t\tReqId:    rawTunnelReq.ReqId,\n\t\t}\n\n\t\trawTunnelReq.Hostname = strings.Replace(t.url, proto+\"://\", \"\", 1)\n\t}\n}\n\nfunc (c *Control) manager() {\n\t// don't crash on panics\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tc.conn.Info(\"Control::manager failed with error %v: %s\", err, debug.Stack())\n\t\t}\n\t}()\n\n\t// kill everything if the control manager stops\n\tdefer c.shutdown.Begin()\n\n\t// notify that manager() has shutdown\n\tdefer c.managerShutdown.Complete()\n\n\t// reaping timer for detecting heartbeat failure\n\treap := time.NewTicker(connReapInterval)\n\tdefer reap.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-reap.C:\n\t\t\tif time.Since(c.lastPing) > pingTimeoutInterval {\n\t\t\t\tc.conn.Info(\"Lost heartbeat\")\n\t\t\t\tc.shutdown.Begin()\n\t\t\t}\n\n\t\tcase mRaw, ok := <-c.in:\n\t\t\t// c.in closes to indicate shutdown\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tswitch m := mRaw.(type) {\n\t\t\tcase *msg.ReqTunnel:\n\t\t\t\tc.registerTunnel(m)\n\n\t\t\tcase *msg.Ping:\n\t\t\t\tc.lastPing = time.Now()\n\t\t\t\tc.out <- &msg.Pong{}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (c *Control) writer() {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tc.conn.Info(\"Control::writer failed with error %v: %s\", err, debug.Stack())\n\t\t}\n\t}()\n\n\t// kill everything if the writer() stops\n\tdefer c.shutdown.Begin()\n\n\t// notify that we've flushed all messages\n\tdefer c.writerShutdown.Complete()\n\n\t// write messages to the control channel\n\tfor m := range c.out {\n\t\tc.conn.SetWriteDeadline(time.Now().Add(controlWriteTimeout))\n\t\tif err := msg.WriteMsg(c.conn, m); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n}\n\nfunc (c *Control) reader() {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tc.conn.Warn(\"Control::reader failed with error %v: %s\", err, debug.Stack())\n\t\t}\n\t}()\n\n\t// kill everything if the reader stops\n\tdefer c.shutdown.Begin()\n\n\t// notify that we're done\n\tdefer c.readerShutdown.Complete()\n\n\t// read messages from the control channel\n\tfor {\n\t\tif msg, err := msg.ReadMsg(c.conn); err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tc.conn.Info(\"EOF\")\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t} else {\n\t\t\t// this can also panic during shutdown\n\t\t\tc.in <- msg\n\t\t}\n\t}\n}\n\nfunc (c *Control) stopper() {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tc.conn.Error(\"Failed to shut down control: %v\", r)\n\t\t}\n\t}()\n\n\t// wait until we're instructed to shutdown\n\tc.shutdown.WaitBegin()\n\n\t// remove ourself from the control registry\n\tcontrolRegistry.Del(c.id)\n\n\t// shutdown manager() so that we have no more work to do\n\tclose(c.in)\n\tc.managerShutdown.WaitComplete()\n\n\t// shutdown writer()\n\tclose(c.out)\n\tc.writerShutdown.WaitComplete()\n\n\t// close connection fully\n\tc.conn.Close()\n\n\t// shutdown all of the tunnels\n\tfor _, t := range c.tunnels {\n\t\tt.Shutdown()\n\t}\n\n\t// shutdown all of the proxy connections\n\tclose(c.proxies)\n\tfor p := range c.proxies {\n\t\tp.Close()\n\t}\n\n\tc.shutdown.Complete()\n\tc.conn.Info(\"Shutdown complete\")\n}\n\nfunc (c *Control) RegisterProxy(conn conn.Conn) {\n\tconn.AddLogPrefix(c.id)\n\n\tconn.SetDeadline(time.Now().Add(proxyStaleDuration))\n\tselect {\n\tcase c.proxies <- conn:\n\t\tconn.Info(\"Registered\")\n\tdefault:\n\t\tconn.Info(\"Proxies buffer is full, discarding.\")\n\t\tconn.Close()\n\t}\n}\n\n// Remove a proxy connection from the pool and return it\n// If not proxy connections are in the pool, request one\n// and wait until it is available\n// Returns an error if we couldn't get a proxy because it took too long\n// or the tunnel is closing\nfunc (c *Control) GetProxy() (proxyConn conn.Conn, err error) {\n\tvar ok bool\n\n\t// get a proxy connection from the pool\n\tselect {\n\tcase proxyConn, ok = <-c.proxies:\n\t\tif !ok {\n\t\t\terr = fmt.Errorf(\"No proxy connections available, control is closing\")\n\t\t\treturn\n\t\t}\n\tdefault:\n\t\t// no proxy available in the pool, ask for one over the control channel\n\t\tc.conn.Debug(\"No proxy in pool, requesting proxy from control . . .\")\n\t\tif err = util.PanicToError(func() { c.out <- &msg.ReqProxy{} }); err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tselect {\n\t\tcase proxyConn, ok = <-c.proxies:\n\t\t\tif !ok {\n\t\t\t\terr = fmt.Errorf(\"No proxy connections available, control is closing\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\tcase <-time.After(pingTimeoutInterval):\n\t\t\terr = fmt.Errorf(\"Timeout trying to get proxy connection\")\n\t\t\treturn\n\t\t}\n\t}\n\treturn\n}\n\n// Called when this control is replaced by another control\n// this can happen if the network drops out and the client reconnects\n// before the old tunnel has lost its heartbeat\nfunc (c *Control) Replaced(replacement *Control) {\n\tc.conn.Info(\"Replaced by control: %s\", replacement.conn.Id())\n\n\t// set the control id to empty string so that when stopper()\n\t// calls registry.Del it won't delete the replacement\n\tc.id = \"\"\n\n\t// tell the old one to shutdown\n\tc.shutdown.Begin()\n}\n"
  },
  {
    "path": "src/ngrok/server/http.go",
    "content": "package server\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\tvhost \"github.com/inconshreveable/go-vhost\"\n\t//\"net\"\n\t\"ngrok/conn\"\n\t\"ngrok/log\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst (\n\tNotAuthorized = `HTTP/1.0 401 Not Authorized\nWWW-Authenticate: Basic realm=\"ngrok\"\nContent-Length: 23\n\nAuthorization required\n`\n\n\tNotFound = `HTTP/1.0 404 Not Found\nContent-Length: %d\n\nTunnel %s not found\n`\n\n\tBadRequest = `HTTP/1.0 400 Bad Request\nContent-Length: 12\n\nBad Request\n`\n)\n\n// Listens for new http(s) connections from the public internet\nfunc startHttpListener(addr string, tlsCfg *tls.Config) (listener *conn.Listener) {\n\t// bind/listen for incoming connections\n\tvar err error\n\tif listener, err = conn.Listen(addr, \"pub\", tlsCfg); err != nil {\n\t\tpanic(err)\n\t}\n\n\tproto := \"http\"\n\tif tlsCfg != nil {\n\t\tproto = \"https\"\n\t}\n\n\tlog.Info(\"Listening for public %s connections on %v\", proto, listener.Addr.String())\n\tgo func() {\n\t\tfor conn := range listener.Conns {\n\t\t\tgo httpHandler(conn, proto)\n\t\t}\n\t}()\n\n\treturn\n}\n\n// Handles a new http connection from the public internet\nfunc httpHandler(c conn.Conn, proto string) {\n\tdefer c.Close()\n\tdefer func() {\n\t\t// recover from failures\n\t\tif r := recover(); r != nil {\n\t\t\tc.Warn(\"httpHandler failed with error %v\", r)\n\t\t}\n\t}()\n\n\t// Make sure we detect dead connections while we decide how to multiplex\n\tc.SetDeadline(time.Now().Add(connReadTimeout))\n\n\t// multiplex by extracting the Host header, the vhost library\n\tvhostConn, err := vhost.HTTP(c)\n\tif err != nil {\n\t\tc.Warn(\"Failed to read valid %s request: %v\", proto, err)\n\t\tc.Write([]byte(BadRequest))\n\t\treturn\n\t}\n\n\t// read out the Host header and auth from the request\n\thost := strings.ToLower(vhostConn.Host())\n\tauth := vhostConn.Request.Header.Get(\"Authorization\")\n\n\t// done reading mux data, free up the request memory\n\tvhostConn.Free()\n\n\t// We need to read from the vhost conn now since it mucked around reading the stream\n\tc = conn.Wrap(vhostConn, \"pub\")\n\n\t// multiplex to find the right backend host\n\tc.Debug(\"Found hostname %s in request\", host)\n\ttunnel := tunnelRegistry.Get(fmt.Sprintf(\"%s://%s\", proto, host))\n\tif tunnel == nil {\n\t\tc.Info(\"No tunnel found for hostname %s\", host)\n\t\tc.Write([]byte(fmt.Sprintf(NotFound, len(host)+18, host)))\n\t\treturn\n\t}\n\n\t// If the client specified http auth and it doesn't match this request's auth\n\t// then fail the request with 401 Not Authorized and request the client reissue the\n\t// request with basic authdeny the request\n\tif tunnel.req.HttpAuth != \"\" && auth != tunnel.req.HttpAuth {\n\t\tc.Info(\"Authentication failed: %s\", auth)\n\t\tc.Write([]byte(NotAuthorized))\n\t\treturn\n\t}\n\n\t// dead connections will now be handled by tunnel heartbeating and the client\n\tc.SetDeadline(time.Time{})\n\n\t// let the tunnel handle the connection now\n\ttunnel.HandlePublicConnection(c)\n}\n"
  },
  {
    "path": "src/ngrok/server/main.go",
    "content": "package server\n\nimport (\n\t\"crypto/tls\"\n\t\"math/rand\"\n\t\"ngrok/conn\"\n\tlog \"ngrok/log\"\n\t\"ngrok/msg\"\n\t\"ngrok/util\"\n\t\"os\"\n\t\"runtime/debug\"\n\t\"time\"\n)\n\nconst (\n\tregistryCacheSize uint64        = 1024 * 1024 // 1 MB\n\tconnReadTimeout   time.Duration = 10 * time.Second\n)\n\n// GLOBALS\nvar (\n\ttunnelRegistry  *TunnelRegistry\n\tcontrolRegistry *ControlRegistry\n\n\t// XXX: kill these global variables - they're only used in tunnel.go for constructing forwarding URLs\n\topts      *Options\n\tlisteners map[string]*conn.Listener\n)\n\nfunc NewProxy(pxyConn conn.Conn, regPxy *msg.RegProxy) {\n\t// fail gracefully if the proxy connection fails to register\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tpxyConn.Warn(\"Failed with error: %v\", r)\n\t\t\tpxyConn.Close()\n\t\t}\n\t}()\n\n\t// set logging prefix\n\tpxyConn.SetType(\"pxy\")\n\n\t// look up the control connection for this proxy\n\tpxyConn.Info(\"Registering new proxy for %s\", regPxy.ClientId)\n\tctl := controlRegistry.Get(regPxy.ClientId)\n\n\tif ctl == nil {\n\t\tpanic(\"No client found for identifier: \" + regPxy.ClientId)\n\t}\n\n\tctl.RegisterProxy(pxyConn)\n}\n\n// Listen for incoming control and proxy connections\n// We listen for incoming control and proxy connections on the same port\n// for ease of deployment. The hope is that by running on port 443, using\n// TLS and running all connections over the same port, we can bust through\n// restrictive firewalls.\nfunc tunnelListener(addr string, tlsConfig *tls.Config) {\n\t// listen for incoming connections\n\tlistener, err := conn.Listen(addr, \"tun\", tlsConfig)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tlog.Info(\"Listening for control and proxy connections on %s\", listener.Addr.String())\n\tfor c := range listener.Conns {\n\t\tgo func(tunnelConn conn.Conn) {\n\t\t\t// don't crash on panics\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\ttunnelConn.Info(\"tunnelListener failed with error %v: %s\", r, debug.Stack())\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\ttunnelConn.SetReadDeadline(time.Now().Add(connReadTimeout))\n\t\t\tvar rawMsg msg.Message\n\t\t\tif rawMsg, err = msg.ReadMsg(tunnelConn); err != nil {\n\t\t\t\ttunnelConn.Warn(\"Failed to read message: %v\", err)\n\t\t\t\ttunnelConn.Close()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// don't timeout after the initial read, tunnel heartbeating will kill\n\t\t\t// dead connections\n\t\t\ttunnelConn.SetReadDeadline(time.Time{})\n\n\t\t\tswitch m := rawMsg.(type) {\n\t\t\tcase *msg.Auth:\n\t\t\t\tNewControl(tunnelConn, m)\n\n\t\t\tcase *msg.RegProxy:\n\t\t\t\tNewProxy(tunnelConn, m)\n\n\t\t\tdefault:\n\t\t\t\ttunnelConn.Close()\n\t\t\t}\n\t\t}(c)\n\t}\n}\n\nfunc Main() {\n\t// parse options\n\topts = parseArgs()\n\n\t// init logging\n\tlog.LogTo(opts.logto, opts.loglevel)\n\n\t// seed random number generator\n\tseed, err := util.RandomSeed()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\trand.Seed(seed)\n\n\t// init tunnel/control registry\n\tregistryCacheFile := os.Getenv(\"REGISTRY_CACHE_FILE\")\n\ttunnelRegistry = NewTunnelRegistry(registryCacheSize, registryCacheFile)\n\tcontrolRegistry = NewControlRegistry()\n\n\t// start listeners\n\tlisteners = make(map[string]*conn.Listener)\n\n\t// load tls configuration\n\ttlsConfig, err := LoadTLSConfig(opts.tlsCrt, opts.tlsKey)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// listen for http\n\tif opts.httpAddr != \"\" {\n\t\tlisteners[\"http\"] = startHttpListener(opts.httpAddr, nil)\n\t}\n\n\t// listen for https\n\tif opts.httpsAddr != \"\" {\n\t\tlisteners[\"https\"] = startHttpListener(opts.httpsAddr, tlsConfig)\n\t}\n\n\t// ngrok clients\n\ttunnelListener(opts.tunnelAddr, tlsConfig)\n}\n"
  },
  {
    "path": "src/ngrok/server/metrics.go",
    "content": "package server\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\tgometrics \"github.com/rcrowley/go-metrics\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"ngrok/conn\"\n\t\"ngrok/log\"\n\t\"os\"\n\t\"time\"\n)\n\nvar metrics Metrics\n\nfunc init() {\n\tkeenApiKey := os.Getenv(\"KEEN_API_KEY\")\n\n\tif keenApiKey != \"\" {\n\t\tmetrics = NewKeenIoMetrics(60 * time.Second)\n\t} else {\n\t\tmetrics = NewLocalMetrics(30 * time.Second)\n\t}\n}\n\ntype Metrics interface {\n\tlog.Logger\n\tOpenConnection(*Tunnel, conn.Conn)\n\tCloseConnection(*Tunnel, conn.Conn, time.Time, int64, int64)\n\tOpenTunnel(*Tunnel)\n\tCloseTunnel(*Tunnel)\n}\n\ntype LocalMetrics struct {\n\tlog.Logger\n\treportInterval time.Duration\n\twindowsCounter gometrics.Counter\n\tlinuxCounter   gometrics.Counter\n\tosxCounter     gometrics.Counter\n\totherCounter   gometrics.Counter\n\n\ttunnelMeter        gometrics.Meter\n\ttcpTunnelMeter     gometrics.Meter\n\thttpTunnelMeter    gometrics.Meter\n\tconnMeter          gometrics.Meter\n\tlostHeartbeatMeter gometrics.Meter\n\n\tconnTimer gometrics.Timer\n\n\tbytesInCount  gometrics.Counter\n\tbytesOutCount gometrics.Counter\n\n\t/*\n\t   tunnelGauge gometrics.Gauge\n\t   tcpTunnelGauge gometrics.Gauge\n\t   connGauge gometrics.Gauge\n\t*/\n}\n\nfunc NewLocalMetrics(reportInterval time.Duration) *LocalMetrics {\n\tmetrics := LocalMetrics{\n\t\tLogger:         log.NewPrefixLogger(\"metrics\"),\n\t\treportInterval: reportInterval,\n\t\twindowsCounter: gometrics.NewCounter(),\n\t\tlinuxCounter:   gometrics.NewCounter(),\n\t\tosxCounter:     gometrics.NewCounter(),\n\t\totherCounter:   gometrics.NewCounter(),\n\n\t\ttunnelMeter:        gometrics.NewMeter(),\n\t\ttcpTunnelMeter:     gometrics.NewMeter(),\n\t\thttpTunnelMeter:    gometrics.NewMeter(),\n\t\tconnMeter:          gometrics.NewMeter(),\n\t\tlostHeartbeatMeter: gometrics.NewMeter(),\n\n\t\tconnTimer: gometrics.NewTimer(),\n\n\t\tbytesInCount:  gometrics.NewCounter(),\n\t\tbytesOutCount: gometrics.NewCounter(),\n\n\t\t/*\n\t\t   metrics.tunnelGauge = gometrics.NewGauge(),\n\t\t   metrics.tcpTunnelGauge = gometrics.NewGauge(),\n\t\t   metrics.connGauge = gometrics.NewGauge(),\n\t\t*/\n\t}\n\n\tgo metrics.Report()\n\n\treturn &metrics\n}\n\nfunc (m *LocalMetrics) OpenTunnel(t *Tunnel) {\n\tm.tunnelMeter.Mark(1)\n\n\tswitch t.ctl.auth.OS {\n\tcase \"windows\":\n\t\tm.windowsCounter.Inc(1)\n\tcase \"linux\":\n\t\tm.linuxCounter.Inc(1)\n\tcase \"darwin\":\n\t\tm.osxCounter.Inc(1)\n\tdefault:\n\t\tm.otherCounter.Inc(1)\n\t}\n\n\tswitch t.req.Protocol {\n\tcase \"tcp\":\n\t\tm.tcpTunnelMeter.Mark(1)\n\tcase \"http\":\n\t\tm.httpTunnelMeter.Mark(1)\n\t}\n}\n\nfunc (m *LocalMetrics) CloseTunnel(t *Tunnel) {\n}\n\nfunc (m *LocalMetrics) OpenConnection(t *Tunnel, c conn.Conn) {\n\tm.connMeter.Mark(1)\n}\n\nfunc (m *LocalMetrics) CloseConnection(t *Tunnel, c conn.Conn, start time.Time, bytesIn, bytesOut int64) {\n\tm.bytesInCount.Inc(bytesIn)\n\tm.bytesOutCount.Inc(bytesOut)\n}\n\nfunc (m *LocalMetrics) Report() {\n\tm.Info(\"Reporting every %d seconds\", int(m.reportInterval.Seconds()))\n\n\tfor {\n\t\ttime.Sleep(m.reportInterval)\n\t\tbuffer, err := json.Marshal(map[string]interface{}{\n\t\t\t\"windows\":               m.windowsCounter.Count(),\n\t\t\t\"linux\":                 m.linuxCounter.Count(),\n\t\t\t\"osx\":                   m.osxCounter.Count(),\n\t\t\t\"other\":                 m.otherCounter.Count(),\n\t\t\t\"httpTunnelMeter.count\": m.httpTunnelMeter.Count(),\n\t\t\t\"tcpTunnelMeter.count\":  m.tcpTunnelMeter.Count(),\n\t\t\t\"tunnelMeter.count\":     m.tunnelMeter.Count(),\n\t\t\t\"tunnelMeter.m1\":        m.tunnelMeter.Rate1(),\n\t\t\t\"connMeter.count\":       m.connMeter.Count(),\n\t\t\t\"connMeter.m1\":          m.connMeter.Rate1(),\n\t\t\t\"bytesIn.count\":         m.bytesInCount.Count(),\n\t\t\t\"bytesOut.count\":        m.bytesOutCount.Count(),\n\t\t})\n\n\t\tif err != nil {\n\t\t\tm.Error(\"Failed to serialize metrics: %v\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tm.Info(\"Reporting: %s\", buffer)\n\t}\n}\n\ntype KeenIoMetric struct {\n\tCollection string\n\tEvent      interface{}\n}\n\ntype KeenIoMetrics struct {\n\tlog.Logger\n\tApiKey       string\n\tProjectToken string\n\tHttpClient   http.Client\n\tMetrics      chan *KeenIoMetric\n}\n\nfunc NewKeenIoMetrics(batchInterval time.Duration) *KeenIoMetrics {\n\tk := &KeenIoMetrics{\n\t\tLogger:       log.NewPrefixLogger(\"metrics\"),\n\t\tApiKey:       os.Getenv(\"KEEN_API_KEY\"),\n\t\tProjectToken: os.Getenv(\"KEEN_PROJECT_TOKEN\"),\n\t\tMetrics:      make(chan *KeenIoMetric, 1000),\n\t}\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tk.Error(\"KeenIoMetrics failed: %v\", r)\n\t\t\t}\n\t\t}()\n\n\t\tbatch := make(map[string][]interface{})\n\t\tbatchTimer := time.Tick(batchInterval)\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase m := <-k.Metrics:\n\t\t\t\tlist, ok := batch[m.Collection]\n\t\t\t\tif !ok {\n\t\t\t\t\tlist = make([]interface{}, 0)\n\t\t\t\t}\n\t\t\t\tbatch[m.Collection] = append(list, m.Event)\n\n\t\t\tcase <-batchTimer:\n\t\t\t\t// no metrics to report\n\t\t\t\tif len(batch) == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tpayload, err := json.Marshal(batch)\n\t\t\t\tif err != nil {\n\t\t\t\t\tk.Error(\"Failed to serialize metrics payload: %v, %v\", batch, err)\n\t\t\t\t} else {\n\t\t\t\t\tfor key, val := range batch {\n\t\t\t\t\t\tk.Debug(\"Reporting %d metrics for %s\", len(val), key)\n\t\t\t\t\t}\n\n\t\t\t\t\tk.AuthedRequest(\"POST\", \"/events\", bytes.NewReader(payload))\n\t\t\t\t}\n\t\t\t\tbatch = make(map[string][]interface{})\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn k\n}\n\nfunc (k *KeenIoMetrics) AuthedRequest(method, path string, body *bytes.Reader) (resp *http.Response, err error) {\n\tpath = fmt.Sprintf(\"https://api.keen.io/3.0/projects/%s%s\", k.ProjectToken, path)\n\treq, err := http.NewRequest(method, path, body)\n\tif err != nil {\n\t\treturn\n\t}\n\n\treq.Header.Add(\"Authorization\", k.ApiKey)\n\n\tif body != nil {\n\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\treq.ContentLength = int64(body.Len())\n\t}\n\n\trequestStartAt := time.Now()\n\tresp, err = k.HttpClient.Do(req)\n\n\tif err != nil {\n\t\tk.Error(\"Failed to send metric event to keen.io %v\", err)\n\t} else {\n\t\tk.Info(\"keen.io processed request in %f sec\", time.Since(requestStartAt).Seconds())\n\t\tdefer resp.Body.Close()\n\t\tif resp.StatusCode != 200 {\n\t\t\tbytes, _ := ioutil.ReadAll(resp.Body)\n\t\t\tk.Error(\"Got %v response from keen.io: %s\", resp.StatusCode, bytes)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (k *KeenIoMetrics) OpenConnection(t *Tunnel, c conn.Conn) {\n}\n\nfunc (k *KeenIoMetrics) CloseConnection(t *Tunnel, c conn.Conn, start time.Time, in, out int64) {\n\tevent := struct {\n\t\tKeen               KeenStruct `json:\"keen\"`\n\t\tOS                 string\n\t\tClientId           string\n\t\tProtocol           string\n\t\tUrl                string\n\t\tUser               string\n\t\tVersion            string\n\t\tReason             string\n\t\tHttpAuth           bool\n\t\tSubdomain          bool\n\t\tTunnelDuration     float64\n\t\tConnectionDuration float64\n\t\tBytesIn            int64\n\t\tBytesOut           int64\n\t}{\n\t\tKeen: KeenStruct{\n\t\t\tTimestamp: start.UTC().Format(\"2006-01-02T15:04:05.000Z\"),\n\t\t},\n\t\tOS:                 t.ctl.auth.OS,\n\t\tClientId:           t.ctl.id,\n\t\tProtocol:           t.req.Protocol,\n\t\tUrl:                t.url,\n\t\tUser:               t.ctl.auth.User,\n\t\tVersion:            t.ctl.auth.MmVersion,\n\t\tHttpAuth:           t.req.HttpAuth != \"\",\n\t\tSubdomain:          t.req.Subdomain != \"\",\n\t\tTunnelDuration:     time.Since(t.start).Seconds(),\n\t\tConnectionDuration: time.Since(start).Seconds(),\n\t\tBytesIn:            in,\n\t\tBytesOut:           out,\n\t}\n\n\tk.Metrics <- &KeenIoMetric{Collection: \"CloseConnection\", Event: event}\n}\n\nfunc (k *KeenIoMetrics) OpenTunnel(t *Tunnel) {\n}\n\ntype KeenStruct struct {\n\tTimestamp string `json:\"timestamp\"`\n}\n\nfunc (k *KeenIoMetrics) CloseTunnel(t *Tunnel) {\n\tevent := struct {\n\t\tKeen      KeenStruct `json:\"keen\"`\n\t\tOS        string\n\t\tClientId  string\n\t\tProtocol  string\n\t\tUrl       string\n\t\tUser      string\n\t\tVersion   string\n\t\tReason    string\n\t\tDuration  float64\n\t\tHttpAuth  bool\n\t\tSubdomain bool\n\t}{\n\t\tKeen: KeenStruct{\n\t\t\tTimestamp: t.start.UTC().Format(\"2006-01-02T15:04:05.000Z\"),\n\t\t},\n\t\tOS:       t.ctl.auth.OS,\n\t\tClientId: t.ctl.id,\n\t\tProtocol: t.req.Protocol,\n\t\tUrl:      t.url,\n\t\tUser:     t.ctl.auth.User,\n\t\tVersion:  t.ctl.auth.MmVersion,\n\t\t//Reason: reason,\n\t\tDuration:  time.Since(t.start).Seconds(),\n\t\tHttpAuth:  t.req.HttpAuth != \"\",\n\t\tSubdomain: t.req.Subdomain != \"\",\n\t}\n\n\tk.Metrics <- &KeenIoMetric{Collection: \"CloseTunnel\", Event: event}\n}\n"
  },
  {
    "path": "src/ngrok/server/registry.go",
    "content": "package server\n\nimport (\n\t\"encoding/gob\"\n\t\"fmt\"\n\t\"net\"\n\t\"ngrok/cache\"\n\t\"ngrok/log\"\n\t\"sync\"\n\t\"time\"\n)\n\nconst (\n\tcacheSaveInterval time.Duration = 10 * time.Minute\n)\n\ntype cacheUrl string\n\nfunc (url cacheUrl) Size() int {\n\treturn len(url)\n}\n\n// TunnelRegistry maps a tunnel URL to Tunnel structures\ntype TunnelRegistry struct {\n\ttunnels  map[string]*Tunnel\n\taffinity *cache.LRUCache\n\tlog.Logger\n\tsync.RWMutex\n}\n\nfunc NewTunnelRegistry(cacheSize uint64, cacheFile string) *TunnelRegistry {\n\tregistry := &TunnelRegistry{\n\t\ttunnels:  make(map[string]*Tunnel),\n\t\taffinity: cache.NewLRUCache(cacheSize),\n\t\tLogger:   log.NewPrefixLogger(\"registry\", \"tun\"),\n\t}\n\n\t// LRUCache uses Gob encoding. Unfortunately, Gob is fickle and will fail\n\t// to encode or decode any non-primitive types that haven't been \"registered\"\n\t// with it. Since we store cacheUrl objects, we need to register them here first\n\t// for the encoding/decoding to work\n\tvar urlobj cacheUrl\n\tgob.Register(urlobj)\n\n\t// try to load and then periodically save the affinity cache to file, if specified\n\tif cacheFile != \"\" {\n\t\terr := registry.affinity.LoadItemsFromFile(cacheFile)\n\t\tif err != nil {\n\t\t\tregistry.Error(\"Failed to load affinity cache %s: %v\", cacheFile, err)\n\t\t}\n\n\t\tregistry.SaveCacheThread(cacheFile, cacheSaveInterval)\n\t} else {\n\t\tregistry.Info(\"No affinity cache specified\")\n\t}\n\n\treturn registry\n}\n\n// Spawns a goroutine the periodically saves the cache to a file.\nfunc (r *TunnelRegistry) SaveCacheThread(path string, interval time.Duration) {\n\tgo func() {\n\t\tr.Info(\"Saving affinity cache to %s every %s\", path, interval.String())\n\t\tfor {\n\t\t\ttime.Sleep(interval)\n\n\t\t\tr.Debug(\"Saving affinity cache\")\n\t\t\terr := r.affinity.SaveItemsToFile(path)\n\t\t\tif err != nil {\n\t\t\t\tr.Error(\"Failed to save affinity cache: %v\", err)\n\t\t\t} else {\n\t\t\t\tr.Info(\"Saved affinity cache\")\n\t\t\t}\n\t\t}\n\t}()\n}\n\n// Register a tunnel with a specific url, returns an error\n// if a tunnel is already registered at that url\nfunc (r *TunnelRegistry) Register(url string, t *Tunnel) error {\n\tr.Lock()\n\tdefer r.Unlock()\n\n\tif r.tunnels[url] != nil {\n\t\treturn fmt.Errorf(\"The tunnel %s is already registered.\", url)\n\t}\n\n\tr.tunnels[url] = t\n\n\treturn nil\n}\n\nfunc (r *TunnelRegistry) cacheKeys(t *Tunnel) (ip string, id string) {\n\tclientIp := t.ctl.conn.RemoteAddr().(*net.TCPAddr).IP.String()\n\tclientId := t.ctl.id\n\n\tipKey := fmt.Sprintf(\"client-ip-%s:%s\", t.req.Protocol, clientIp)\n\tidKey := fmt.Sprintf(\"client-id-%s:%s\", t.req.Protocol, clientId)\n\treturn ipKey, idKey\n}\n\nfunc (r *TunnelRegistry) GetCachedRegistration(t *Tunnel) (url string) {\n\tipCacheKey, idCacheKey := r.cacheKeys(t)\n\n\t// check cache for ID first, because we prefer that over IP which might\n\t// not be specific to a user because of NATs\n\tif v, ok := r.affinity.Get(idCacheKey); ok {\n\t\turl = string(v.(cacheUrl))\n\t\tt.Debug(\"Found registry affinity %s for %s\", url, idCacheKey)\n\t} else if v, ok := r.affinity.Get(ipCacheKey); ok {\n\t\turl = string(v.(cacheUrl))\n\t\tt.Debug(\"Found registry affinity %s for %s\", url, ipCacheKey)\n\t}\n\treturn\n}\n\nfunc (r *TunnelRegistry) RegisterAndCache(url string, t *Tunnel) (err error) {\n\tif err = r.Register(url, t); err == nil {\n\t\t// we successfully assigned a url, cache it\n\t\tipCacheKey, idCacheKey := r.cacheKeys(t)\n\t\tr.affinity.Set(ipCacheKey, cacheUrl(url))\n\t\tr.affinity.Set(idCacheKey, cacheUrl(url))\n\t}\n\treturn\n\n}\n\n// Register a tunnel with the following process:\n// Consult the affinity cache to try to assign a previously used tunnel url if possible\n// Generate new urls repeatedly with the urlFn and register until one is available.\nfunc (r *TunnelRegistry) RegisterRepeat(urlFn func() string, t *Tunnel) (string, error) {\n\turl := r.GetCachedRegistration(t)\n\tif url == \"\" {\n\t\turl = urlFn()\n\t}\n\n\tmaxAttempts := 5\n\tfor i := 0; i < maxAttempts; i++ {\n\t\tif err := r.RegisterAndCache(url, t); err != nil {\n\t\t\t// pick a new url and try again\n\t\t\turl = urlFn()\n\t\t} else {\n\t\t\t// we successfully assigned a url, we're done\n\t\t\treturn url, nil\n\t\t}\n\t}\n\n\treturn \"\", fmt.Errorf(\"Failed to assign a URL after %d attempts!\", maxAttempts)\n}\n\nfunc (r *TunnelRegistry) Del(url string) {\n\tr.Lock()\n\tdefer r.Unlock()\n\tdelete(r.tunnels, url)\n}\n\nfunc (r *TunnelRegistry) Get(url string) *Tunnel {\n\tr.RLock()\n\tdefer r.RUnlock()\n\treturn r.tunnels[url]\n}\n\n// ControlRegistry maps a client ID to Control structures\ntype ControlRegistry struct {\n\tcontrols map[string]*Control\n\tlog.Logger\n\tsync.RWMutex\n}\n\nfunc NewControlRegistry() *ControlRegistry {\n\treturn &ControlRegistry{\n\t\tcontrols: make(map[string]*Control),\n\t\tLogger:   log.NewPrefixLogger(\"registry\", \"ctl\"),\n\t}\n}\n\nfunc (r *ControlRegistry) Get(clientId string) *Control {\n\tr.RLock()\n\tdefer r.RUnlock()\n\treturn r.controls[clientId]\n}\n\nfunc (r *ControlRegistry) Add(clientId string, ctl *Control) (oldCtl *Control) {\n\tr.Lock()\n\tdefer r.Unlock()\n\n\toldCtl = r.controls[clientId]\n\tif oldCtl != nil {\n\t\toldCtl.Replaced(ctl)\n\t}\n\n\tr.controls[clientId] = ctl\n\tr.Info(\"Registered control with id %s\", clientId)\n\treturn\n}\n\nfunc (r *ControlRegistry) Del(clientId string) error {\n\tr.Lock()\n\tdefer r.Unlock()\n\tif r.controls[clientId] == nil {\n\t\treturn fmt.Errorf(\"No control found for client id: %s\", clientId)\n\t} else {\n\t\tr.Info(\"Removed control registry id %s\", clientId)\n\t\tdelete(r.controls, clientId)\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "src/ngrok/server/tls.go",
    "content": "package server\n\nimport (\n\t\"crypto/tls\"\n\t\"io/ioutil\"\n\t\"ngrok/server/assets\"\n)\n\nfunc LoadTLSConfig(crtPath string, keyPath string) (tlsConfig *tls.Config, err error) {\n\tfileOrAsset := func(path string, default_path string) ([]byte, error) {\n\t\tloadFn := ioutil.ReadFile\n\t\tif path == \"\" {\n\t\t\tloadFn = assets.Asset\n\t\t\tpath = default_path\n\t\t}\n\n\t\treturn loadFn(path)\n\t}\n\n\tvar (\n\t\tcrt  []byte\n\t\tkey  []byte\n\t\tcert tls.Certificate\n\t)\n\n\tif crt, err = fileOrAsset(crtPath, \"assets/server/tls/snakeoil.crt\"); err != nil {\n\t\treturn\n\t}\n\n\tif key, err = fileOrAsset(keyPath, \"assets/server/tls/snakeoil.key\"); err != nil {\n\t\treturn\n\t}\n\n\tif cert, err = tls.X509KeyPair(crt, key); err != nil {\n\t\treturn\n\t}\n\n\ttlsConfig = &tls.Config{\n\t\tCertificates: []tls.Certificate{cert},\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "src/ngrok/server/tunnel.go",
    "content": "package server\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net\"\n\t\"ngrok/conn\"\n\t\"ngrok/log\"\n\t\"ngrok/msg\"\n\t\"ngrok/util\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\nvar defaultPortMap = map[string]int{\n\t\"http\":  80,\n\t\"https\": 443,\n\t\"smtp\":  25,\n}\n\n/**\n * Tunnel: A control connection, metadata and proxy connections which\n *         route public traffic to a firewalled endpoint.\n */\ntype Tunnel struct {\n\t// request that opened the tunnel\n\treq *msg.ReqTunnel\n\n\t// time when the tunnel was opened\n\tstart time.Time\n\n\t// public url\n\turl string\n\n\t// tcp listener\n\tlistener *net.TCPListener\n\n\t// control connection\n\tctl *Control\n\n\t// logger\n\tlog.Logger\n\n\t// closing\n\tclosing int32\n}\n\n// Common functionality for registering virtually hosted protocols\nfunc registerVhost(t *Tunnel, protocol string, servingPort int) (err error) {\n\tvhost := os.Getenv(\"VHOST\")\n\tif vhost == \"\" {\n\t\tvhost = fmt.Sprintf(\"%s:%d\", opts.domain, servingPort)\n\t}\n\n\t// Canonicalize virtual host by removing default port (e.g. :80 on HTTP)\n\tdefaultPort, ok := defaultPortMap[protocol]\n\tif !ok {\n\t\treturn fmt.Errorf(\"Couldn't find default port for protocol %s\", protocol)\n\t}\n\n\tdefaultPortSuffix := fmt.Sprintf(\":%d\", defaultPort)\n\tif strings.HasSuffix(vhost, defaultPortSuffix) {\n\t\tvhost = vhost[0 : len(vhost)-len(defaultPortSuffix)]\n\t}\n\n\t// Canonicalize by always using lower-case\n\tvhost = strings.ToLower(vhost)\n\n\t// Register for specific hostname\n\thostname := strings.ToLower(strings.TrimSpace(t.req.Hostname))\n\tif hostname != \"\" {\n\t\tt.url = fmt.Sprintf(\"%s://%s\", protocol, hostname)\n\t\treturn tunnelRegistry.Register(t.url, t)\n\t}\n\n\t// Register for specific subdomain\n\tsubdomain := strings.ToLower(strings.TrimSpace(t.req.Subdomain))\n\tif subdomain != \"\" {\n\t\tt.url = fmt.Sprintf(\"%s://%s.%s\", protocol, subdomain, vhost)\n\t\treturn tunnelRegistry.Register(t.url, t)\n\t}\n\n\t// Register for random URL\n\tt.url, err = tunnelRegistry.RegisterRepeat(func() string {\n\t\treturn fmt.Sprintf(\"%s://%x.%s\", protocol, rand.Int31(), vhost)\n\t}, t)\n\n\treturn\n}\n\n// Create a new tunnel from a registration message received\n// on a control channel\nfunc NewTunnel(m *msg.ReqTunnel, ctl *Control) (t *Tunnel, err error) {\n\tt = &Tunnel{\n\t\treq:    m,\n\t\tstart:  time.Now(),\n\t\tctl:    ctl,\n\t\tLogger: log.NewPrefixLogger(),\n\t}\n\n\tproto := t.req.Protocol\n\tswitch proto {\n\tcase \"tcp\":\n\t\tbindTcp := func(port int) error {\n\t\t\tif t.listener, err = net.ListenTCP(\"tcp\", &net.TCPAddr{IP: net.ParseIP(\"0.0.0.0\"), Port: port}); err != nil {\n\t\t\t\terr = t.ctl.conn.Error(\"Error binding TCP listener: %v\", err)\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t// create the url\n\t\t\taddr := t.listener.Addr().(*net.TCPAddr)\n\t\t\tt.url = fmt.Sprintf(\"tcp://%s:%d\", opts.domain, addr.Port)\n\n\t\t\t// register it\n\t\t\tif err = tunnelRegistry.RegisterAndCache(t.url, t); err != nil {\n\t\t\t\t// This should never be possible because the OS will\n\t\t\t\t// only assign available ports to us.\n\t\t\t\tt.listener.Close()\n\t\t\t\terr = fmt.Errorf(\"TCP listener bound, but failed to register %s\", t.url)\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tgo t.listenTcp(t.listener)\n\t\t\treturn nil\n\t\t}\n\n\t\t// use the custom remote port you asked for\n\t\tif t.req.RemotePort != 0 {\n\t\t\tbindTcp(int(t.req.RemotePort))\n\t\t\treturn\n\t\t}\n\n\t\t// try to return to you the same port you had before\n\t\tcachedUrl := tunnelRegistry.GetCachedRegistration(t)\n\t\tif cachedUrl != \"\" {\n\t\t\tvar port int\n\t\t\tparts := strings.Split(cachedUrl, \":\")\n\t\t\tportPart := parts[len(parts)-1]\n\t\t\tport, err = strconv.Atoi(portPart)\n\t\t\tif err != nil {\n\t\t\t\tt.ctl.conn.Error(\"Failed to parse cached url port as integer: %s\", portPart)\n\t\t\t} else {\n\t\t\t\t// we have a valid, cached port, let's try to bind with it\n\t\t\t\tif bindTcp(port) != nil {\n\t\t\t\t\tt.ctl.conn.Warn(\"Failed to get custom port %d: %v, trying a random one\", port, err)\n\t\t\t\t} else {\n\t\t\t\t\t// success, we're done\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Bind for TCP connections\n\t\tbindTcp(0)\n\t\treturn\n\n\tcase \"http\", \"https\":\n\t\tl, ok := listeners[proto]\n\t\tif !ok {\n\t\t\terr = fmt.Errorf(\"Not listening for %s connections\", proto)\n\t\t\treturn\n\t\t}\n\n\t\tif err = registerVhost(t, proto, l.Addr.(*net.TCPAddr).Port); err != nil {\n\t\t\treturn\n\t\t}\n\n\tdefault:\n\t\terr = fmt.Errorf(\"Protocol %s is not supported\", proto)\n\t\treturn\n\t}\n\n\t// pre-encode the http basic auth for fast comparisons later\n\tif m.HttpAuth != \"\" {\n\t\tm.HttpAuth = \"Basic \" + base64.StdEncoding.EncodeToString([]byte(m.HttpAuth))\n\t}\n\n\tt.AddLogPrefix(t.Id())\n\tt.Info(\"Registered new tunnel on: %s\", t.ctl.conn.Id())\n\n\tmetrics.OpenTunnel(t)\n\treturn\n}\n\nfunc (t *Tunnel) Shutdown() {\n\tt.Info(\"Shutting down\")\n\n\t// mark that we're shutting down\n\tatomic.StoreInt32(&t.closing, 1)\n\n\t// if we have a public listener (this is a raw TCP tunnel), shut it down\n\tif t.listener != nil {\n\t\tt.listener.Close()\n\t}\n\n\t// remove ourselves from the tunnel registry\n\ttunnelRegistry.Del(t.url)\n\n\t// let the control connection know we're shutting down\n\t// currently, only the control connection shuts down tunnels,\n\t// so it doesn't need to know about it\n\t// t.ctl.stoptunnel <- t\n\n\tmetrics.CloseTunnel(t)\n}\n\nfunc (t *Tunnel) Id() string {\n\treturn t.url\n}\n\n// Listens for new public tcp connections from the internet.\nfunc (t *Tunnel) listenTcp(listener *net.TCPListener) {\n\tfor {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tlog.Warn(\"listenTcp failed with error %v\", r)\n\t\t\t}\n\t\t}()\n\n\t\t// accept public connections\n\t\ttcpConn, err := listener.AcceptTCP()\n\n\t\tif err != nil {\n\t\t\t// not an error, we're shutting down this tunnel\n\t\t\tif atomic.LoadInt32(&t.closing) == 1 {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tt.Error(\"Failed to accept new TCP connection: %v\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tconn := conn.Wrap(tcpConn, \"pub\")\n\t\tconn.AddLogPrefix(t.Id())\n\t\tconn.Info(\"New connection from %v\", conn.RemoteAddr())\n\n\t\tgo t.HandlePublicConnection(conn)\n\t}\n}\n\nfunc (t *Tunnel) HandlePublicConnection(publicConn conn.Conn) {\n\tdefer publicConn.Close()\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tpublicConn.Warn(\"HandlePublicConnection failed with error %v\", r)\n\t\t}\n\t}()\n\n\tstartTime := time.Now()\n\tmetrics.OpenConnection(t, publicConn)\n\n\tvar proxyConn conn.Conn\n\tvar err error\n\tfor i := 0; i < (2 * proxyMaxPoolSize); i++ {\n\t\t// get a proxy connection\n\t\tif proxyConn, err = t.ctl.GetProxy(); err != nil {\n\t\t\tt.Warn(\"Failed to get proxy connection: %v\", err)\n\t\t\treturn\n\t\t}\n\t\tdefer proxyConn.Close()\n\t\tt.Info(\"Got proxy connection %s\", proxyConn.Id())\n\t\tproxyConn.AddLogPrefix(t.Id())\n\n\t\t// tell the client we're going to start using this proxy connection\n\t\tstartPxyMsg := &msg.StartProxy{\n\t\t\tUrl:        t.url,\n\t\t\tClientAddr: publicConn.RemoteAddr().String(),\n\t\t}\n\n\t\tif err = msg.WriteMsg(proxyConn, startPxyMsg); err != nil {\n\t\t\tproxyConn.Warn(\"Failed to write StartProxyMessage: %v, attempt %d\", err, i)\n\t\t\tproxyConn.Close()\n\t\t} else {\n\t\t\t// success\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif err != nil {\n\t\t// give up\n\t\tpublicConn.Error(\"Too many failures starting proxy connection\")\n\t\treturn\n\t}\n\n\t// To reduce latency handling tunnel connections, we employ the following curde heuristic:\n\t// Whenever we take a proxy connection from the pool, replace it with a new one\n\tutil.PanicToError(func() { t.ctl.out <- &msg.ReqProxy{} })\n\n\t// no timeouts while connections are joined\n\tproxyConn.SetDeadline(time.Time{})\n\n\t// join the public and proxy connections\n\tbytesIn, bytesOut := conn.Join(publicConn, proxyConn)\n\tmetrics.CloseConnection(t, publicConn, startTime, bytesIn, bytesOut)\n}\n"
  },
  {
    "path": "src/ngrok/util/broadcast.go",
    "content": "package util\n\ntype Broadcast struct {\n\tlisteners []chan interface{}\n\treg       chan (chan interface{})\n\tunreg     chan (chan interface{})\n\tin        chan interface{}\n}\n\nfunc NewBroadcast() *Broadcast {\n\tb := &Broadcast{\n\t\tlisteners: make([]chan interface{}, 0),\n\t\treg:       make(chan (chan interface{})),\n\t\tunreg:     make(chan (chan interface{})),\n\t\tin:        make(chan interface{}),\n\t}\n\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase l := <-b.unreg:\n\t\t\t\t// remove L from b.listeners\n\t\t\t\t// this operation is slow: O(n) but not used frequently\n\t\t\t\t// unlike iterating over listeners\n\t\t\t\toldListeners := b.listeners\n\t\t\t\tb.listeners = make([]chan interface{}, 0, len(oldListeners))\n\t\t\t\tfor _, oldL := range oldListeners {\n\t\t\t\t\tif l != oldL {\n\t\t\t\t\t\tb.listeners = append(b.listeners, oldL)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase l := <-b.reg:\n\t\t\t\tb.listeners = append(b.listeners, l)\n\n\t\t\tcase item := <-b.in:\n\t\t\t\tfor _, l := range b.listeners {\n\t\t\t\t\tl <- item\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn b\n}\n\nfunc (b *Broadcast) In() chan interface{} {\n\treturn b.in\n}\n\nfunc (b *Broadcast) Reg() chan interface{} {\n\tlistener := make(chan interface{})\n\tb.reg <- listener\n\treturn listener\n}\n\nfunc (b *Broadcast) UnReg(listener chan interface{}) {\n\tb.unreg <- listener\n}\n"
  },
  {
    "path": "src/ngrok/util/errors.go",
    "content": "package util\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n)\n\nconst crashMessage = `panic: %v\n\n%s\n\nOh noes! ngrok crashed!\n\nPlease submit the stack trace and any relevant information to:\ngithub.com/inconshreveable/ngrok/issues`\n\nfunc MakePanicTrace(err interface{}) string {\n\tstackBuf := make([]byte, 4096)\n\tn := runtime.Stack(stackBuf, false)\n\treturn fmt.Sprintf(crashMessage, err, stackBuf[:n])\n}\n\n// Runs the given function and converts any panic encountered while doing so\n// into an error. Useful for sending to channels that will close\nfunc PanicToError(fn func()) (err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = fmt.Errorf(\"Panic: %v\", r)\n\t\t}\n\t}()\n\tfn()\n\treturn\n}\n"
  },
  {
    "path": "src/ngrok/util/id.go",
    "content": "package util\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\tmrand \"math/rand\"\n)\n\nfunc RandomSeed() (seed int64, err error) {\n\terr = binary.Read(rand.Reader, binary.LittleEndian, &seed)\n\treturn\n}\n\n// creates a random identifier of the specified length\nfunc RandId(idlen int) string {\n\tb := make([]byte, idlen)\n\tvar randVal uint32\n\tfor i := 0; i < idlen; i++ {\n\t\tbyteIdx := i % 4\n\t\tif byteIdx == 0 {\n\t\t\trandVal = mrand.Uint32()\n\t\t}\n\t\tb[i] = byte((randVal >> (8 * uint(byteIdx))) & 0xFF)\n\t}\n\treturn fmt.Sprintf(\"%x\", b)\n}\n\n// like RandId, but uses a crypto/rand for secure random identifiers\nfunc SecureRandId(idlen int) (id string, err error) {\n\tb := make([]byte, idlen)\n\tn, err := rand.Read(b)\n\n\tif n != idlen {\n\t\terr = fmt.Errorf(\"Only generated %d random bytes, %d requested\", n, idlen)\n\t\treturn\n\t}\n\n\tif err != nil {\n\t\treturn\n\t}\n\n\tid = fmt.Sprintf(\"%x\", b)\n\treturn\n}\n\nfunc SecureRandIdOrPanic(idlen int) string {\n\tid, err := SecureRandId(idlen)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n"
  },
  {
    "path": "src/ngrok/util/ring.go",
    "content": "package util\n\nimport (\n\t\"container/list\"\n\t\"sync\"\n)\n\ntype Ring struct {\n\tsync.Mutex\n\t*list.List\n\tcapacity int\n}\n\nfunc NewRing(capacity int) *Ring {\n\treturn &Ring{capacity: capacity, List: list.New()}\n}\n\nfunc (r *Ring) Add(item interface{}) interface{} {\n\tr.Lock()\n\tdefer r.Unlock()\n\n\t// add new item\n\tr.PushFront(item)\n\n\t// remove old item if at capacity\n\tvar old interface{}\n\tif r.Len() >= r.capacity {\n\t\told = r.Remove(r.Back())\n\t}\n\n\treturn old\n}\n\nfunc (r *Ring) Slice() []interface{} {\n\tr.Lock()\n\tdefer r.Unlock()\n\n\ti := 0\n\titems := make([]interface{}, r.Len())\n\tfor e := r.Front(); e != nil; e = e.Next() {\n\t\titems[i] = e.Value\n\t\ti++\n\t}\n\n\treturn items\n}\n"
  },
  {
    "path": "src/ngrok/util/shutdown.go",
    "content": "package util\n\nimport (\n\t\"sync\"\n)\n\n// A small utility class for managing controlled shutdowns\ntype Shutdown struct {\n\tsync.Mutex\n\tinProgress bool\n\tbegin      chan int // closed when the shutdown begins\n\tcomplete   chan int // closed when the shutdown completes\n}\n\nfunc NewShutdown() *Shutdown {\n\treturn &Shutdown{\n\t\tbegin:    make(chan int),\n\t\tcomplete: make(chan int),\n\t}\n}\n\nfunc (s *Shutdown) Begin() {\n\ts.Lock()\n\tdefer s.Unlock()\n\tif s.inProgress == true {\n\t\treturn\n\t} else {\n\t\ts.inProgress = true\n\t\tclose(s.begin)\n\t}\n}\n\nfunc (s *Shutdown) WaitBegin() {\n\t<-s.begin\n}\n\nfunc (s *Shutdown) Complete() {\n\tclose(s.complete)\n}\n\nfunc (s *Shutdown) WaitComplete() {\n\t<-s.complete\n}\n"
  },
  {
    "path": "src/ngrok/version/version.go",
    "content": "package version\n\nimport (\n\t\"fmt\"\n)\n\nconst (\n\tProto = \"2\"\n\tMajor = \"1\"\n\tMinor = \"7\"\n)\n\nfunc MajorMinor() string {\n\treturn fmt.Sprintf(\"%s.%s\", Major, Minor)\n}\n\nfunc Full() string {\n\treturn fmt.Sprintf(\"%s-%s.%s\", Proto, Major, Minor)\n}\n\nfunc Compat(client string, server string) bool {\n\treturn client == server\n}\n"
  }
]