Full Code of inconshreveable/ngrok for AI

master 61d48289f3bf cached
66 files
271.5 KB
88.5k tokens
489 symbols
1 requests
Download .txt
Showing preview only (289K chars total). Download the full file or copy to clipboard to get everything.
Repository: inconshreveable/ngrok
Branch: master
Commit: 61d48289f3bf
Files: 66
Total size: 271.5 KB

Directory structure:
gitextract_7xnxnl94/

├── .gitignore
├── .travis.yml
├── CONTRIBUTORS
├── LICENSE
├── Makefile
├── README.md
├── assets/
│   ├── client/
│   │   ├── page.html
│   │   ├── static/
│   │   │   └── js/
│   │   │       ├── angular.js
│   │   │       ├── base64.js
│   │   │       ├── jquery.timeago.js
│   │   │       ├── ngrok.js
│   │   │       └── vkbeautify.js
│   │   └── tls/
│   │       ├── ngrokroot.crt
│   │       └── snakeoilca.crt
│   └── server/
│       └── tls/
│           ├── snakeoil.crt
│           └── snakeoil.key
├── contrib/
│   └── com.ngrok.client.plist
├── docs/
│   ├── CHANGELOG.md
│   ├── DEVELOPMENT.md
│   └── SELFHOSTING.md
└── src/
    └── ngrok/
        ├── cache/
        │   └── lru.go
        ├── client/
        │   ├── cli.go
        │   ├── config.go
        │   ├── controller.go
        │   ├── debug.go
        │   ├── main.go
        │   ├── metrics.go
        │   ├── model.go
        │   ├── mvc/
        │   │   ├── controller.go
        │   │   ├── model.go
        │   │   ├── state.go
        │   │   └── view.go
        │   ├── release.go
        │   ├── tls.go
        │   ├── update_debug.go
        │   ├── update_release.go
        │   └── views/
        │       ├── term/
        │       │   ├── area.go
        │       │   ├── http.go
        │       │   └── view.go
        │       └── web/
        │           ├── http.go
        │           └── view.go
        ├── conn/
        │   ├── conn.go
        │   └── tee.go
        ├── log/
        │   └── logger.go
        ├── main/
        │   ├── ngrok/
        │   │   └── ngrok.go
        │   └── ngrokd/
        │       └── ngrokd.go
        ├── msg/
        │   ├── conn.go
        │   ├── msg.go
        │   └── pack.go
        ├── proto/
        │   ├── http.go
        │   ├── interface.go
        │   └── tcp.go
        ├── server/
        │   ├── cli.go
        │   ├── control.go
        │   ├── http.go
        │   ├── main.go
        │   ├── metrics.go
        │   ├── registry.go
        │   ├── tls.go
        │   └── tunnel.go
        ├── util/
        │   ├── broadcast.go
        │   ├── errors.go
        │   ├── id.go
        │   ├── ring.go
        │   └── shutdown.go
        └── version/
            └── version.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
*.swp
bin/
pkg/
src/code.google.com
src/github.com
src/bitbucket.org
src/launchpad.net
src/gopkg.in
src/ngrok/client/assets/
src/ngrok/server/assets/


================================================
FILE: .travis.yml
================================================
sudo: false
language: go
script: make release-all
install: true
go: 
    - 1.4
    - 1.5
    - 1.6
    - tip

matrix:
    allow_failures:
        - go: tip


================================================
FILE: CONTRIBUTORS
================================================
Contributors to ngrok, both large and small:

- Alan Shreve 
- Brandon Philips 
- Caleb Spare 
- Jay Hayes 
- Kevin Burke 
- Kyle Conroy 
- Nick Presta 
- Stephen Huenneke 
- inconshreveable 
- jzs 


================================================
FILE: LICENSE
================================================
Copyright 2013 Alan Shreve

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.


================================================
FILE: Makefile
================================================
.PHONY: default server client deps fmt clean all release-all assets client-assets server-assets contributors
export GOPATH:=$(shell pwd)

BUILDTAGS=debug
default: all

deps: assets
	go get -tags '$(BUILDTAGS)' -d -v ngrok/...

server: deps
	go install -tags '$(BUILDTAGS)' ngrok/main/ngrokd

fmt:
	go fmt ngrok/...

client: deps
	go install -tags '$(BUILDTAGS)' ngrok/main/ngrok

assets: client-assets server-assets

bin/go-bindata:
	GOOS="" GOARCH="" go get github.com/jteeuwen/go-bindata/go-bindata

client-assets: bin/go-bindata
	bin/go-bindata -nomemcopy -pkg=assets -tags=$(BUILDTAGS) \
		-debug=$(if $(findstring debug,$(BUILDTAGS)),true,false) \
		-o=src/ngrok/client/assets/assets_$(BUILDTAGS).go \
		assets/client/...

server-assets: bin/go-bindata
	bin/go-bindata -nomemcopy -pkg=assets -tags=$(BUILDTAGS) \
		-debug=$(if $(findstring debug,$(BUILDTAGS)),true,false) \
		-o=src/ngrok/server/assets/assets_$(BUILDTAGS).go \
		assets/server/...

release-client: BUILDTAGS=release
release-client: client

release-server: BUILDTAGS=release
release-server: server

release-all: fmt release-client release-server

all: fmt client server

clean:
	go clean -i -r ngrok/...
	rm -rf src/ngrok/client/assets/ src/ngrok/server/assets/

contributors:
	echo "Contributors to ngrok, both large and small:\n" > CONTRIBUTORS
	git log --raw | grep "^Author: " | sort | uniq | cut -d ' ' -f2- | sed 's/^/- /' | cut -d '<' -f1 >> CONTRIBUTORS


================================================
FILE: README.md
================================================
# ngrok - Unified Ingress for Developers

[https://ngrok.com](https://ngrok.com)

## ngrok Community on GitHub

If 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)

## This repository is archived

This is the GitHub repository for the old v1 version of ngrok which was actively developed from 2013-2016.

**This repository is archived: ngrok v1 is no longer developed, supported or maintained.**

Thank 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.

ngrok's cloud service continues to operate and you can sign up for it here: [https://ngrok.com/signup](https://ngrok.com/signup)

## What is ngrok?

ngrok 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.

To use ngrok, sign up at [https://ngrok.com/signup](https://ngrok.com/signup)

## ngrok open-source development
ngrok continues to contribute to the open source ecosystem at [https://github.com/ngrok](https://github.com/ngrok) with:
- [The ngrok kubernetes operator](https://github.com/ngrok/kubernetes-ingress-controller)
- [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)


## What is ngrok for?

[What can you do with ngrok?](https://ngrok.com/docs/what-is-ngrok/#what-can-you-do-with-ngrok)

- Site-to-site Connectivity: Connect securely to APIs and databases in your customers' networks without complex network configuration.
- Developer Previews: Demoing an app from your local machine without deploying it
- Webhook Testing: Developing any services which consume webhooks (HTTP callbacks) by allowing you to replay those requests
- 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.
- Device Gateway: Run ngrok on your IoT devices to control device APIs from your cloud 
- Debug and understand any web service by inspecting the HTTP traffic to it


================================================
FILE: assets/client/page.html
================================================
<html>
    <head>
        <title>ngrok</title>
        <link href="/static/css/highlight.min.css" rel="stylesheet">
        <link href="/static/css/bootstrap.min.css" rel="stylesheet">
        <script src="/static/js/highlight.min.js"></script>
        <script src="/static/js/vkbeautify.js"></script>
        <script src="/static/js/jquery-1.9.1.min.js"></script>
        <script src="/static/js/jquery.timeago.js"></script>
        <script src="/static/js/angular.js"></script>
        <script src="/static/js/angular-sanitize.min.js"></script>
        <script src="/static/js/base64.js"></script>
        <script src="/static/js/ngrok.js"></script>
        <script type="text/javascript">
            window.data = JSON.parse({% . %});
        </script>
        <style type="text/css">
            body { margin-top: 50px; }
            table.params { font-size: 12px; font-family: Courier, monospace; }
            .txn-selector tr { cursor: pointer; }
            .txn-selector tr:hover { background-color: #ddd; }
            tr.selected, tr.selected:hover {
                background-color: #ff9999;
                background-color: #000000;
                color:white;
            }
            .path {
              width: 100%;
            }
            .wrapped {
              word-wrap: break-word;
              word-break: break-word;
              overflow: hidden;
            }
        </style>
    </head>

    <body ng-app="ngrok">
        <div class="container" ng-controller="HttpTxns">
            <div class="navbar navbar-inverse navbar-fixed-top">
                <div class="navbar-inner">
                    <div class="container">
                        <a class="brand" href="#">ngrok</a>
                        <ul class="nav">
                            <li class="active"><a href="#">Inbound Requests</a></li>
                            <!--
                            <li><a href="#">Outbound Requests</a></li>
                            <li><a href="#">Configuration</a></li>
                            -->
                        </ul>
                    </div>
                </div>
            </div>
            <div ng-show="txns.length==0" class="row">
                <div class="span6 offset3">
                    <div class="well" style="padding: 20px 50px;">
                        <h4>No requests to display yet</h4>
			<hr />
                        <h5>To get started, make a request to one of your tunnel URLs:</h5>
                            <ul>
                                <li ng-repeat="t in tunnels"><p class="lead"><a target="_blank" href="{{ t.PublicUrl }}">{{ t.PublicUrl }}</a></p></li>
                            </ul>
                        </p>
                    </div>
                </div>
            </div>
            <div ng-show="txns.length>0" class="row">
                <div class="span6">
                    <h4>All Requests</h4>
                    <table class="table txn-selector">
                        <tr ng-controller="TxnNavItem" ng-class="{'selected':isActive()}" ng-repeat="txn in txns" ng-click="makeActive()">
                            <td class="wrapped"><div class="path">{{ txn.Req.MethodPath }}</div></td>
                            <td>{{ txn.Resp.Status }}</td>
                            <td><span class="pull-right">{{ txn.Duration }}</span></td>
                        </tr>
                    </table>
                </div>
                <div class="span6" ng-controller="HttpTxn" ng-show="!!Txn">
                    <div class="row-fluid">
                        <div class="span4">
                            <span title="{{ISO8601(Txn.Start)}}" class="muted">
                                {{TimeFormat(Txn.Start)}}
                            </span>
                        </div>
                        <div class="span4">
                            <i class="icon-time"></i> Duration
                            <span style="margin-left: 8px;" class="muted">{{Txn.Duration}}</span>
                        </div>
                        <div  class="span4">
                            <i class="icon-user"></i> IP
                            <span style="margin-left: 8px;" class="muted">{{Txn.ConnCtx.ClientAddr.split(":")[0]}}</span>
                        </div>
                    </div>
                    <hr />
                    <div ng-show="!!Req" ng-controller="HttpRequest">
                        <h3 class="wrapped">{{ Req.MethodPath }}</h3>
                        <div onbtnclick="replay()" btn="Replay" tabs="Summary,Headers,Raw,Binary">
                        </div>

                        <div ng-show="isTab('Summary')">
                            <keyval title="Query Params" tuples="Req.Params"></keyval>
                            <div body="Req.Body" binary="Req.Binary"></div>
                        </div>

                        <div ng-show="isTab('Headers')">
                            <keyval title="Headers" tuples="Req.Header"></keyval>
                        </div>

                        <div ng-show="isTab('Raw')">
                            <pre><code class="http">{{ Req.RawText }}</code></pre>
                        </div>

                        <div ng-show="isTab('Binary')">
                            <pre><code>{{ Req.RawBytes }}</code></pre>
                        </div>

                    </div>

                    <hr style="margin: 40px 0 20px" />

                    <div ng-show="!!Resp" ng-controller="HttpResponse">
                        <h3 ng-class="Resp.statusClass">{{ Resp.Status }}</h3>

                        <div tabs="Summary,Headers,Raw,Binary"></div>
                        <div ng-show="isTab('Summary')">
                            <div body="Resp.Body" binary="Resp.Binary"></div>
                        </div>

                        <div ng-show="isTab('Headers')">
                            <keyval title="Headers" tuples="Resp.Header"></keyval>
                        </div>

                        <div ng-show="isTab('Raw')">
                            <pre><code class="http">{{ Resp.RawText }}</code></pre>
                        </div>

                        <div ng-show="isTab('Binary')">
                            <pre><code>{{ Resp.RawBytes }}</code></pre>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <!-- UserVoice JavaScript SDK (only needed once on a page) -->
        <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>

        <!-- A tab to launch the Classic Widget -->
        <script>
        UserVoice = window.UserVoice || [];
        UserVoice.push(['showTab', 'classic_widget', {
          mode: 'feedback',
          primary_color: '#cc6d00',
          link_color: '#007dbf',
          forum_id: 211925,
          tab_label: 'Feedback',
          tab_color: '#cc6d00',
          tab_position: 'middle-left',
          tab_inverted: false
        }]);
        </script>
    </body>
</html>


================================================
FILE: assets/client/static/js/angular.js
================================================
/*
 AngularJS v1.1.5
 (c) 2010-2012 Google, Inc. http://angularjs.org
 License: MIT
*/
(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=
0;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");
return 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==
"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);
for(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,{})));
return 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)===
"$"||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&&
a.$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||
"").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,
a){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]:[])}
function 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,
function(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,
d,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",
"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]=
c}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,
c,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,
" ").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=
b.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||
c.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=
[],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,
!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");
if(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]);
case 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+
f)||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=
g(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()},
function(){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())}))}
var 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"))):
b?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)+
"="+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=
["$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),
b},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=
{},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=
g.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])});
var 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)||
!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()),
"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",
j)&&(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;
if(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=
u(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=
-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(),
J.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,
va(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"]=
(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:{}};
ja(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 ["+
b.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,
a)})})})}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",
b),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}]}
function 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=
["$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,
m))),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("/"),
a=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("/")+
1)}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):
b+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=
b+(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)?
(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"),
j=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++,
c.$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=
c.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"||
a=="\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-
1)=="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);
h&&(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=[],
r,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,
text: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,
c,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()),
!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=
y(),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=
b.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;
h(":");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,
e){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,
e)[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=
(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=
f[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="+
(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=
["$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||
d)(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}}},
i=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)||
(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=
"^"+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",
j);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});
B(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+)(.*)/),
f=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;
this["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=
a.$$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,
g)}},$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},
function(){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=
n.$$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;
if(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];
c||(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=
this,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=
{},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=
e.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]===""},
c=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=
{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=
t({},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)&&
!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,
b,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,
h),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))},
responseError: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(":",""))}]}
function 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)}
var 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",
"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",
posSuf:"",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",
mediumDate:"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
g?(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;
return!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=
0;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,
d){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),
a.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=
b.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=
N(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,
"").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=
Za(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||
"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,
a)};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()})}}
function 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)};
var 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",
!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,
function(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=
{}),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=
M.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,
splice:[].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],
d===""&&(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;
b.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<
this.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)&
16)))}: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||
[]},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,
removeClass: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,
i=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",
"$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"])>
0)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&&
!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",
"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;
var 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},
"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,
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 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",
t:"\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",
2,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),
2);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,
g,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=
function(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}$/,
wd=/^\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),
d=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)?
(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)})});
e.$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,"-"):"";
e.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=
!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)}})};
var 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",
link: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)?
a.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",
c);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(" "),
function(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=
a;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(),
o.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]=
c(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 '"+
m+"'.");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=
C.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=
l.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,
function(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=
c.$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,
element: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=
l,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+(.*?))?$/,
e={$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=
function(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=
B(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]=
z[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",
q.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()}
var 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<
q;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(),
h=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",
d.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"),
db("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,
input: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);
a.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>');


================================================
FILE: assets/client/static/js/base64.js
================================================
/*
Copyright (c) 2008 Fred Palmer fred.palmer_at_gmail.com

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
function StringBuffer()
{ 
    this.buffer = []; 
} 

StringBuffer.prototype.append = function append(string)
{ 
    this.buffer.push(string); 
    return this; 
}; 

StringBuffer.prototype.toString = function toString()
{ 
    return this.buffer.join(""); 
}; 

window.Base64 =
{
    codex : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

    encode : function (input)
    {
        var output = new StringBuffer();

        var enumerator = new Utf8EncodeEnumerator(input);
        while (enumerator.moveNext())
        {
            var chr1 = enumerator.current;

            enumerator.moveNext();
            var chr2 = enumerator.current;

            enumerator.moveNext();
            var chr3 = enumerator.current;

            var enc1 = chr1 >> 2;
            var enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            var enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            var enc4 = chr3 & 63;

            if (isNaN(chr2))
            {
                enc3 = enc4 = 64;
            }
            else if (isNaN(chr3))
            {
                enc4 = 64;
            }

            output.append(this.codex.charAt(enc1) + this.codex.charAt(enc2) + this.codex.charAt(enc3) + this.codex.charAt(enc4));
        }

        return output.toString();
    },

    decode : function (input)
    {
        var output = new StringBuffer();
        var outputBytes = [];

        var enumerator = new Base64DecodeEnumerator(input);
        while (enumerator.moveNext())
        {
            var charCode = enumerator.current;
            outputBytes.push(charCode);

            if (charCode < 128)
                output.append(String.fromCharCode(charCode));
            else if ((charCode > 191) && (charCode < 224))
            {
                enumerator.moveNext();
                var charCode2 = enumerator.current;
                outputBytes.push(charCode2);

                output.append(String.fromCharCode(((charCode & 31) << 6) | (charCode2 & 63)));
            }
            else
            {
                enumerator.moveNext();
                var charCode2 = enumerator.current;
                outputBytes.push(charCode2);

                enumerator.moveNext();
                var charCode3 = enumerator.current;
                outputBytes.push(charCode3);

                output.append(String.fromCharCode(((charCode & 15) << 12) | ((charCode2 & 63) << 6) | (charCode3 & 63)));
            }
        }

        return {
            "bytes": outputBytes,
            "text": output.toString()
        };
    }
};

function Utf8EncodeEnumerator(input)
{
    this._input = input;
    this._index = -1;
    this._buffer = [];
}

Utf8EncodeEnumerator.prototype =
{
    current: Number.NaN,

    moveNext: function()
    {
        if (this._buffer.length > 0)
        {
            this.current = this._buffer.shift();
            return true;
        }
        else if (this._index >= (this._input.length - 1))
        {
            this.current = Number.NaN;
            return false;
        }
        else
        {
            var charCode = this._input.charCodeAt(++this._index);

            // "\r\n" -> "\n"
            //
            if ((charCode == 13) && (this._input.charCodeAt(this._index + 1) == 10))
            {
                charCode = 10;
                this._index += 2;
            }

            if (charCode < 128)
            {
                this.current = charCode;
            }
            else if ((charCode > 127) && (charCode < 2048))
            {
                this.current = (charCode >> 6) | 192;
                this._buffer.push((charCode & 63) | 128);
            }
            else
            {
                this.current = (charCode >> 12) | 224;
                this._buffer.push(((charCode >> 6) & 63) | 128);
                this._buffer.push((charCode & 63) | 128);
            }

            return true;
        }
    }
}

function Base64DecodeEnumerator(input)
{
    this._input = input;
    this._index = -1;
    this._buffer = [];
}

Base64DecodeEnumerator.prototype =
{
    current: 64,

    moveNext: function()
    {
        if (this._buffer.length > 0)
        {
            this.current = this._buffer.shift();
            return true;
        }
        else if (this._index >= (this._input.length - 1))
        {
            this.current = 64;
            return false;
        }
        else
        {
            var enc1 = Base64.codex.indexOf(this._input.charAt(++this._index));
            var enc2 = Base64.codex.indexOf(this._input.charAt(++this._index));
            var enc3 = Base64.codex.indexOf(this._input.charAt(++this._index));
            var enc4 = Base64.codex.indexOf(this._input.charAt(++this._index));

            var chr1 = (enc1 << 2) | (enc2 >> 4);
            var chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            var chr3 = ((enc3 & 3) << 6) | enc4;

            this.current = chr1;

            if (enc3 != 64)
                this._buffer.push(chr2);

            if (enc4 != 64)
                this._buffer.push(chr3);

            return true;
        }
    }
};


================================================
FILE: assets/client/static/js/jquery.timeago.js
================================================
/**
 * Timeago is a jQuery plugin that makes it easy to support automatically
 * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
 *
 * @name timeago
 * @version 1.3.0
 * @requires jQuery v1.2.3+
 * @author Ryan McGeary
 * @license MIT License - http://www.opensource.org/licenses/mit-license.php
 *
 * For usage and examples, visit:
 * http://timeago.yarp.com/
 *
 * Copyright (c) 2008-2013, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org)
 */

(function (factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define(['jquery'], factory);
  } else {
    // Browser globals
    factory(jQuery);
  }
}(function ($) {
  $.timeago = function(timestamp) {
    if (timestamp instanceof Date) {
      return inWords(timestamp);
    } else if (typeof timestamp === "string") {
      return inWords($.timeago.parse(timestamp));
    } else if (typeof timestamp === "number") {
      return inWords(new Date(timestamp));
    } else {
      return inWords($.timeago.datetime(timestamp));
    }
  };
  var $t = $.timeago;

  $.extend($.timeago, {
    settings: {
      refreshMillis: 60000,
      allowFuture: false,
      localeTitle: false,
      cutoff: 0,
      strings: {
        prefixAgo: null,
        prefixFromNow: null,
        suffixAgo: "ago",
        suffixFromNow: "from now",
        seconds: "less than a minute",
        minute: "about a minute",
        minutes: "%d minutes",
        hour: "about an hour",
        hours: "about %d hours",
        day: "a day",
        days: "%d days",
        month: "about a month",
        months: "%d months",
        year: "about a year",
        years: "%d years",
        wordSeparator: " ",
        numbers: []
      }
    },
    inWords: function(distanceMillis) {
      var $l = this.settings.strings;
      var prefix = $l.prefixAgo;
      var suffix = $l.suffixAgo;
      if (this.settings.allowFuture) {
        if (distanceMillis < 0) {
          prefix = $l.prefixFromNow;
          suffix = $l.suffixFromNow;
        }
      }

      var seconds = Math.abs(distanceMillis) / 1000;
      var minutes = seconds / 60;
      var hours = minutes / 60;
      var days = hours / 24;
      var years = days / 365;

      function substitute(stringOrFunction, number) {
        var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
        var value = ($l.numbers && $l.numbers[number]) || number;
        return string.replace(/%d/i, value);
      }

      var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
        seconds < 90 && substitute($l.minute, 1) ||
        minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
        minutes < 90 && substitute($l.hour, 1) ||
        hours < 24 && substitute($l.hours, Math.round(hours)) ||
        hours < 42 && substitute($l.day, 1) ||
        days < 30 && substitute($l.days, Math.round(days)) ||
        days < 45 && substitute($l.month, 1) ||
        days < 365 && substitute($l.months, Math.round(days / 30)) ||
        years < 1.5 && substitute($l.year, 1) ||
        substitute($l.years, Math.round(years));

      var separator = $l.wordSeparator || "";
      if ($l.wordSeparator === undefined) { separator = " "; }
      return $.trim([prefix, words, suffix].join(separator));
    },
    parse: function(iso8601) {
      var s = $.trim(iso8601);
      s = s.replace(/\.\d+/,""); // remove milliseconds
      s = s.replace(/-/,"/").replace(/-/,"/");
      s = s.replace(/T/," ").replace(/Z/," UTC");
      s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
      return new Date(s);
    },
    datetime: function(elem) {
      var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title");
      return $t.parse(iso8601);
    },
    isTime: function(elem) {
      // jQuery's `is()` doesn't play well with HTML5 in IE
      return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
    }
  });

  // functions that can be called via $(el).timeago('action')
  // init is default when no action is given
  // functions are called with context of a single element
  var functions = {
    init: function(){
      var refresh_el = $.proxy(refresh, this);
      refresh_el();
      var $s = $t.settings;
      if ($s.refreshMillis > 0) {
        setInterval(refresh_el, $s.refreshMillis);
      }
    },
    update: function(time){
      $(this).data('timeago', { datetime: $t.parse(time) });
      refresh.apply(this);
    },
    updateFromDOM: function(){
      $(this).data('timeago', { datetime: $t.parse( $t.isTime(this) ? $(this).attr("datetime") : $(this).attr("title") ) });
      refresh.apply(this);
    }
  };

  $.fn.timeago = function(action, options) {
    var fn = action ? functions[action] : functions.init;
    if(!fn){
      throw new Error("Unknown function name '"+ action +"' for timeago");
    }
    // each over objects here and call the requested function
    this.each(function(){
      fn.call(this, options);
    });
    return this;
  };

  function refresh() {
    var data = prepareData(this);
    var $s = $t.settings;

    if (!isNaN(data.datetime)) {
      if ( $s.cutoff == 0 || distance(data.datetime) < $s.cutoff) {
        $(this).text(inWords(data.datetime));
      }
    }
    return this;
  }

  function prepareData(element) {
    element = $(element);
    if (!element.data("timeago")) {
      element.data("timeago", { datetime: $t.datetime(element) });
      var text = $.trim(element.text());
      if ($t.settings.localeTitle) {
        element.attr("title", element.data('timeago').datetime.toLocaleString());
      } else if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) {
        element.attr("title", text);
      }
    }
    return element.data("timeago");
  }

  function inWords(date) {
    return $t.inWords(distance(date));
  }

  function distance(date) {
    return (new Date().getTime() - date.getTime());
  }

  // fix for IE6 suckage
  document.createElement("abbr");
  document.createElement("time");
}));


================================================
FILE: assets/client/static/js/ngrok.js
================================================
var ngrok = angular.module("ngrok", ["ngSanitize"]);

var hexRepr = function(bytes) {
    var buf = [];
    var ascii = [];
    for (var i=0; i<bytes.length; ++i) {
        var b = bytes[i];

        if (!(i%8) && i!=0) {
            buf.push("\t");
            buf.push.apply(buf, ascii)
            buf.push('\n');
            ascii = [];
        }

        if (b < 16) {
            buf.push("0");
        }

        if (b < 0x20 || b > 0x7e) {
            ascii.push('.');
        } else {
            ascii.push(String.fromCharCode(b));
        }

        buf.push(b.toString(16));
        buf.push(" ");
        ascii.push(" ");
    }

    if (ascii.length > 0) {
        var charsLeft = 8 - (ascii.length / 2);
        for (i=0; i<charsLeft; ++i) {
            buf.push("   ");
        }
        buf.push("\t");
        buf.push.apply(buf, ascii);
    }

    return buf.join("");
}

ngrok.factory("txnSvc", function() {
    var processBody = function(body, binary) {
        body.binary = binary;
        body.isForm = body.ContentType == "application/x-www-form-urlencoded";
        body.exists = body.Length > 0;
        body.hasError = !!body.Error;

        var syntaxClass = {
            "text/xml":               "xml",
            "application/xml":        "xml",
            "text/html":              "xml",
            "text/css":               "css",
            "application/json":       "json",
            "text/javascript":        "javascript",
            "application/javascript": "javascript",
        }[body.ContentType];

        // decode body
        if (binary) {
            body.Text = "";
        } else {
            body.Text = Base64.decode(body.Text).text;
        }

        // prettify
        var transform = {
            "xml": "xml",
            "json": "json"
        }[syntaxClass];

        if (!body.hasError && !!transform) {
            try {
                // vkbeautify does poorly at formatting html
                if (body.ContentType != "text/html") {
                    body.Text = vkbeautify[transform](body.Text);
                }
            } catch (e) {
            }
        }

        if (!!syntaxClass) {
            body.Text = hljs.highlight(syntaxClass, body.Text).value;
        } else {
            // highlight.js doesn't have a 'plaintext' syntax, so we'll just copy its escaping function.
            body.Text = body.Text.replace(/&/gm, '&amp;').replace(/</gm, '&lt;').replace(/>/gm, '&gt;');
        }
    };

    var processReq = function(req) {
        if (!req.RawBytes) {
            var decoded = Base64.decode(req.Raw);
            req.RawBytes = hexRepr(decoded.bytes);

            if (!req.Binary) {
                req.RawText = decoded.text;
            }
        }

        processBody(req.Body, req.Binary);
    };

    var processResp = function(resp) {
        resp.statusClass = {
            '2': "text-info",
            '3': "muted",
            '4': "text-warning",
            '5': "text-error"
        }[resp.Status[0]];

        if (!resp.RawBytes) {
            var decoded = Base64.decode(resp.Raw);
            resp.RawBytes = hexRepr(decoded.bytes);

            if (!resp.Binary) {
                resp.RawText = decoded.text;
            }
        }

        processBody(resp.Body, resp.Binary);
    };

    var processTxn = function(txn) {
        processReq(txn.Req);
        processResp(txn.Resp);
    };

    var preprocessTxn = function(txn) {
        var toFixed = function(value, precision) {
            var power = Math.pow(10, precision || 0);
            return String(Math.round(value * power) / power);
        }
        // parse nanosecond count
        var ns = txn.Duration;
        var ms = ns / (1000 * 1000);
        txn.Duration = ms;
        if (ms > 1000) {
            txn.Duration = toFixed(ms / 1000, 2) + "s";
        } else {
            txn.Duration = toFixed(ms, 2) + "ms";
        }
    };


    var active;
    var txns = window.data.Txns;
    txns.forEach(function(t) {
        preprocessTxn(t);
    });

    var activate = function(txn) {
        if (!txn.processed) {
            processTxn(txn);
            txn.processed = true;
        }
        active = txn;
    }

    if (txns.length > 0) {
        activate(txns[0]);
    }

    return {
        add: function(txnData) {
            txns.unshift(JSON.parse(txnData));
            preprocessTxn(txns[0]);
            if (!active) {
                activate(txns[0]);
            }
        },
        all: function() {
            return txns;
        },
        active: function(txn) {
            if (!txn) {
                return active;
            } else {
                activate(txn);
            }
        },
        isActive: function(txn) {
            return !!active && txn.Id == active.Id;
        }
    };
});

ngrok.directive({
    "keyval": function() {
        return {
            scope: {
                title: "@",
                tuples: "=",
            },
            replace: true,
            restrict: "E",
            template: "" +
            '<div ng-show="hasKeys()">' +
                '<h6>{{title}}</h6>' +
                '<table class="table params">' +
                    '<tr ng-repeat="(key, value) in tuples">' +
                        '<th>{{ key }}</th>' +
                        '<td>{{ value }}</td>' +
                    '</tr>' +
                '</table>' +
            '</div>',
            link: function($scope) {
                $scope.hasKeys = function() {
                    for (key in $scope.tuples) { return true; }
                    return false;
                };
            }
        };
    },

    "tabs": function() {
        return {
            scope: {
                "tabs": "@",
                "btn": "@",
                "onbtnclick": "&"
            },
            replace: true,
            template: '' +
            '<ul class="nav nav-pills">' +
                '<li ng-repeat="tab in tabNames" ng-class="{\'active\': isTab(tab)}">' +
                    '<a href="" ng-click="setTab(tab)">{{tab}}</a>' +
                '</li>' +
                '<li ng-show="!!btn" class="pull-right"> <button class="btn btn-primary" ng-click="onbtnclick()">{{btn}}</button></li>' +
            '</ul>',
            link: function postLink(scope, element, attrs) {
                scope.tabNames = attrs.tabs.split(",");
                scope.activeTab = scope.tabNames[0];
                scope.setTab = function(t) {
                    scope.activeTab = t;
                };
                scope.$parent.isTab = scope.isTab = function(t) {
                    return t == scope.activeTab;
                };
            },
        };
    },

    "body": function() {
        return {
            scope: {
                "body": "=",
                "binary": "="
            },
            template: '' +
            '<h6 ng-show="body.exists">' +
                '{{ body.Length }} bytes ' +
                '{{ body.RawContentType }}' +
            '</h6>' +
'' +
            '<div ng-show="!body.isForm && !body.binary">' +
                '<pre ng-show="body.exists"><code ng-bind-html="body.Text"></code></pre>' +
            '</div>' +
'' +
            '<div ng-show="body.isForm">' +
                '<keyval title="Form Params" tuples="body.Form">' +
            '</div>' +
            '<div ng-show="body.hasError" class="alert">' +
                '{{ body.Error }}' +
            '</div>',

            link: function($scope, $elem) {
                $scope.$watch(function() { return $scope.body; }, function() {
                    if ($scope.body && $scope.body.ErrorOffset > -1) {
                        var offset = $scope.body.ErrorOffset;

                        function textNodes(node) {
                            var textNodes = [];

                            function getTextNodes(node) {
                                if (node.nodeType == 3) {
                                    textNodes.push(node);
                                } else {
                                    for (var i = 0, len = node.childNodes.length; i < len; ++i) {
                                        getTextNodes(node.childNodes[i]);
                                    }
                                }
                            }

                            getTextNodes(node);
                            return textNodes;
                        }

                        var tNodes = textNodes($elem.find("code").get(0));
                        for (var i=0; i<tNodes.length; i++) {
                            offset -= tNodes[i].nodeValue.length;
                            if (offset < 0) {
                                $(tNodes[i]).parent().css("background-color", "orange");
                                break;
                            }
                        }
                    }
                });
            }
        };
    }
});

ngrok.controller({
    "HttpTxns": function($scope, txnSvc) {
        $scope.tunnels = window.data.UiState.Tunnels;
        $scope.txns = txnSvc.all();

        if (!!window.WebSocket) {
            var ws = new WebSocket("ws://" + location.host + "/_ws");
            ws.onopen = function() {
                console.log("connected websocket for real-time updates");
            };

            ws.onmessage = function(message) {
                $scope.$apply(function() {
                    txnSvc.add(message.data);
                });
            };

            ws.onerror = function(err) {
                console.log("Web socket error:")
                console.log(err);
            };

            ws.onclose = function(cls) {
                console.log("Web socket closed:" + cls);
            };
        }
    },

    "HttpRequest": function($scope, txnSvc) {
        $scope.replay = function() {
            $.ajax({
                type: "POST",
                url: "/http/in/replay",
                data: { txnid: txnSvc.active().Id }
            });
        }
        var setReq = function() {
            var txn = txnSvc.active();
            if (!!txn && txn.Req) {
                $scope.Req = txnSvc.active().Req;
            } else {
                $scope.Req = null;
            }
        };
        $scope.$watch(function() { return txnSvc.active() }, setReq);
    },

    "HttpResponse": function($scope, $element, txnSvc) {
        var setResp = function() {
            var txn = txnSvc.active();
            if (!!txn && txn.Resp) {
                $scope.Resp = txnSvc.active().Resp;
            } else {
                $scope.Resp = null;
            }
        };
        $scope.$watch(function() { return txnSvc.active() }, setResp);
    },

    "TxnNavItem": function($scope, txnSvc) {
        $scope.isActive = function() { return txnSvc.isActive($scope.txn); }
        $scope.makeActive = function() {
            txnSvc.active($scope.txn);
        };
    },

    "HttpTxn": function($scope, txnSvc, $timeout) {
        var setTxn = function() {
            $scope.Txn = txnSvc.active();
        };

        $scope.ISO8601 = function(ts) {
            if (!!ts) {
                return new Date(ts * 1000).toISOString();
            }
        };

        $scope.TimeFormat = function(ts) {
            if (!!ts) {
                return $.timeago($scope.ISO8601(ts));
            }
        };

        $scope.$watch(function() { return txnSvc.active() }, setTxn);

        // this causes angular to update the timestamps
        setInterval(function() { $scope.$apply(function() {}); }, 30000);
    },
});


================================================
FILE: assets/client/static/js/vkbeautify.js
================================================
/**
* vkBeautify - javascript plugin to pretty-print or minify text in XML, JSON, CSS and SQL formats.
*  
* Version - 0.99.00.beta 
* Copyright (c) 2012 Vadim Kiryukhin
* vkiryukhin @ gmail.com
* http://www.eslinstructor.net/vkbeautify/
* 
* Dual licensed under the MIT and GPL licenses:
*   http://www.opensource.org/licenses/mit-license.php
*   http://www.gnu.org/licenses/gpl.html
*
*   Pretty print
*
*        vkbeautify.xml(text [,indent_pattern]);
*        vkbeautify.json(text [,indent_pattern]);
*        vkbeautify.css(text [,indent_pattern]);
*        vkbeautify.sql(text [,indent_pattern]);
*
*        @text - String; text to beatufy;
*        @indent_pattern - Integer | String;
*                Integer:  number of white spaces;
*                String:   character string to visualize indentation ( can also be a set of white spaces )
*   Minify
*
*        vkbeautify.xmlmin(text [,preserve_comments]);
*        vkbeautify.jsonmin(text);
*        vkbeautify.cssmin(text [,preserve_comments]);
*        vkbeautify.sqlmin(text);
*
*        @text - String; text to minify;
*        @preserve_comments - Bool; [optional];
*                Set this flag to true to prevent removing comments from @text ( minxml and mincss functions only. )
*
*   Examples:
*        vkbeautify.xml(text); // pretty print XML
*        vkbeautify.json(text, 4 ); // pretty print JSON
*        vkbeautify.css(text, '. . . .'); // pretty print CSS
*        vkbeautify.sql(text, '----'); // pretty print SQL
*
*        vkbeautify.xmlmin(text, true);// minify XML, preserve comments
*        vkbeautify.jsonmin(text);// minify JSON
*        vkbeautify.cssmin(text);// minify CSS, remove comments ( default )
*        vkbeautify.sqlmin(text);// minify SQL
*
*/

(function() {

function createShiftArr(step) {

	var space = '    ';
	
	if ( isNaN(parseInt(step)) ) {  // argument is string
		space = step;
	} else { // argument is integer
		switch(step) {
			case 1: space = ' '; break;
			case 2: space = '  '; break;
			case 3: space = '   '; break;
			case 4: space = '    '; break;
			case 5: space = '     '; break;
			case 6: space = '      '; break;
			case 7: space = '       '; break;
			case 8: space = '        '; break;
			case 9: space = '         '; break;
			case 10: space = '          '; break;
			case 11: space = '           '; break;
			case 12: space = '            '; break;
		}
	}

	var shift = ['\n']; // array of shifts
	for(ix=0;ix<100;ix++){
		shift.push(shift[ix]+space); 
	}
	return shift;
}

function vkbeautify(){
	this.step = '    '; // 4 spaces
	this.shift = createShiftArr(this.step);
};

vkbeautify.prototype.xml = function(text,step) {

	var ar = text.replace(/>\s{0,}</g,"><")
				 .replace(/</g,"~::~<")
				 .replace(/\s*xmlns\:/g,"~::~xmlns:")
				 .replace(/\s*xmlns\=/g,"~::~xmlns=")
				 .split('~::~'),
		len = ar.length,
		inComment = false,
		deep = 0,
		str = '',
		ix = 0,
		shift = step ? createShiftArr(step) : this.shift;

		for(ix=0;ix<len;ix++) {
			// start comment or <![CDATA[...]]> or <!DOCTYPE //
			if(ar[ix].search(/<!/) > -1) { 
				str += shift[deep]+ar[ix];
				inComment = true; 
				// end comment  or <![CDATA[...]]> //
				if(ar[ix].search(/-->/) > -1 || ar[ix].search(/\]>/) > -1 || ar[ix].search(/!DOCTYPE/) > -1 ) { 
					inComment = false; 
				}
			} else 
			// end comment  or <![CDATA[...]]> //
			if(ar[ix].search(/-->/) > -1 || ar[ix].search(/\]>/) > -1) { 
				str += ar[ix];
				inComment = false; 
			} else 
			// <elm></elm> //
			if( /^<\w/.exec(ar[ix-1]) && /^<\/\w/.exec(ar[ix]) &&
				/^<[\w:\-\.\,]+/.exec(ar[ix-1]) == /^<\/[\w:\-\.\,]+/.exec(ar[ix])[0].replace('/','')) { 
				str += ar[ix];
				if(!inComment) deep--;
			} else
			 // <elm> //
			if(ar[ix].search(/<\w/) > -1 && ar[ix].search(/<\//) == -1 && ar[ix].search(/\/>/) == -1 ) {
				str = !inComment ? str += shift[deep++]+ar[ix] : str += ar[ix];
			} else 
			 // <elm>...</elm> //
			if(ar[ix].search(/<\w/) > -1 && ar[ix].search(/<\//) > -1) {
				str = !inComment ? str += shift[deep]+ar[ix] : str += ar[ix];
			} else 
			// </elm> //
			if(ar[ix].search(/<\//) > -1) { 
				str = !inComment ? str += shift[--deep]+ar[ix] : str += ar[ix];
			} else 
			// <elm/> //
			if(ar[ix].search(/\/>/) > -1 ) { 
				str = !inComment ? str += shift[deep]+ar[ix] : str += ar[ix];
			} else 
			// <? xml ... ?> //
			if(ar[ix].search(/<\?/) > -1) { 
				str += shift[deep]+ar[ix];
			} else 
			// xmlns //
			if( ar[ix].search(/xmlns\:/) > -1  || ar[ix].search(/xmlns\=/) > -1) { 
				str += shift[deep]+ar[ix];
			} 
			
			else {
				str += ar[ix];
			}
		}
		
	return  (str[0] == '\n') ? str.slice(1) : str;
}

vkbeautify.prototype.json = function(text,step) {

	var step = step ? step : this.step;
	
	if (typeof JSON === 'undefined' ) return text; 
	
	if ( typeof text === "string" ) return JSON.stringify(JSON.parse(text), null, step);
	if ( typeof text === "object" ) return JSON.stringify(text, null, step);
		
	return text; // text is not string nor object
}

vkbeautify.prototype.css = function(text, step) {

	var ar = text.replace(/\s{1,}/g,' ')
				.replace(/\{/g,"{~::~")
				.replace(/\}/g,"~::~}~::~")
				.replace(/\;/g,";~::~")
				.replace(/\/\*/g,"~::~/*")
				.replace(/\*\//g,"*/~::~")
				.replace(/~::~\s{0,}~::~/g,"~::~")
				.split('~::~'),
		len = ar.length,
		deep = 0,
		str = '',
		ix = 0,
		shift = step ? createShiftArr(step) : this.shift;
		
		for(ix=0;ix<len;ix++) {

			if( /\{/.exec(ar[ix]))  { 
				str += shift[deep++]+ar[ix];
			} else 
			if( /\}/.exec(ar[ix]))  { 
				str += shift[--deep]+ar[ix];
			} else
			if( /\*\\/.exec(ar[ix]))  { 
				str += shift[deep]+ar[ix];
			}
			else {
				str += shift[deep]+ar[ix];
			}
		}
		return str.replace(/^\n{1,}/,'');
}

//----------------------------------------------------------------------------

function isSubquery(str, parenthesisLevel) {
	return  parenthesisLevel - (str.replace(/\(/g,'').length - str.replace(/\)/g,'').length )
}

function split_sql(str, tab) {

	return str.replace(/\s{1,}/g," ")

				.replace(/ AND /ig,"~::~"+tab+tab+"AND ")
				.replace(/ BETWEEN /ig,"~::~"+tab+"BETWEEN ")
				.replace(/ CASE /ig,"~::~"+tab+"CASE ")
				.replace(/ ELSE /ig,"~::~"+tab+"ELSE ")
				.replace(/ END /ig,"~::~"+tab+"END ")
				.replace(/ FROM /ig,"~::~FROM ")
				.replace(/ GROUP\s{1,}BY/ig,"~::~GROUP BY ")
				.replace(/ HAVING /ig,"~::~HAVING ")
				//.replace(/ SET /ig," SET~::~")
				.replace(/ IN /ig," IN ")
				
				.replace(/ JOIN /ig,"~::~JOIN ")
				.replace(/ CROSS~::~{1,}JOIN /ig,"~::~CROSS JOIN ")
				.replace(/ INNER~::~{1,}JOIN /ig,"~::~INNER JOIN ")
				.replace(/ LEFT~::~{1,}JOIN /ig,"~::~LEFT JOIN ")
				.replace(/ RIGHT~::~{1,}JOIN /ig,"~::~RIGHT JOIN ")
				
				.replace(/ ON /ig,"~::~"+tab+"ON ")
				.replace(/ OR /ig,"~::~"+tab+tab+"OR ")
				.replace(/ ORDER\s{1,}BY/ig,"~::~ORDER BY ")
				.replace(/ OVER /ig,"~::~"+tab+"OVER ")

				.replace(/\(\s{0,}SELECT /ig,"~::~(SELECT ")
				.replace(/\)\s{0,}SELECT /ig,")~::~SELECT ")
				
				.replace(/ THEN /ig," THEN~::~"+tab+"")
				.replace(/ UNION /ig,"~::~UNION~::~")
				.replace(/ USING /ig,"~::~USING ")
				.replace(/ WHEN /ig,"~::~"+tab+"WHEN ")
				.replace(/ WHERE /ig,"~::~WHERE ")
				.replace(/ WITH /ig,"~::~WITH ")
				
				//.replace(/\,\s{0,}\(/ig,",~::~( ")
				//.replace(/\,/ig,",~::~"+tab+tab+"")

				.replace(/ ALL /ig," ALL ")
				.replace(/ AS /ig," AS ")
				.replace(/ ASC /ig," ASC ")	
				.replace(/ DESC /ig," DESC ")	
				.replace(/ DISTINCT /ig," DISTINCT ")
				.replace(/ EXISTS /ig," EXISTS ")
				.replace(/ NOT /ig," NOT ")
				.replace(/ NULL /ig," NULL ")
				.replace(/ LIKE /ig," LIKE ")
				.replace(/\s{0,}SELECT /ig,"SELECT ")
				.replace(/\s{0,}UPDATE /ig,"UPDATE ")
				.replace(/ SET /ig," SET ")
							
				.replace(/~::~{1,}/g,"~::~")
				.split('~::~');
}

vkbeautify.prototype.sql = function(text,step) {

	var ar_by_quote = text.replace(/\s{1,}/g," ")
							.replace(/\'/ig,"~::~\'")
							.split('~::~'),
		len = ar_by_quote.length,
		ar = [],
		deep = 0,
		tab = this.step,//+this.step,
		inComment = true,
		inQuote = false,
		parenthesisLevel = 0,
		str = '',
		ix = 0,
		shift = step ? createShiftArr(step) : this.shift;;

		for(ix=0;ix<len;ix++) {
			if(ix%2) {
				ar = ar.concat(ar_by_quote[ix]);
			} else {
				ar = ar.concat(split_sql(ar_by_quote[ix], tab) );
			}
		}
		
		len = ar.length;
		for(ix=0;ix<len;ix++) {
			
			parenthesisLevel = isSubquery(ar[ix], parenthesisLevel);
			
			if( /\s{0,}\s{0,}SELECT\s{0,}/.exec(ar[ix]))  { 
				ar[ix] = ar[ix].replace(/\,/g,",\n"+tab+tab+"")
			} 
			
			if( /\s{0,}\s{0,}SET\s{0,}/.exec(ar[ix]))  { 
				ar[ix] = ar[ix].replace(/\,/g,",\n"+tab+tab+"")
			} 
			
			if( /\s{0,}\(\s{0,}SELECT\s{0,}/.exec(ar[ix]))  { 
				deep++;
				str += shift[deep]+ar[ix];
			} else 
			if( /\'/.exec(ar[ix]) )  { 
				if(parenthesisLevel<1 && deep) {
					deep--;
				}
				str += ar[ix];
			}
			else  { 
				str += shift[deep]+ar[ix];
				if(parenthesisLevel<1 && deep) {
					deep--;
				}
			} 
			var junk = 0;
		}

		str = str.replace(/^\n{1,}/,'').replace(/\n{1,}/g,"\n");
		return str;
}


vkbeautify.prototype.xmlmin = function(text, preserveComments) {

	var str = preserveComments ? text
							   : text.replace(/\<![ \r\n\t]*(--([^\-]|[\r\n]|-[^\-])*--[ \r\n\t]*)\>/g,"")
									 .replace(/[ \r\n\t]{1,}xmlns/g, ' xmlns');
	return  str.replace(/>\s{0,}</g,"><"); 
}

vkbeautify.prototype.jsonmin = function(text) {

	if (typeof JSON === 'undefined' ) return text; 
	
	return JSON.stringify(JSON.parse(text), null, 0); 
				
}

vkbeautify.prototype.cssmin = function(text, preserveComments) {
	
	var str = preserveComments ? text
							   : text.replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\//g,"") ;

	return str.replace(/\s{1,}/g,' ')
			  .replace(/\{\s{1,}/g,"{")
			  .replace(/\}\s{1,}/g,"}")
			  .replace(/\;\s{1,}/g,";")
			  .replace(/\/\*\s{1,}/g,"/*")
			  .replace(/\*\/\s{1,}/g,"*/");
}

vkbeautify.prototype.sqlmin = function(text) {
	return text.replace(/\s{1,}/g," ").replace(/\s{1,}\(/,"(").replace(/\s{1,}\)/,")");
}

window.vkbeautify = new vkbeautify();

})();



================================================
FILE: assets/client/tls/ngrokroot.crt
================================================
-----BEGIN CERTIFICATE-----
MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
-----END CERTIFICATE-----


================================================
FILE: assets/client/tls/snakeoilca.crt
================================================
-----BEGIN CERTIFICATE-----
MIIFFDCCAvwCCQCkbN0RG/o15DANBgkqhkiG9w0BAQUFADBMMQswCQYDVQQGEwJV
UzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEChMJbmdyb2suY29tMRQwEgYD
VQQDFAsqLm5ncm9rLmNvbTAeFw0xMzA2MDMwMzUxNTZaFw0yMzA2MDEwMzUxNTZa
MEwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRIwEAYDVQQKEwlu
Z3Jvay5jb20xFDASBgNVBAMUCyoubmdyb2suY29tMIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEA6QryXeKl8AWWa9uG2UbSOpooH74zLkXs3FZfk9gKvqki
zXXQCRmtU6Dyn0+OuS3sE/rRmZAsSjkQG/YDtdE/SgL4dV6S62qiQngPokjR0USh
PC4Hwb8TjM9W5Cd+owVzMQ0vl0AYhQk8Yc/0vX+zDOwmRWGjNKPq422usF9CJFc/
8QY+ODJDHun8VVAkq3XfcPXgytHIqxvSJnYgDouFCA+GTsKp/65S5cigSlIrQZbH
775cTWhCjvYnq6gzyrk3RiGdb1IGuIJftMJxuJyJVbfTFtqgMGTmjHZxiLvM7dz7
j/bmrz4PvnhbQSZZLhsvP1o8mxnoNMpo/To5tHp/Ts6b5FQNL7FHpmOVLAoQ3FdX
VryTjoSjiE3JLDGZINQ9MFEPgPzR8mrzqFo/6e7uB4AYlKoQo01Kzx+YmVwRKEtr
VCTRZRcl66+gMkcX0ryoVnggjIWWu4d8uAh3jf++Kd0Djb/l7DCPpEgSJwZYTjCL
Z6SxiBwQ4o9dEQadMGgk3tlDFCBsrHoq7NyzvXHP0BF2HKb8KREBEKCIuQj9RChW
g07zmOpjngWs0CXaYly+TDP+5DZCMGD9kmXkQY9q/zqqvMt+T+/TBK9lwUsoi2Uc
v93wS+TNu06aRopqPo9YZr38ka3xKPiO964pk2BoFN57g767G8k9TbhkBxitvFUC
AwEAATANBgkqhkiG9w0BAQUFAAOCAgEARf/hVYntzUwFUgQrWD0l/UaCBgrlvxVC
yUa8Isj3vezAhFSyZntEL+ELFv8vvQbtBGHH/tCn76WuqjwOjVL23yxkaJsrnR9+
TRNFnVeB8157+IF6HKzLCL/HIAiQ0kw/2OSLD0lZnAkg24A0/9SHcpI5GA0WlThE
4GqgcUiN9m+mL8jWG3gj+SXC7IcVS3vAvS1J7Kz0NzTh1dYkQNWJlauO2Qn95T99
plkPPh87yZO9a9bxpX9PUJkTJzOwUkZISRZEbTA0CfspUpq/phzuTViy7o2fr+To
xVVa2aKT912vlQadWw5oqEK7xyxTqsYwV7CUljtCnhpS7wOZhwzI3qUk9HKH0Rt9
/HQsANuSikZosNvdM3/hv3c5DRUOwKiKdbgZCyqQf8XSeJRM1iQvcHo8U8kEJEEt
dmftn+0gH3RPsV028+7XD6wraqfc7dzNzLnq2rDLSAfG3T7pp0n19JyUieWsTR45
pGcwNpXDk+weqQKknwbNoha3o51Xq/1nhRtqrUPIEgOnBWBCV8vkhXMh9Vbe+oAm
LmweN1Mr6MjrHWddVn+JGcB5p+AMWr8zE+bhpPCAnupFR98z3fbOleMCWq6Q+hMP
MUThqJROHamHRIJ3Iz8whIrj7EKkDBKLEfE1ExS3B9VQ+YdHy6sjexdmCIQgElq4
4SMuY/JkZlo=
-----END CERTIFICATE-----


================================================
FILE: assets/server/tls/snakeoil.crt
================================================
-----BEGIN CERTIFICATE-----
MIIFDDCCAvQCAQEwDQYJKoZIhvcNAQEFBQAwTDELMAkGA1UEBhMCVVMxEzARBgNV
BAgTCkNhbGlmb3JuaWExEjAQBgNVBAoTCW5ncm9rLmNvbTEUMBIGA1UEAxQLKi5u
Z3Jvay5jb20wHhcNMTMwNjAzMDQyMzAwWhcNMjMwNjAxMDQyMzAwWjBMMQswCQYD
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEChMJbmdyb2suY29t
MRQwEgYDVQQDFAsqLm5ncm9rLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
AgoCggIBANouXC6wJacPIFYEiKIGGhliShxsy181LJ064ZTD3ARMTi9ONOsRkHXT
oM0CV1ZNDniPp3JILiGL52FPp0hdJegQ+pzz/gDjX0h9MCWexM4sqLpY0TfEqk9l
HWKPJg+ODK8wa1/wuQOK++m/XY3wcbZgLj23EHcGybl/xGJkaeaqGNvrJo/4RgSL
/jTpp0dDWKUmKBpTH4PuD6bkl8qKQAP1eul6ZS+Gr/llFYJWqwgd3SD3Wh/61Y4n
6sDusem2NQltnlFwe5VS1LfopVtO3e2N5fveEUdbg2YDs7evNYVK+1i0kjo0dD3D
tbmhe13OQ/baufxcHc1xjtGsJ57ws8nePL4eo+vFuD5Qjc9ESfzunSem0hC5AJgF
rsWzIYsr7UsiuKG4gQdD17L+DogVCqRcdYXugrPEJONi8YG3eRRP/Q9klX4PWy7O
Zj7KX/xBrvS5GeDNqrDVsPL0W24FJ//Sus7BSF0fc5DSYqtD4Zgf1GRi0+xSa3m9
AgJwWOHm6iC0mHwddozPLd+CP/M7PYzPRDiWY/ehoCFiqRAglYO0mhKyJvoTOjq6
TBYYp/8LcbqfqUkI6DSnZ2fNdXKDyirMSOT4K1ug1MHaq8obucLqx+5Hie5d6CKR
gnN15sJwGKIy27fg6LAzi1GWuCzZ1kHqjuQh3oVfuBKnFfsdLUnFAgMBAAEwDQYJ
KoZIhvcNAQEFBQADggIBADael2+SR8LC5xNCBcu4eqmil30Whb7qqr16EHU/MSQb
nmFMxBqTa2B8RDZIIkb7LukH8rsAdU8Bzkc2yRdjoAfEMMcJA/fMpwuaXI5cuaKV
idZFpNUyR+K5UG/CntcCvwzZp4//g+LVK9qPDZ2BJmA/PMR6OphRwRwG+ruSLUCi
ywgFFhNlPMpPZ49vsFm/Q0A4JmLpZXaARt53zNbNiHT1FgTP/9L1HIpkbfoFQT6R
pB/VYe8O+GmrwaL3l+L8aO07JRM+u0OKNPxOgxWgE7UngiWulXpnIHAXYxTVMQ8V
1IYutv9bYJ/+TAy9Kxzc4Q33K+5qvS58GCAjOF10NnHkiTQPbhUWISoj21eZJ1hb
Z4BbC5z/Y8zNbMao0ACF0QmlnXlt0YrkjkcZQuqerzpC6YMw32vsemREc55X9UQM
mVbnDah4xyPM+yNIg/uKHVTYJ6+4TttWEp1JGRaP2EOYr0yGe4XFFrKIPH2DlApM
nNfqR+Mfx9WZS3n6FuWPGWUDBye2fdxOjW9Jwc/+JDR3BsS65LlzPrs6C8TcNnnD
EdWJ4klYzWkuSdUiV1EXbsB1sSIKmUud2f4vJuOqlBsgS8/mTxjk123uXaN9zaN6
A9cMWQI2MbobK0HErkk0QyNTtVTKumEEko/c2ktsn8lrdJcKqCeQ9EKwp6r0Lmtp
-----END CERTIFICATE-----


================================================
FILE: assets/server/tls/snakeoil.key
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEA2i5cLrAlpw8gVgSIogYaGWJKHGzLXzUsnTrhlMPcBExOL040
6xGQddOgzQJXVk0OeI+nckguIYvnYU+nSF0l6BD6nPP+AONfSH0wJZ7EziyouljR
N8SqT2UdYo8mD44MrzBrX/C5A4r76b9djfBxtmAuPbcQdwbJuX/EYmRp5qoY2+sm
j/hGBIv+NOmnR0NYpSYoGlMfg+4PpuSXyopAA/V66XplL4av+WUVglarCB3dIPda
H/rVjifqwO6x6bY1CW2eUXB7lVLUt+ilW07d7Y3l+94RR1uDZgOzt681hUr7WLSS
OjR0PcO1uaF7Xc5D9tq5/FwdzXGO0awnnvCzyd48vh6j68W4PlCNz0RJ/O6dJ6bS
ELkAmAWuxbMhiyvtSyK4obiBB0PXsv4OiBUKpFx1he6Cs8Qk42Lxgbd5FE/9D2SV
fg9bLs5mPspf/EGu9LkZ4M2qsNWw8vRbbgUn/9K6zsFIXR9zkNJiq0PhmB/UZGLT
7FJreb0CAnBY4ebqILSYfB12jM8t34I/8zs9jM9EOJZj96GgIWKpECCVg7SaErIm
+hM6OrpMFhin/wtxup+pSQjoNKdnZ811coPKKsxI5PgrW6DUwdqryhu5wurH7keJ
7l3oIpGCc3XmwnAYojLbt+DosDOLUZa4LNnWQeqO5CHehV+4EqcV+x0tScUCAwEA
AQKCAgBb6Ru8L0gtUBn3IoHMf3WPK/C8eLhTqzrYIW3WFYwh42MsWm3AeO26NSSQ
OGRCXsOx1hJb+jw0tZMLU1rNCTBmyoBIjiB6j04cY2Bc+L0/fWC236ODMr3sJFR0
qIkIFHcTdfpFuEq4S1xD4/GtUZUVlv7j0LKG8b0Y/9HjARn7qbw/KJheHeChGbhE
4gkt5Bj7uU87h7jHAwpk6/dlw0ekY00b/guSMdL/5K1i8s+p46q7sHeu8SP1dqtW
Cze3lKJTDnKbLB9jkDk8IC1IgbjL0fMIX0w4Gz0HRJf40T5ioGuxup+/FUnCmyd6
w6QMqE/JNesTfFqxqRzZBwTJ1+xkXkSFmFhZ9McfxmEtozau0LH6TtkGmHvfmn6/
I8ImlIRyHGOUVHsEAR8c7aD9dag0LbXmjyolfin8Dow130/G3hTXYnDETx66k1RT
Bh6kDEYvuxvFqkXYKRAu5u11vQA9udYqN4i+htQ2OOYOEKNX8Kxy562rXgd85MyT
eTL4Dmh/6J7VtWG4nN2G8pnj5Lg99Q8fzEFQmFLFcK0kJwHu04pJaqrHL+uHHd5T
KOfPAr0pUbeu2sVd9chgDhADUE1/8YPhSCsHudM4No4tDCZVEqKJ1pGLCpUw3H1v
h0aA/B582p2CGFpGoInKfFKvUbYt2neDZZQygmYCUHywqNxQAQKCAQEA/H0MCJYV
VRRLufZkz3fpC+wT+coqbguwrT2bpVBuNdvyDKYIkbcSDtIVNsO/1p1/CGormDll
k1jNFhR+lqBB8QQfb1ZetPohxzPNncPiJypb9Uivu7GG1MNUHJUecFt8uITcY93s
UgRBVsCpLgUP7SF6m1Mpu2BaXQ/sEM8dYCSmt4Fax7LW00cIpLleZmCXoc/jsTUM
ixYTM5r5UWXBhl196Lfu+WjTmoxGgFeTwF8x1wuo7mQvPfripvUX/bjGR4o5JQOw
1I0XK13yrhFkEAIV61nHBGiyhmJShPaSI2uDudx0ZkfuUIM1UFvqeqWsjATaLmOW
X7lE0qNg75lsRQKCAQEA3Tcqgviss9a4Owr0BagleObUnTMFzk8bgpemNdzi47eG
7C572Wx8Gs6XgNbO28lpE0PD7KU/Rvt36wP9mzGiP0M3Zr4h81AfLvftoMMs6ytk
wHJOdtPUCZqvnblvKOI409nGnoUD+3nfH/qqHk4ZL3fn4vHbxwModZfOXRrH2hJr
EVqAnnexi9S0JbJJxdcSlBMI5vQMKGLCj52C2mfcF+XRMuF88bwfJ7Xcb3yhyv8i
pXHHZZ9QKE7eG76UO+EGKz8gn3wWKuh5HXp3HoyKidgzXtPIbu8fd4UzXl/BTMFc
jWAG0Ycsm5dTobQ0Yo0Xeju5Hs5QCtItY7OAFsH/gQKCAQBBCTC9UXNjO9wZpYbo
DdoAkSnAELwHJom2xgS+e044H1Rkv6u7ZO2I1cJTHe7fKChdkYNzLW2lm50QD+1f
fR4fJ9G1CwlQEpH6zrQq7Bbnwbh4IOXrMdoqGbojtqFljZs9qDNgofxKUABIiU3K
pdEpYpNDSROZyULdb8l9tuu5JRewcuhgQgel2kk2rOzM8Bp+up7KuYBmnyQJCeUo
e05y/sf81sv+gGrpBzLtwiEzzxF2c/FqnnGwxFv3Z3BrkVm5ebgoeZ/l0AXkzMlC
3wXoPbFJsxFZaGJ7zP22dBDGgN4oVMnCwsp3AKUN8u8d8mjUlDdi9ZH5TC6XFzBT
5zAFAoIBAFCjlXmc0MfV096iBYYyX0aNTp/nQ4yLRcn7IfmshYDhG+vonfkKFMto
1819gHaaGxWMtFUFf+WOMY6YK9Bw7WYGSKHJWXLqmBN1CUh7HVq0vMtyX6vtV/QQ
UUg7movau0BuuHp8npEDQhTUOUNG0ON+4CbYZ3dKbWtAZVeHNacG48S1qwEZPL1u
UiUTstTNq9YSgkI+YFgweCAGGPcouRB1FCdqDzPHkcvV/X8efZQUITsSGM+wnXW0
Gj8e38ZcJvWI04mPoD0P9WaLh/S44p+REljU9tGJlXzqL2mNmlcyfVyDzrh+gAJP
zYq6uAXczNwf/UF/j6oCJ82aV2z0VwECggEBAJga2Kb1ovNLMQ8Vum1tE8BOku9D
hTVpz+sFugZuY5Dzl9sPGT3UXGjZZ8pGEGpLPQK2YP5yc+ehCng3ePtXV0sPUR5W
17jibM2JYFkR9RJCCA8c++2OwxZTVJcSDUkHG7cpl5QLbzOJAWG78EziiNqZEyC0
RsYKPad6e0enYtPvYlnCYr77Y4KN6mU5Ot3OzqGeP4++dUMznL21vb2wmO6xHLR0
Mwa6w7fIXYNAOuCk0GMeYzSHZT7ZYytUUhiiTVbHZuazJR6JyR7yf3dnjkPfRVHe
qLsKIVNbyWayop61fNZWfN4FgLMmUDzCNNDz9sFX09Jm1QaEo+Dun7Dw8Ss=
-----END RSA PRIVATE KEY-----


================================================
FILE: contrib/com.ngrok.client.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!-- 
This is an example launchd script for MacOS. It was written under 10.9.1. 
You'll have to modify some arguments below to get this working on your box.
Unfortunetly I was unable to get any environment variables working in the launchd script.
-->
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>KeepAlive</key>
	<true/>
	<key>Label</key>
	<string>com.ngrok.client</string>
	<key>ProgramArguments</key>
	<array>
		<string>/usr/local/bin/ngrok</string>
		<string>-log</string>
		<string>stdout</string>
		<string>-subdomain</string>
		<string>mySubDomain</string>
		<string>80</string>
	</array>
	<key>StandardOutPath</key>
	<string>/tmp/ngrok.log</string>
	<key>StandardErrorPath</key>
	<string>/tmp/ngrok.log</string>
</dict>
</plist>


================================================
FILE: docs/CHANGELOG.md
================================================
# Changelog
## 1.7 - 6/6/2014
- IMPROVEMENT: Print a better help message when run without any arguments
- IMPROVEMENT: Display useful help message and instructions when double-clicked from explorer on Windows
- IMPROVEMENT: ngrok now uses the specified server_addr to set the SNI header instead of forcing ngrokd.ngrok.com
- IMPROVEMENT: ngrok now uses equinox.io for automatic updates with greater speed and safety
- IMPROVEMENT: Many documentation improvements
- IMPROVEMENT: Added example plist file for autostart on OS X
- BUGFIX: Fixed an issue where ngrok could crash when parsing some websocket requests
- BUGFIX: Fixed an issue where the web UI would truncate the raw request to 8192 bytes
- BUGFIX: Fixed an issue where ngrok could not replay requests where the request was larger than 8192 bytes
- BUGFIX: Fixed an issue where the web UI would not update in realtime when not accessed over localhost
- BUGFIX: Fixed an unlikely race condition in ngrokd when loading the tunnel URL cache
- BUGFIX: Check for a valid server address without trying to resolve for less confusing errors

## 1.6 -  10/25/2013
- BUGFIX: Fixed a goroutine/memory leak in ngrok/proto's parsing of http traffic
- IMPROVEMENT: The web inspection API can now be disabled again by setting inspect_addr: disabled in the config file

## 1.5 - 10/20/2013
- FEATURE: Added support a "remote_port" configuration parameter that lets you request a specific remote port for TCP tunnels
- IMPROVEMENT: Upload instructions on crash reports are displayed after the dump where it is more likely to be seen
- IMPROVEMENT: Improvements to ngrok's logging for easier debugging
- IMPROVEMENT: Batch metric reporting to Keen to not be limited by the speed of their API at high request loads
- IMPROVEMENT: Added additional safety to ensure the server doesn't crash on panics()
- BUGFIX: Fixed an issue with prefetching tunnel connections that could hang tunnel connections when behind an aggressive NAT
- BUGFIX: Fixed a race condition where ngrokd could send back a different message instead of AuthResp first
- BUGFIX: Fixed an issue where under some circumstances, reconnecting would fail and tell the client the tunnels were still in use
- BUGFIX: Fixed an issue where a race-condition with handling pings could cause a tunnel to hang forever and stop handling requests

## 1.4 - 09/27/2013
- BUGFIX: Fixed an issue where long URL paths were not truncated in the terminal UI
- BUGFIX: Fixed an issue where long URL paths ruined the web UI's formatting
- BUGFIX: Fixed an issue where authtokens would not be remembered if an existing configuration file didn't exist

## 0.23 - 09/06/2013
- BUGFIX: Fixed a bug which caused some important HTTP headers to be omitted from request introspection and replay

## 0.22 - 09/04/2013
- FEATURE: ngrok now tunnels websocket requests

## 0.21 - 08/17/2013
- IMPROVEMENT: The ngrok web ui can now be disabled with -webport=-1

## 0.20 - 08/17/2013
- BUGFIX: Fixed a bug where ngrok would not stop its autoupdate loop even after it should stop

## 0.19 - 08/17/2013
- BUGFIX: Fixed a bug where ngrok's would loop infinitely trying to checking for updates after the second update check
- BUGFIX: Fixed a race condition in ngrokd's metrics logging immediately after start up

## 0.18 - 08/15/2013
- BUGFIX: Fixed a bug where ngrok would compare the Host header for virtual hosting using case-sensitive comparisons
- BUGFIX: Fixed a bug where ngrok would not include the port number in the virtual host when not serving on port 80
- BUGFIX: Fixed a bug where ngrok would crash when trying to replay a request
- IMPROVEMENT: ngrok can now indicate manual updates again
- IMPROVEMENT: ngrok can now supports update channels
- IMPROVEMENT: ngrok can now detect some updates that will fail before downloading

## 0.17 - 07/30/2013
- BUGFIX: Fixed an issue where ngrok's registry cache would return a URL from a different protocol

## 0.16 - 07/30/2013
- BUGFIX: Fixed an issue where ngrok would crash when parsing bad XML that wasn't a syntax error
- BUGFIX: Fixed an issue where ngrok would crash when parsing bad JSON that wasn't a syntax error
- BUGFIX: Fixed an issue where the web ui would sometimes not update the request body when changing requests
- BUGFIX: Fixed an issue where ngrokd's registry cache would not load from file
- BUGFIX: Fixed an issue where ngrokd's registry cache would not save to file
- BUGFIX: Fixed an issue where ngrok would refuse requests with an Authorization header if no HTTP auth was specified.
- BUGFIX: Fixed a bug where ngrok would fail to cross-compile in you hadn't compiled natively first
- IMPROVEMENT: ngrok's registry cache now handles and attempts to restore TCP URLs
- IMPROVEMENT: Added simple Travis CI integration to make sure ngrok compiles

## 0.15 - 07/27/2013
- FEATURE: ngrok can now update itself automatically

## 0.14 - 07/03/2013
- BUGFIX: Fix an issue where ngrok could never save/load the authtoken file on linux
- BUGFIX: Fix an issue where ngrok wouldn't emit log messages while loading authtokens

## 0.13 - 07/02/2013
- FEATURE: -hostname switch on client allows you to run tunnels over custom domains (requires you CNAME your DNS)
- IMPROVEMENT: ngrok client UI now shows the client IP address for a request
- IMPROVEMENT: ngrok client UI now shows how long ago a request was made (uservoice request 4127487)
- IMPROVEMENT: ngrokd now uses and LRU cache for tunnel affinity data
- IMPROVEMENT: ngrokd can now save and restore its tunnel affinity cache to a file to preserve across restarts

## 0.12 - 06/30/2013
- IMPROVEMENT: Improved developer documentation
- IMPROVEMENT: Simplified build process with custom version of go-bindata that compiles assets into binary releases
- BUGFIX: GitHub issue #4: Raw/Binary requests bodies are no longer truncated at 8192 bytes.


================================================
FILE: docs/DEVELOPMENT.md
================================================
# Developer's guide to ngrok


## Components
The ngrok project is composed of two components, the ngrok client (ngrok) and the ngrok server (ngrokd).
The ngrok client is the more complicated piece because it has UIs for displaying saved requests and responses.

## Compiling

    git clone git@github.com:inconshreveable/ngrok.git
    cd ngrok && make
    bin/ngrok [LOCAL PORT]

There are Makefile targets for compiling just the client or server.

    make client
    make server

**NB: You must compile with Go 1.1+! You must have Mercurial SCM Installed.**

### Compiling release versions
Both the client and the server contain static asset files.
These include TLS/SSL certificates and the html/css/js for the client's web interface.
The release versions embed all of this data into the binaries themselves, whereas the debug versions read these files from the filesystem.

*You should always develop on debug versions so that you don't have to recompile when testing changes in the static assets.*

There are Makefile targets for compiling the client and server for releases:

    make release-client
    make release-server
    make release-all


## Developing locally
The strategy I use for developing on ngrok is to do the following:

Add the following lines to /etc/hosts:

    127.0.0.1 ngrok.me
    127.0.0.1 test.ngrok.me

Run ngrokd with the following options:

    ./bin/ngrokd -domain ngrok.me

Create an ngrok configuration file, "debug.yml" with the following contents:

    server_addr: ngrok.me:4443
    tunnels:
      test:
        proto:
          http: 8080


Then run ngrok with either of these commands:

    ./bin/ngrok -config=debug.yml -log=ngrok.log start test
    ./bin/ngrok -config=debug.yml -log=ngrok.log -subdomain=test 8080

This will get you setup with an ngrok client talking to an ngrok server all locally under your control. Happy hacking!


## Network protocol and tunneling
At a high level, ngrok's tunneling works as follows:

### Connection Setup and Authentication
1. 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*.
1. After the connection is established, the client sends an *Auth* message with authentication and version information.
1. The server validates the client's *Auth* message and sends an *AuthResp* message indicating either success or failure.

### Tunnel creation
1. The client may then ask the server to create tunnels for it by sending *ReqTunnel* messages. 
1. When the server receives a *ReqTunnel* message, it will send 1 or more *NewTunnel* messages that indicate successful tunnel creation or indicate failure.

### Tunneling connections
1. 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*.
1. The server sends a *ReqProxy* message to the client over the control connection.
1. The client initiates a new TCP connection to the server called a *Proxy Connection*.
1. 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).
1. The server sends a *StartProxy* message over the proxy connection with metadata information about the connection (the client IP and name of the tunnel).
1. The server begins copying the traffic byte-for-byte from the public connection to the proxy connection and vice-versa.
1. The client opens a connection to the local address configured for that tunnel. This is called the *Private Connection*.
1. The client begins copying the traffic byte-for-byte from the proxied connection to the private connection and vice-versa.

### Detecting dead tunnels
1. 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.
1. 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.

### Wire format
Messages are sent over the wire as netstrings of the form:

    <message length><message payload>

The message length is sent as a 64-bit little endian integer.

### Code
The definitions and shared protocol routines lives under _src/ngrok/msg_

#### src/ngrok/msg/msg.go
All 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.
    
## ngrokd - the server
### Code
Code for the server lives under src/ngrok/server

### Entry point
The ngrokd entry point is in _src/ngrok/server/main.go_.
There 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.

## ngrok - the client
### Code
Code for the client lives under src/ngrok/client

### Entry point
The ngrok entry point is in _src/ngrok/client/main.go_.
There 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.

## Static assets
The 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.

## Beyond
More documentation can be found in the comments of the code itself.


================================================
FILE: docs/SELFHOSTING.md
================================================
# How to run your own ngrokd server

Running your own ngrok server is really easy! The instructions below will guide you along your way!

## 1. Get an SSL certificate
ngrok provides secure tunnels via TLS, so you'll need an SSL certificate. Assuming you want to create
tunnels on *.example.com, buy a wildcard SSL certificate for *.example.com. Note that if you
don't need to run https tunnels that you don't need a wildcard certificate. (In fact, you can
just use a self-signed cert at that point, see the section on that later in the document).

## 2. Modify your DNS
You need to use the DNS management tools given to you by your provider to create an A
record which points *.example.com to the IP address of the server where you will run ngrokd.

## 3. Compile it
You can compile an ngrokd server with the following command:

	make release-server

Make sure you compile it with the GOOS/GOARCH environment variables set to the platform of
your target server. Then copy the binary over to your server.

## 4. Run the server
You'll run the server with the following command.


	./ngrokd -tlsKey="/path/to/tls.key" -tlsCrt="/path/to/tls.crt" -domain="example.com"

### Specifying your TLS certificate and key
ngrok only makes TLS-encrypted connections. When you run ngrokd, you'll need to instruct it
where to find your TLS certificate and private key. Specify the paths with the following switches:

	-tlsKey="/path/to/tls.key" -tlsCrt="/path/to/tls.crt"

### Setting the server's domain
When you run your own ngrokd server, you need to tell ngrokd the domain it's running on so that it
knows what URLs to issue to clients.

	-domain="example.com"

## 5. Configure the client
In order to connect with a client, you'll need to set two options in ngrok's configuration file.
The ngrok configuration file is a simple YAML file that is read from ~/.ngrok by default. You may specify
a custom configuration file path with the -config switch. Your config file must contain the following two
options.

	server_addr: example.com:4443
	trust_host_root_certs: true

Substitute the address of your ngrokd server for "example.com:4443". The "trust_host_root_certs" parameter instructs
ngrok to trust the root certificates on your computer when establishing TLS connections to the server. By default, ngrok
only trusts the root certificate for ngrok.com.

## 6. Connect with a client
Then, just run ngrok as usual to connect securely to your own ngrokd server!

	ngrok 80

# ngrokd with a self-signed SSL certificate
It's possible to run ngrokd with a a self-signed certificate, but you'll need to recompile ngrok with your signing CA.
If you do choose to use a self-signed cert, please note that you must either remove the configuration value for
trust_host_root_certs or set it to false:

    trust_host_root_certs: false

Special 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:

https://gist.github.com/lyoshenka/002b7fbd801d0fd21f2f
https://github.com/inconshreveable/ngrok/issues/84



================================================
FILE: src/ngrok/cache/lru.go
================================================
// Copyright 2012, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// The implementation borrows heavily from SmallLRUCache (originally by Nathan
// Schrenk). The object maintains a doubly-linked list of elements in the
// When an element is accessed it is promoted to the head of the list, and when
// space is needed the element at the tail of the list (the least recently used
// element) is evicted.
package cache

import (
	"container/list"
	"encoding/gob"
	"fmt"
	"io"
	"os"
	"sync"
	"time"
)

type LRUCache struct {
	mu sync.Mutex

	// list & table of *entry objects
	list  *list.List
	table map[string]*list.Element

	// Our current size, in bytes. Obviously a gross simplification and low-grade
	// approximation.
	size uint64

	// How many bytes we are limiting the cache to.
	capacity uint64
}

// Values that go into LRUCache need to satisfy this interface.
type Value interface {
	Size() int
}

type Item struct {
	Key   string
	Value Value
}

type entry struct {
	key           string
	value         Value
	size          int
	time_accessed time.Time
}

func NewLRUCache(capacity uint64) *LRUCache {
	return &LRUCache{
		list:     list.New(),
		table:    make(map[string]*list.Element),
		capacity: capacity,
	}
}

func (lru *LRUCache) Get(key string) (v Value, ok bool) {
	lru.mu.Lock()
	defer lru.mu.Unlock()

	element := lru.table[key]
	if element == nil {
		return nil, false
	}
	lru.moveToFront(element)
	return element.Value.(*entry).value, true
}

func (lru *LRUCache) Set(key string, value Value) {
	lru.mu.Lock()
	defer lru.mu.Unlock()

	if element := lru.table[key]; element != nil {
		lru.updateInplace(element, value)
	} else {
		lru.addNew(key, value)
	}
}

func (lru *LRUCache) SetIfAbsent(key string, value Value) {
	lru.mu.Lock()
	defer lru.mu.Unlock()

	if element := lru.table[key]; element != nil {
		lru.moveToFront(element)
	} else {
		lru.addNew(key, value)
	}
}

func (lru *LRUCache) Delete(key string) bool {
	lru.mu.Lock()
	defer lru.mu.Unlock()

	element := lru.table[key]
	if element == nil {
		return false
	}

	lru.list.Remove(element)
	delete(lru.table, key)
	lru.size -= uint64(element.Value.(*entry).size)
	return true
}

func (lru *LRUCache) Clear() {
	lru.mu.Lock()
	defer lru.mu.Unlock()

	lru.list.Init()
	lru.table = make(map[string]*list.Element)
	lru.size = 0
}

func (lru *LRUCache) SetCapacity(capacity uint64) {
	lru.mu.Lock()
	defer lru.mu.Unlock()

	lru.capacity = capacity
	lru.checkCapacity()
}

func (lru *LRUCache) Stats() (length, size, capacity uint64, oldest time.Time) {
	lru.mu.Lock()
	defer lru.mu.Unlock()
	if lastElem := lru.list.Back(); lastElem != nil {
		oldest = lastElem.Value.(*entry).time_accessed
	}
	return uint64(lru.list.Len()), lru.size, lru.capacity, oldest
}

func (lru *LRUCache) StatsJSON() string {
	if lru == nil {
		return "{}"
	}
	l, s, c, o := lru.Stats()
	return fmt.Sprintf("{\"Length\": %v, \"Size\": %v, \"Capacity\": %v, \"OldestAccess\": \"%v\"}", l, s, c, o)
}

func (lru *LRUCache) Keys() []string {
	lru.mu.Lock()
	defer lru.mu.Unlock()

	keys := make([]string, 0, lru.list.Len())
	for e := lru.list.Front(); e != nil; e = e.Next() {
		keys = append(keys, e.Value.(*entry).key)
	}
	return keys
}

func (lru *LRUCache) Items() []Item {
	lru.mu.Lock()
	defer lru.mu.Unlock()

	items := make([]Item, 0, lru.list.Len())
	for e := lru.list.Front(); e != nil; e = e.Next() {
		v := e.Value.(*entry)
		items = append(items, Item{Key: v.key, Value: v.value})
	}
	return items
}

func (lru *LRUCache) SaveItems(w io.Writer) error {
	items := lru.Items()
	encoder := gob.NewEncoder(w)
	return encoder.Encode(items)
}

func (lru *LRUCache) SaveItemsToFile(path string) error {
	if wr, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil {
		return err
	} else {
		defer wr.Close()
		return lru.SaveItems(wr)
	}
}

func (lru *LRUCache) LoadItems(r io.Reader) error {
	items := make([]Item, 0)
	decoder := gob.NewDecoder(r)
	if err := decoder.Decode(&items); err != nil {
		return err
	}

	lru.mu.Lock()
	defer lru.mu.Unlock()
	for _, item := range items {
		// XXX: copied from Set()
		if element := lru.table[item.Key]; element != nil {
			lru.updateInplace(element, item.Value)
		} else {
			lru.addNew(item.Key, item.Value)
		}
	}

	return nil
}

func (lru *LRUCache) LoadItemsFromFile(path string) error {
	if rd, err := os.Open(path); err != nil {
		return err
	} else {
		defer rd.Close()
		return lru.LoadItems(rd)
	}
}

func (lru *LRUCache) updateInplace(element *list.Element, value Value) {
	valueSize := value.Size()
	sizeDiff := valueSize - element.Value.(*entry).size
	element.Value.(*entry).value = value
	element.Value.(*entry).size = valueSize
	lru.size += uint64(sizeDiff)
	lru.moveToFront(element)
	lru.checkCapacity()
}

func (lru *LRUCache) moveToFront(element *list.Element) {
	lru.list.MoveToFront(element)
	element.Value.(*entry).time_accessed = time.Now()
}

func (lru *LRUCache) addNew(key string, value Value) {
	newEntry := &entry{key, value, value.Size(), time.Now()}
	element := lru.list.PushFront(newEntry)
	lru.table[key] = element
	lru.size += uint64(newEntry.size)
	lru.checkCapacity()
}

func (lru *LRUCache) checkCapacity() {
	// Partially duplicated from Delete
	for lru.size > lru.capacity {
		delElem := lru.list.Back()
		delValue := delElem.Value.(*entry)
		lru.list.Remove(delElem)
		delete(lru.table, delValue.key)
		lru.size -= uint64(delValue.size)
	}
}


================================================
FILE: src/ngrok/client/cli.go
================================================
package client

import (
	"flag"
	"fmt"
	"ngrok/version"
	"os"
)

const usage1 string = `Usage: %s [OPTIONS] <local port or address>
Options:
`

const usage2 string = `
Examples:
	ngrok 80
	ngrok -subdomain=example 8080
	ngrok -proto=tcp 22
	ngrok -hostname="example.com" -httpauth="user:password" 10.0.0.1


Advanced usage: ngrok [OPTIONS] <command> [command args] [...]
Commands:
	ngrok start [tunnel] [...]    Start tunnels by name from config file
	ngork start-all               Start all tunnels defined in config file
	ngrok list                    List tunnel names from config file
	ngrok help                    Print help
	ngrok version                 Print ngrok version

Examples:
	ngrok start www api blog pubsub
	ngrok -log=stdout -config=ngrok.yml start ssh
	ngrok start-all
	ngrok version

`

type Options struct {
	config    string
	logto     string
	loglevel  string
	authtoken string
	httpauth  string
	hostname  string
	protocol  string
	subdomain string
	command   string
	args      []string
}

func ParseArgs() (opts *Options, err error) {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, usage1, os.Args[0])
		flag.PrintDefaults()
		fmt.Fprintf(os.Stderr, usage2)
	}

	config := flag.String(
		"config",
		"",
		"Path to ngrok configuration file. (default: $HOME/.ngrok)")

	logto := flag.String(
		"log",
		"none",
		"Write log messages to this file. 'stdout' and 'none' have special meanings")

	loglevel := flag.String(
		"log-level",
		"DEBUG",
		"The level of messages to log. One of: DEBUG, INFO, WARNING, ERROR")

	authtoken := flag.String(
		"authtoken",
		"",
		"Authentication token for identifying an ngrok.com account")

	httpauth := flag.String(
		"httpauth",
		"",
		"username:password HTTP basic auth creds protecting the public tunnel endpoint")

	subdomain := flag.String(
		"subdomain",
		"",
		"Request a custom subdomain from the ngrok server. (HTTP only)")

	hostname := flag.String(
		"hostname",
		"",
		"Request a custom hostname from the ngrok server. (HTTP only) (requires CNAME of your DNS)")

	protocol := flag.String(
		"proto",
		"http+https",
		"The protocol of the traffic over the tunnel {'http', 'https', 'tcp'} (default: 'http+https')")

	flag.Parse()

	opts = &Options{
		config:    *config,
		logto:     *logto,
		loglevel:  *loglevel,
		httpauth:  *httpauth,
		subdomain: *subdomain,
		protocol:  *protocol,
		authtoken: *authtoken,
		hostname:  *hostname,
		command:   flag.Arg(0),
	}

	switch opts.command {
	case "list":
		opts.args = flag.Args()[1:]
	case "start":
		opts.args = flag.Args()[1:]
	case "start-all":
		opts.args = flag.Args()[1:]
	case "version":
		fmt.Println(version.MajorMinor())
		os.Exit(0)
	case "help":
		flag.Usage()
		os.Exit(0)
	case "":
		err = fmt.Errorf("Error: Specify a local port to tunnel to, or " +
			"an ngrok command.\n\nExample: To expose port 80, run " +
			"'ngrok 80'")
		return

	default:
		if len(flag.Args()) > 1 {
			err = fmt.Errorf("You may only specify one port to tunnel to on the command line, got %d: %v",
				len(flag.Args()),
				flag.Args())
			return
		}

		opts.command = "default"
		opts.args = flag.Args()
	}

	return
}


================================================
FILE: src/ngrok/client/config.go
================================================
package client

import (
	"fmt"
	"gopkg.in/yaml.v1"
	"io/ioutil"
	"net"
	"net/url"
	"ngrok/log"
	"os"
	"os/user"
	"path"
	"regexp"
	"strconv"
	"strings"
)

type Configuration struct {
	HttpProxy          string                          `yaml:"http_proxy,omitempty"`
	ServerAddr         string                          `yaml:"server_addr,omitempty"`
	InspectAddr        string                          `yaml:"inspect_addr,omitempty"`
	TrustHostRootCerts bool                            `yaml:"trust_host_root_certs,omitempty"`
	AuthToken          string                          `yaml:"auth_token,omitempty"`
	Tunnels            map[string]*TunnelConfiguration `yaml:"tunnels,omitempty"`
	LogTo              string                          `yaml:"-"`
	Path               string                          `yaml:"-"`
}

type TunnelConfiguration struct {
	Subdomain  string            `yaml:"subdomain,omitempty"`
	Hostname   string            `yaml:"hostname,omitempty"`
	Protocols  map[string]string `yaml:"proto,omitempty"`
	HttpAuth   string            `yaml:"auth,omitempty"`
	RemotePort uint16            `yaml:"remote_port,omitempty"`
}

func LoadConfiguration(opts *Options) (config *Configuration, err error) {
	configPath := opts.config
	if configPath == "" {
		configPath = defaultPath()
	}

	log.Info("Reading configuration file %s", configPath)
	configBuf, err := ioutil.ReadFile(configPath)
	if err != nil {
		// failure to read a configuration file is only a fatal error if
		// the user specified one explicitly
		if opts.config != "" {
			err = fmt.Errorf("Failed to read configuration file %s: %v", configPath, err)
			return
		}
	}

	// deserialize/parse the config
	config = new(Configuration)
	if err = yaml.Unmarshal(configBuf, &config); err != nil {
		err = fmt.Errorf("Error parsing configuration file %s: %v", configPath, err)
		return
	}

	// try to parse the old .ngrok format for backwards compatibility
	matched := false
	content := strings.TrimSpace(string(configBuf))
	if matched, err = regexp.MatchString("^[0-9a-zA-Z_\\-!]+$", content); err != nil {
		return
	} else if matched {
		config = &Configuration{AuthToken: content}
	}

	// set configuration defaults
	if config.ServerAddr == "" {
		config.ServerAddr = defaultServerAddr
	}

	if config.InspectAddr == "" {
		config.InspectAddr = defaultInspectAddr
	}

	if config.HttpProxy == "" {
		config.HttpProxy = os.Getenv("http_proxy")
	}

	// validate and normalize configuration
	if config.InspectAddr != "disabled" {
		if config.InspectAddr, err = normalizeAddress(config.InspectAddr, "inspect_addr"); err != nil {
			return
		}
	}

	if config.ServerAddr, err = normalizeAddress(config.ServerAddr, "server_addr"); err != nil {
		return
	}

	if config.HttpProxy != "" {
		var proxyUrl *url.URL
		if proxyUrl, err = url.Parse(config.HttpProxy); err != nil {
			return
		} else {
			if proxyUrl.Scheme != "http" && proxyUrl.Scheme != "https" {
				err = fmt.Errorf("Proxy url scheme must be 'http' or 'https', got %v", proxyUrl.Scheme)
				return
			}
		}
	}

	for name, t := range config.Tunnels {
		if t == nil || t.Protocols == nil || len(t.Protocols) == 0 {
			err = fmt.Errorf("Tunnel %s does not specify any protocols to tunnel.", name)
			return
		}

		for k, addr := range t.Protocols {
			tunnelName := fmt.Sprintf("for tunnel %s[%s]", name, k)
			if t.Protocols[k], err = normalizeAddress(addr, tunnelName); err != nil {
				return
			}

			if err = validateProtocol(k, tunnelName); err != nil {
				return
			}
		}

		// use the name of the tunnel as the subdomain if none is specified
		if t.Hostname == "" && t.Subdomain == "" {
			// XXX: a crude heuristic, really we should be checking if the last part
			// is a TLD
			if len(strings.Split(name, ".")) > 1 {
				t.Hostname = name
			} else {
				t.Subdomain = name
			}
		}
	}

	// override configuration with command-line options
	config.LogTo = opts.logto
	config.Path = configPath
	if opts.authtoken != "" {
		config.AuthToken = opts.authtoken
	}

	switch opts.command {
	// start a single tunnel, the default, simple ngrok behavior
	case "default":
		config.Tunnels = make(map[string]*TunnelConfiguration)
		config.Tunnels["default"] = &TunnelConfiguration{
			Subdomain: opts.subdomain,
			Hostname:  opts.hostname,
			HttpAuth:  opts.httpauth,
			Protocols: make(map[string]string),
		}

		for _, proto := range strings.Split(opts.protocol, "+") {
			if err = validateProtocol(proto, "default"); err != nil {
				return
			}

			if config.Tunnels["default"].Protocols[proto], err = normalizeAddress(opts.args[0], ""); err != nil {
				return
			}
		}

	// list tunnels
	case "list":
		for name, _ := range config.Tunnels {
			fmt.Println(name)
		}
		os.Exit(0)

	// start tunnels
	case "start":
		if len(opts.args) == 0 {
			err = fmt.Errorf("You must specify at least one tunnel to start")
			return
		}

		requestedTunnels := make(map[string]bool)
		for _, arg := range opts.args {
			requestedTunnels[arg] = true

			if _, ok := config.Tunnels[arg]; !ok {
				err = fmt.Errorf("Requested to start tunnel %s which is not defined in the config file.", arg)
				return
			}
		}

		for name, _ := range config.Tunnels {
			if !requestedTunnels[name] {
				delete(config.Tunnels, name)
			}
		}

	case "start-all":
		return

	default:
		err = fmt.Errorf("Unknown command: %s", opts.command)
		return
	}

	return
}

func defaultPath() string {
	user, err := user.Current()

	// user.Current() does not work on linux when cross compiling because
	// it requires CGO; use os.Getenv("HOME") hack until we compile natively
	homeDir := os.Getenv("HOME")
	if err != nil {
		log.Warn("Failed to get user's home directory: %s. Using $HOME: %s", err.Error(), homeDir)
	} else {
		homeDir = user.HomeDir
	}

	return path.Join(homeDir, ".ngrok")
}

func normalizeAddress(addr string, propName string) (string, error) {
	// normalize port to address
	if _, err := strconv.Atoi(addr); err == nil {
		addr = ":" + addr
	}

	host, port, err := net.SplitHostPort(addr)
	if err != nil {
		return "", fmt.Errorf("Invalid address %s '%s': %s", propName, addr, err.Error())
	}

	if host == "" {
		host = "127.0.0.1"
	}

	return fmt.Sprintf("%s:%s", host, port), nil
}

func validateProtocol(proto, propName string) (err error) {
	switch proto {
	case "http", "https", "http+https", "tcp":
	default:
		err = fmt.Errorf("Invalid protocol for %s: %s", propName, proto)
	}

	return
}

func SaveAuthToken(configPath, authtoken string) (err error) {
	// empty configuration by default for the case that we can't read it
	c := new(Configuration)

	// read the configuration
	oldConfigBytes, err := ioutil.ReadFile(configPath)
	if err == nil {
		// unmarshal if we successfully read the configuration file
		if err = yaml.Unmarshal(oldConfigBytes, c); err != nil {
			return
		}
	}

	// no need to save, the authtoken is already the correct value
	if c.AuthToken == authtoken {
		return
	}

	// update auth token
	c.AuthToken = authtoken

	// rewrite configuration
	newConfigBytes, err := yaml.Marshal(c)
	if err != nil {
		return
	}

	err = ioutil.WriteFile(configPath, newConfigBytes, 0600)
	return
}


================================================
FILE: src/ngrok/client/controller.go
================================================
package client

import (
	"fmt"
	"ngrok/client/mvc"
	"ngrok/client/views/term"
	"ngrok/client/views/web"
	"ngrok/log"
	"ngrok/proto"
	"ngrok/util"
	"sync"
)

type command interface{}

type cmdQuit struct {
	// display this message after quit
	message string
}

type cmdPlayRequest struct {
	// the tunnel to play this request over
	tunnel mvc.Tunnel

	// the bytes of the request to issue
	payload []byte
}

// The MVC Controller
type Controller struct {
	// Controller logger
	log.Logger

	// the model sends updates through this broadcast channel
	updates *util.Broadcast

	// the model
	model mvc.Model

	// the views
	views []mvc.View

	// internal structure to issue commands to the controller
	cmds chan command

	// internal structure to synchronize access to State object
	state chan mvc.State

	// options
	config *Configuration
}

// public interface
func NewController() *Controller {
	ctl := &Controller{
		Logger:  log.NewPrefixLogger("controller"),
		updates: util.NewBroadcast(),
		cmds:    make(chan command),
		views:   make([]mvc.View, 0),
		state:   make(chan mvc.State),
	}

	return ctl
}

func (ctl *Controller) State() mvc.State {
	return <-ctl.state
}

func (ctl *Controller) Update(state mvc.State) {
	ctl.updates.In() <- state
}

func (ctl *Controller) Updates() *util.Broadcast {
	return ctl.updates
}

func (ctl *Controller) Shutdown(message string) {
	ctl.cmds <- cmdQuit{message: message}
}

func (ctl *Controller) PlayRequest(tunnel mvc.Tunnel, payload []byte) {
	ctl.cmds <- cmdPlayRequest{tunnel: tunnel, payload: payload}
}

func (ctl *Controller) Go(fn func()) {
	go func() {
		defer func() {
			if r := recover(); r != nil {
				err := util.MakePanicTrace(r)
				ctl.Error(err)
				ctl.Shutdown(err)
			}
		}()

		fn()
	}()
}

// private functions
func (ctl *Controller) doShutdown() {
	ctl.Info("Shutting down")

	var wg sync.WaitGroup

	// wait for all of the views, plus the model
	wg.Add(len(ctl.views) + 1)

	for _, v := range ctl.views {
		vClosure := v
		ctl.Go(func() {
			vClosure.Shutdown()
			wg.Done()
		})
	}

	ctl.Go(func() {
		ctl.model.Shutdown()
		wg.Done()
	})

	wg.Wait()
}

func (ctl *Controller) AddView(v mvc.View) {
	ctl.views = append(ctl.views, v)
}

func (ctl *Controller) GetWebInspectAddr() string {
	return ctl.config.InspectAddr
}

func (ctl *Controller) SetupModel(config *Configuration) *ClientModel {
	model := newClientModel(config, ctl)
	ctl.model = model
	return model
}

func (ctl *Controller) GetModel() *ClientModel {
	return ctl.model.(*ClientModel)
}

func (ctl *Controller) Run(config *Configuration) {
	// Save the configuration
	ctl.config = config

	var model *ClientModel

	if ctl.model == nil {
		model = ctl.SetupModel(config)
	} else {
		model = ctl.model.(*ClientModel)
	}

	// init the model
	var state mvc.State = model

	// init web ui
	var webView *web.WebView
	if config.InspectAddr != "disabled" {
		webView = web.NewWebView(ctl, config.InspectAddr)
		ctl.AddView(webView)
	}

	// init term ui
	var termView *term.TermView
	if config.LogTo != "stdout" {
		termView = term.NewTermView(ctl)
		ctl.AddView(termView)
	}

	for _, protocol := range model.GetProtocols() {
		switch p := protocol.(type) {
		case *proto.Http:
			if termView != nil {
				ctl.AddView(termView.NewHttpView(p))
			}

			if webView != nil {
				ctl.AddView(webView.NewHttpView(p))
			}
		default:
		}
	}

	ctl.Go(func() { autoUpdate(state, config.AuthToken) })
	ctl.Go(ctl.model.Run)

	updates := ctl.updates.Reg()
	defer ctl.updates.UnReg(updates)

	done := make(chan int)
	for {
		select {
		case obj := <-ctl.cmds:
			switch cmd := obj.(type) {
			case cmdQuit:
				msg := cmd.message
				go func() {
					ctl.doShutdown()
					fmt.Println(msg)
					done <- 1
				}()

			case cmdPlayRequest:
				ctl.Go(func() { ctl.model.PlayRequest(cmd.tunnel, cmd.payload) })
			}

		case obj := <-updates:
			state = obj.(mvc.State)

		case ctl.state <- state:
		case <-done:
			return
		}
	}
}


================================================
FILE: src/ngrok/client/debug.go
================================================
// +build !release

package client

var (
	rootCrtPaths = []string{"assets/client/tls/ngrokroot.crt", "assets/client/tls/snakeoilca.crt"}
)

func useInsecureSkipVerify() bool {
	return true
}


================================================
FILE: src/ngrok/client/main.go
================================================
package client

import (
	"fmt"
	"github.com/inconshreveable/mousetrap"
	"math/rand"
	"ngrok/log"
	"ngrok/util"
	"os"
	"runtime"
	"time"
)

func init() {
	if runtime.GOOS == "windows" {
		if mousetrap.StartedByExplorer() {
			fmt.Println("Don't double-click ngrok!")
			fmt.Println("You need to open cmd.exe and run it from the command line!")
			time.Sleep(5 * time.Second)
			os.Exit(1)
		}
	}
}

func Main() {
	// parse options
	opts, err := ParseArgs()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// set up logging
	log.LogTo(opts.logto, opts.loglevel)

	// read configuration file
	config, err := LoadConfiguration(opts)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// seed random number generator
	seed, err := util.RandomSeed()
	if err != nil {
		fmt.Printf("Couldn't securely seed the random number generator!")
		os.Exit(1)
	}
	rand.Seed(seed)

	NewController().Run(config)
}


================================================
FILE: src/ngrok/client/metrics.go
================================================
package client

import (
	metrics "github.com/rcrowley/go-metrics"
)

const (
	sampleSize  int     = 1028
	sampleAlpha float64 = 0.015
)

type ClientMetrics struct {
	// metrics
	connGauge       metrics.Gauge
	connMeter       metrics.Meter
	connTimer       metrics.Timer
	proxySetupTimer metrics.Timer
	bytesIn         metrics.Histogram
	bytesOut        metrics.Histogram
	bytesInCount    metrics.Counter
	bytesOutCount   metrics.Counter
}

func NewClientMetrics() *ClientMetrics {
	return &ClientMetrics{
		connGauge:       metrics.NewGauge(),
		connMeter:       metrics.NewMeter(),
		connTimer:       metrics.NewTimer(),
		proxySetupTimer: metrics.NewTimer(),
		bytesIn:         metrics.NewHistogram(metrics.NewExpDecaySample(sampleSize, sampleAlpha)),
		bytesOut:        metrics.NewHistogram(metrics.NewExpDecaySample(sampleSize, sampleAlpha)),
		bytesInCount:    metrics.NewCounter(),
		bytesOutCount:   metrics.NewCounter(),
	}
}


================================================
FILE: src/ngrok/client/model.go
================================================
package client

import (
	"crypto/tls"
	"fmt"
	metrics "github.com/rcrowley/go-metrics"
	"io/ioutil"
	"math"
	"net"
	"ngrok/client/mvc"
	"ngrok/conn"
	"ngrok/log"
	"ngrok/msg"
	"ngrok/proto"
	"ngrok/util"
	"ngrok/version"
	"runtime"
	"strings"
	"sync/atomic"
	"time"
)

const (
	defaultServerAddr   = "ngrokd.ngrok.com:443"
	defaultInspectAddr  = "127.0.0.1:4040"
	pingInterval        = 20 * time.Second
	maxPongLatency      = 15 * time.Second
	updateCheckInterval = 6 * time.Hour
	BadGateway          = `<html>
<body style="background-color: #97a8b9">
    <div style="margin:auto; width:400px;padding: 20px 60px; background-color: #D3D3D3; border: 5px solid maroon;">
        <h2>Tunnel %s unavailable</h2>
        <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>
`
)

type ClientModel struct {
	log.Logger

	id            string
	tunnels       map[string]mvc.Tunnel
	serverVersion string
	metrics       *ClientMetrics
	updateStatus  mvc.UpdateStatus
	connStatus    mvc.ConnStatus
	protoMap      map[string]proto.Protocol
	protocols     []proto.Protocol
	ctl           mvc.Controller
	serverAddr    string
	proxyUrl      string
	authToken     string
	tlsConfig     *tls.Config
	tunnelConfig  map[string]*TunnelConfiguration
	configPath    string
}

func newClientModel(config *Configuration, ctl mvc.Controller) *ClientModel {
	protoMap := make(map[string]proto.Protocol)
	protoMap["http"] = proto.NewHttp()
	protoMap["https"] = protoMap["http"]
	protoMap["tcp"] = proto.NewTcp()
	protocols := []proto.Protocol{protoMap["http"], protoMap["tcp"]}

	m := &ClientModel{
		Logger: log.NewPrefixLogger("client"),

		// server address
		serverAddr: config.ServerAddr,

		// proxy address
		proxyUrl: config.HttpProxy,

		// auth token
		authToken: config.AuthToken,

		// connection status
		connStatus: mvc.ConnConnecting,

		// update status
		updateStatus: mvc.UpdateNone,

		// metrics
		metrics: NewClientMetrics(),

		// protocols
		protoMap: protoMap,

		// protocol list
		protocols: protocols,

		// open tunnels
		tunnels: make(map[string]mvc.Tunnel),

		// controller
		ctl: ctl,

		// tunnel configuration
		tunnelConfig: config.Tunnels,

		// config path
		configPath: config.Path,
	}

	// configure TLS
	if config.TrustHostRootCerts {
		m.Info("Trusting host's root certificates")
		m.tlsConfig = &tls.Config{}
	} else {
		m.Info("Trusting root CAs: %v", rootCrtPaths)
		var err error
		if m.tlsConfig, err = LoadTLSConfig(rootCrtPaths); err != nil {
			panic(err)
		}
	}

	// configure TLS SNI
	m.tlsConfig.ServerName = serverName(m.serverAddr)
	m.tlsConfig.InsecureSkipVerify = useInsecureSkipVerify()

	return m
}

// server name in release builds is the host part of the server address
func serverName(addr string) string {
	host, _, err := net.SplitHostPort(addr)

	// should never panic because the config parser calls SplitHostPort first
	if err != nil {
		panic(err)
	}

	return host
}

// mvc.State interface
func (c ClientModel) GetProtocols() []proto.Protocol { return c.protocols }
func (c ClientModel) GetClientVersion() string       { return version.MajorMinor() }
func (c ClientModel) GetServerVersion() string       { return c.serverVersion }
func (c ClientModel) GetTunnels() []mvc.Tunnel {
	tunnels := make([]mvc.Tunnel, 0)
	for _, t := range c.tunnels {
		tunnels = append(tunnels, t)
	}
	return tunnels
}
func (c ClientModel) GetConnStatus() mvc.ConnStatus     { return c.connStatus }
func (c ClientModel) GetUpdateStatus() mvc.UpdateStatus { return c.updateStatus }

func (c ClientModel) GetConnectionMetrics() (metrics.Meter, metrics.Timer) {
	return c.metrics.connMeter, c.metrics.connTimer
}

func (c ClientModel) GetBytesInMetrics() (metrics.Counter, metrics.Histogram) {
	return c.metrics.bytesInCount, c.metrics.bytesIn
}

func (c ClientModel) GetBytesOutMetrics() (metrics.Counter, metrics.Histogram) {
	return c.metrics.bytesOutCount, c.metrics.bytesOut
}
func (c ClientModel) SetUpdateStatus(updateStatus mvc.UpdateStatus) {
	c.updateStatus = updateStatus
	c.update()
}

// mvc.Model interface
func (c *ClientModel) PlayRequest(tunnel mvc.Tunnel, payload []byte) {
	var localConn conn.Conn
	localConn, err := conn.Dial(tunnel.LocalAddr, "prv", nil)
	if err != nil {
		c.Warn("Failed to open private leg to %s: %v", tunnel.LocalAddr, err)
		return
	}

	defer localConn.Close()
	localConn = tunnel.Protocol.WrapConn(localConn, mvc.ConnectionContext{Tunnel: tunnel, ClientAddr: "127.0.0.1"})
	localConn.Write(payload)
	ioutil.ReadAll(localConn)
}

func (c *ClientModel) Shutdown() {
}

func (c *ClientModel) update() {
	c.ctl.Update(c)
}

func (c *ClientModel) Run() {
	// how long we should wait before we reconnect
	maxWait := 30 * time.Second
	wait := 1 * time.Second

	for {
		// run the control channel
		c.control()

		// control only returns when a failure has occurred, so we're going to try to reconnect
		if c.connStatus == mvc.ConnOnline {
			wait = 1 * time.Second
		}

		log.Info("Waiting %d seconds before reconnecting", int(wait.Seconds()))
		time.Sleep(wait)
		// exponentially increase wait time
		wait = 2 * wait
		wait = time.Duration(math.Min(float64(wait), float64(maxWait)))
		c.connStatus = mvc.ConnReconnecting
		c.update()
	}
}

// Establishes and manages a tunnel control connection with the server
func (c *ClientModel) control() {
	defer func() {
		if r := recover(); r != nil {
			log.Error("control recovering from failure %v", r)
		}
	}()

	// establish control channel
	var (
		ctlConn conn.Conn
		err     error
	)
	if c.proxyUrl == "" {
		// simple non-proxied case, just connect to the server
		ctlConn, err = conn.Dial(c.serverAddr, "ctl", c.tlsConfig)
	} else {
		ctlConn, err = conn.DialHttpProxy(c.proxyUrl, c.serverAddr, "ctl", c.tlsConfig)
	}
	if err != nil {
		panic(err)
	}
	defer ctlConn.Close()

	// authenticate with the server
	auth := &msg.Auth{
		ClientId:  c.id,
		OS:        runtime.GOOS,
		Arch:      runtime.GOARCH,
		Version:   version.Proto,
		MmVersion: version.MajorMinor(),
		User:      c.authToken,
	}

	if err = msg.WriteMsg(ctlConn, auth); err != nil {
		panic(err)
	}

	// wait for the server to authenticate us
	var authResp msg.AuthResp
	if err = msg.ReadMsgInto(ctlConn, &authResp); err != nil {
		panic(err)
	}

	if authResp.Error != "" {
		emsg := fmt.Sprintf("Failed to authenticate to server: %s", authResp.Error)
		c.ctl.Shutdown(emsg)
		return
	}

	c.id = authResp.ClientId
	c.serverVersion = authResp.MmVersion
	c.Info("Authenticated with server, client id: %v", c.id)
	c.update()
	if err = SaveAuthToken(c.configPath, c.authToken); err != nil {
		c.Error("Failed to save auth token: %v", err)
	}

	// request tunnels
	reqIdToTunnelConfig := make(map[string]*TunnelConfiguration)
	for _, config := range c.tunnelConfig {
		// create the protocol list to ask for
		var protocols []string
		for proto, _ := range config.Protocols {
			protocols = append(protocols, proto)
		}

		reqTunnel := &msg.ReqTunnel{
			ReqId:      util.RandId(8),
			Protocol:   strings.Join(protocols, "+"),
			Hostname:   config.Hostname,
			Subdomain:  config.Subdomain,
			HttpAuth:   config.HttpAuth,
			RemotePort: config.RemotePort,
		}

		// send the tunnel request
		if err = msg.WriteMsg(ctlConn, reqTunnel); err != nil {
			panic(err)
		}

		// save request id association so we know which local address
		// to proxy to later
		reqIdToTunnelConfig[reqTunnel.ReqId] = config
	}

	// start the heartbeat
	lastPong := time.Now().UnixNano()
	c.ctl.Go(func() { c.heartbeat(&lastPong, ctlConn) })

	// main control loop
	for {
		var rawMsg msg.Message
		if rawMsg, err = msg.ReadMsg(ctlConn); err != nil {
			panic(err)
		}

		switch m := rawMsg.(type) {
		case *msg.ReqProxy:
			c.ctl.Go(c.proxy)

		case *msg.Pong:
			atomic.StoreInt64(&lastPong, time.Now().UnixNano())

		case *msg.NewTunnel:
			if m.Error != "" {
				emsg := fmt.Sprintf("Server failed to allocate tunnel: %s", m.Error)
				c.Error(emsg)
				c.ctl.Shutdown(emsg)
				continue
			}

			tunnel := mvc.Tunnel{
				PublicUrl: m.Url,
				LocalAddr: reqIdToTunnelConfig[m.ReqId].Protocols[m.Protocol],
				Protocol:  c.protoMap[m.Protocol],
			}

			c.tunnels[tunnel.PublicUrl] = tunnel
			c.connStatus = mvc.ConnOnline
			c.Info("Tunnel established at %v", tunnel.PublicUrl)
			c.update()

		default:
			ctlConn.Warn("Ignoring unknown control message %v ", m)
		}
	}
}

// Establishes and manages a tunnel proxy connection with the server
func (c *ClientModel) proxy() {
	var (
		remoteConn conn.Conn
		err        error
	)

	if c.proxyUrl == "" {
		remoteConn, err = conn.Dial(c.serverAddr, "pxy", c.tlsConfig)
	} else {
		remoteConn, err = conn.DialHttpProxy(c.proxyUrl, c.serverAddr, "pxy", c.tlsConfig)
	}

	if err != nil {
		log.Error("Failed to establish proxy connection: %v", err)
		return
	}
	defer remoteConn.Close()

	err = msg.WriteMsg(remoteConn, &msg.RegProxy{ClientId: c.id})
	if err != nil {
		remoteConn.Error("Failed to write RegProxy: %v", err)
		return
	}

	// wait for the server to ack our register
	var startPxy msg.StartProxy
	if err = msg.ReadMsgInto(remoteConn, &startPxy); err != nil {
		remoteConn.Error("Server failed to write StartProxy: %v", err)
		return
	}

	tunnel, ok := c.tunnels[startPxy.Url]
	if !ok {
		remoteConn.Error("Couldn't find tunnel for proxy: %s", startPxy.Url)
		return
	}

	// start up the private connection
	start := time.Now()
	localConn, err := conn.Dial(tunnel.LocalAddr, "prv", nil)
	if err != nil {
		remoteConn.Warn("Failed to open private leg %s: %v", tunnel.LocalAddr, err)

		if tunnel.Protocol.GetName() == "http" {
			// try to be helpful when you're in HTTP mode and a human might see the output
			badGatewayBody := fmt.Sprintf(BadGateway, tunnel.PublicUrl, tunnel.LocalAddr, tunnel.LocalAddr)
			remoteConn.Write([]byte(fmt.Sprintf(`HTTP/1.0 502 Bad Gateway
Content-Type: text/html
Content-Length: %d

%s`, len(badGatewayBody), badGatewayBody)))
		}
		return
	}
	defer localConn.Close()

	m := c.metrics
	m.proxySetupTimer.Update(time.Since(start))
	m.connMeter.Mark(1)
	c.update()
	m.connTimer.Time(func() {
		localConn := tunnel.Protocol.WrapConn(localConn, mvc.ConnectionContext{Tunnel: tunnel, ClientAddr: startPxy.ClientAddr})
		bytesIn, bytesOut := conn.Join(localConn, remoteConn)
		m.bytesIn.Update(bytesIn)
		m.bytesOut.Update(bytesOut)
		m.bytesInCount.Inc(bytesIn)
		m.bytesOutCount.Inc(bytesOut)
	})
	c.update()
}

// Hearbeating to ensure our connection ngrokd is still live
func (c *ClientModel) heartbeat(lastPongAddr *int64, conn conn.Conn) {
	lastPing := time.Unix(atomic.LoadInt64(lastPongAddr)-1, 0)
	ping := time.NewTicker(pingInterval)
	pongCheck := time.NewTicker(time.Second)

	defer func() {
		conn.Close()
		ping.Stop()
		pongCheck.Stop()
	}()

	for {
		select {
		case <-pongCheck.C:
			lastPong := time.Unix(0, atomic.LoadInt64(lastPongAddr))
			needPong := lastPong.Sub(lastPing) < 0
			pongLatency := time.Since(lastPing)

			if needPong && pongLatency > maxPongLatency {
				c.Info("Last ping: %v, Last pong: %v", lastPing, lastPong)
				c.Info("Connection stale, haven't gotten PongMsg in %d seconds", int(pongLatency.Seconds()))
				return
			}

		case <-ping.C:
			err := msg.WriteMsg(conn, &msg.Ping{})
			if err != nil {
				conn.Debug("Got error %v when writing PingMsg", err)
				return
			}
			lastPing = time.Now()
		}
	}
}


================================================
FILE: src/ngrok/client/mvc/controller.go
================================================
package mvc

import (
	"ngrok/util"
)

type Controller interface {
	// how the model communicates that it has changed state
	Update(State)

	// instructs the controller to shut the app down
	Shutdown(message string)

	// PlayRequest instructs the model to play requests
	PlayRequest(tunnel Tunnel, payload []byte)

	// A channel of updates
	Updates() *util.Broadcast

	// returns the current state
	State() State

	// safe wrapper for running go-routines
	Go(fn func())

	// the address where the web inspection interface is running
	GetWebInspectAddr() string
}


================================================
FILE: src/ngrok/client/mvc/model.go
================================================
package mvc

type Model interface {
	Run()

	Shutdown()

	PlayRequest(tunnel Tunnel, payload []byte)
}


================================================
FILE: src/ngrok/client/mvc/state.go
================================================
package mvc

import (
	metrics "github.com/rcrowley/go-metrics"
	"ngrok/proto"
)

type UpdateStatus int

const (
	UpdateNone = -1 * iota
	UpdateInstalling
	UpdateReady
	UpdateAvailable
)

type ConnStatus int

const (
	ConnConnecting = iota
	ConnReconnecting
	ConnOnline
)

type Tunnel struct {
	PublicUrl string
	Protocol  proto.Protocol
	LocalAddr string
}

type ConnectionContext struct {
	Tunnel     Tunnel
	ClientAddr string
}

type State interface {
	GetClientVersion() string
	GetServerVersion() string
	GetTunnels() []Tunnel
	GetProtocols() []proto.Protocol
	GetUpdateStatus() UpdateStatus
	GetConnStatus() ConnStatus
	GetConnectionMetrics() (metrics.Meter, metrics.Timer)
	GetBytesInMetrics() (metrics.Counter, metrics.Histogram)
	GetBytesOutMetrics() (metrics.Counter, metrics.Histogram)
	SetUpdateStatus(UpdateStatus)
}


================================================
FILE: src/ngrok/client/mvc/view.go
================================================
package mvc

type View interface {
	Shutdown()
}


================================================
FILE: src/ngrok/client/release.go
================================================
// +build release

package client

var (
	rootCrtPaths = []string{"assets/client/tls/ngrokroot.crt"}
)

func useInsecureSkipVerify() bool {
	return false
}


================================================
FILE: src/ngrok/client/tls.go
================================================
package client

import (
	_ "crypto/sha512"
	"crypto/tls"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"ngrok/client/assets"
)

func LoadTLSConfig(rootCertPaths []string) (*tls.Config, error) {
	pool := x509.NewCertPool()

	for _, certPath := range rootCertPaths {
		rootCrt, err := assets.Asset(certPath)
		if err != nil {
			return nil, err
		}

		pemBlock, _ := pem.Decode(rootCrt)
		if pemBlock == nil {
			return nil, fmt.Errorf("Bad PEM data")
		}

		certs, err := x509.ParseCertificates(pemBlock.Bytes)
		if err != nil {
			return nil, err
		}

		pool.AddCert(certs[0])
	}

	return &tls.Config{RootCAs: pool}, nil
}


================================================
FILE: src/ngrok/client/update_debug.go
================================================
// +build !release,!autoupdate

package client

import (
	"ngrok/client/mvc"
)

// no auto-updating in debug mode
func autoUpdate(state mvc.State, token string) {
}


================================================
FILE: src/ngrok/client/update_release.go
================================================
// +build release autoupdate

package client

import (
	"ngrok/client/mvc"
	"ngrok/log"
	"ngrok/version"
	"time"

	"gopkg.in/inconshreveable/go-update.v0"
	"gopkg.in/inconshreveable/go-update.v0/check"
)

const (
	appId          = "ap_pJSFC5wQYkAy
Download .txt
gitextract_7xnxnl94/

├── .gitignore
├── .travis.yml
├── CONTRIBUTORS
├── LICENSE
├── Makefile
├── README.md
├── assets/
│   ├── client/
│   │   ├── page.html
│   │   ├── static/
│   │   │   └── js/
│   │   │       ├── angular.js
│   │   │       ├── base64.js
│   │   │       ├── jquery.timeago.js
│   │   │       ├── ngrok.js
│   │   │       └── vkbeautify.js
│   │   └── tls/
│   │       ├── ngrokroot.crt
│   │       └── snakeoilca.crt
│   └── server/
│       └── tls/
│           ├── snakeoil.crt
│           └── snakeoil.key
├── contrib/
│   └── com.ngrok.client.plist
├── docs/
│   ├── CHANGELOG.md
│   ├── DEVELOPMENT.md
│   └── SELFHOSTING.md
└── src/
    └── ngrok/
        ├── cache/
        │   └── lru.go
        ├── client/
        │   ├── cli.go
        │   ├── config.go
        │   ├── controller.go
        │   ├── debug.go
        │   ├── main.go
        │   ├── metrics.go
        │   ├── model.go
        │   ├── mvc/
        │   │   ├── controller.go
        │   │   ├── model.go
        │   │   ├── state.go
        │   │   └── view.go
        │   ├── release.go
        │   ├── tls.go
        │   ├── update_debug.go
        │   ├── update_release.go
        │   └── views/
        │       ├── term/
        │       │   ├── area.go
        │       │   ├── http.go
        │       │   └── view.go
        │       └── web/
        │           ├── http.go
        │           └── view.go
        ├── conn/
        │   ├── conn.go
        │   └── tee.go
        ├── log/
        │   └── logger.go
        ├── main/
        │   ├── ngrok/
        │   │   └── ngrok.go
        │   └── ngrokd/
        │       └── ngrokd.go
        ├── msg/
        │   ├── conn.go
        │   ├── msg.go
        │   └── pack.go
        ├── proto/
        │   ├── http.go
        │   ├── interface.go
        │   └── tcp.go
        ├── server/
        │   ├── cli.go
        │   ├── control.go
        │   ├── http.go
        │   ├── main.go
        │   ├── metrics.go
        │   ├── registry.go
        │   ├── tls.go
        │   └── tunnel.go
        ├── util/
        │   ├── broadcast.go
        │   ├── errors.go
        │   ├── id.go
        │   ├── ring.go
        │   └── shutdown.go
        └── version/
            └── version.go
Download .txt
SYMBOL INDEX (489 symbols across 51 files)

FILE: assets/client/static/js/angular.js
  function lc (line 6) | function lc(){var b=M.angular;M.angular=mc;return b}
  function Xa (line 6) | function Xa(b){return!b||typeof b.length!=="number"?!1:typeof b.hasOwnPr...
  function n (line 6) | function n(b,a,c){var d;if(b)if(H(b))for(d in b)d!="prototype"&&d!="leng...
  function qb (line 7) | function qb(b){var a=[],c;for(c in b)b.hasOwnProperty(c)&&a.push(c);retu...
  function nc (line 7) | function nc(b,a,c){for(var d=qb(b),e=0;e<d.length;e++)a.call(c,b[d[e]],d...
  function rb (line 7) | function rb(b){return function(a,c){b(c,a)}}
  function Fa (line 7) | function Fa(){for(var b=ba.length,a;b;){b--;a=ba[b].charCodeAt(0);if(a==...
  function sb (line 8) | function sb(b,a){a?b.$$hashKey=a:delete b.$$hashKey}
  function t (line 8) | function t(b){var a=b.$$hashKey;n(arguments,function(a){a!==b&&n(a,funct...
  function N (line 8) | function N(b){return parseInt(b,10)}
  function tb (line 8) | function tb(b,a){return t(new (t(function(){},{prototype:b})),a)}
  function q (line 8) | function q(){}
  function qa (line 8) | function qa(b){return b}
  function S (line 8) | function S(b){return function(){return b}}
  function C (line 8) | function C(b){return typeof b=="undefined"}
  function B (line 8) | function B(b){return typeof b!="undefined"}
  function L (line 8) | function L(b){return b!=null&&typeof b=="object"}
  function E (line 8) | function E(b){return typeof b==
  function Ya (line 9) | function Ya(b){return typeof b=="number"}
  function ra (line 9) | function ra(b){return Ea.apply(b)=="[object Date]"}
  function F (line 9) | function F(b){return Ea.apply(b)=="[object Array]"}
  function H (line 9) | function H(b){return typeof b=="function"}
  function sa (line 9) | function sa(b){return b&&b.document&&b.location&&b.alert&&b.setInterval}
  function U (line 9) | function U(b){return E(b)?b.replace(/^\s*/,"").replace(/\s*$/,""):b}
  function oc (line 9) | function oc(b){return b&&(b.nodeName||b.bind&&b.find)}
  function Za (line 9) | function Za(b,a,c){var d=[];n(b,function(b,g,i){d.push(a.call(c,b,g,i))}...
  function Ga (line 9) | function Ga(b,a){if(b.indexOf)return b.indexOf(a);
  function ta (line 10) | function ta(b,a){var c=Ga(b,a);c>=0&&b.splice(c,1);return a}
  function V (line 10) | function V(b,a){if(sa(b)||b&&b.$evalAsync&&b.$watch)throw Error("Can't c...
  function pc (line 11) | function pc(b,a){var a=a||{},c;for(c in b)b.hasOwnProperty(c)&&c.substr(...
  function ia (line 11) | function ia(b,a){if(b===a)return!0;if(b===null||a===null)return!1;if(b!=...
  function $a (line 12) | function $a(b,a){var c=arguments.length>2?ka.call(arguments,2):[];return...
  function qc (line 12) | function qc(b,a){var c=a;/^\$+/.test(b)?c=p:sa(a)?c="$WINDOW":a&&T===a?c...
  function ha (line 13) | function ha(b,a){return JSON.stringify(b,qc,a?"  ":null)}
  function ub (line 13) | function ub(b){return E(b)?JSON.parse(b):b}
  function ua (line 13) | function ua(b){b&&b.length!==0?(b=I(""+b),b=!(b=="f"||b=="0"||b=="false"...
  function va (line 13) | function va(b){b=w(b).clone();try{b.html("")}catch(a){}var c=w("<div>")....
  function vb (line 13) | function vb(b){var a={},c,d;n((b||
  function wb (line 14) | function wb(b){var a=[];n(b,function(b,d){a.push(wa(d,!0)+(b===!0?"":"="...
  function ab (line 14) | function ab(b){return wa(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=")...
  function wa (line 14) | function wa(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").repla...
  function rc (line 14) | function rc(b,
  function xb (line 16) | function xb(b,a){var c=function(){b=w(b);a=a||[];a.unshift(["$provide",f...
  function bb (line 16) | function bb(b,a){a=a||"_";return b.replace(sc,
  function cb (line 17) | function cb(b,a,c){if(!b)throw Error("Argument '"+(a||"?")+"' is "+(c||"...
  function xa (line 17) | function xa(b,a,c){c&&F(b)&&(b=b[b.length-1]);cb(H(b),a,"not a function,...
  function tc (line 17) | function tc(b){function a(a,b,e){return a[b]||(a[b]=e())}return a(a(b,"a...
  function Ia (line 19) | function Ia(b){return b.replace(uc,function(a,b,d,e){return e?d.toUpperC...
  function db (line 19) | function db(b,a){function c(){var e;for(var b=[this],c=a,i,f,h,j,m,k;b.l...
  function R (line 20) | function R(b){if(b instanceof R)return b;if(!(this instanceof R)){if(E(b...
  function fb (line 20) | function fb(b){return b.cloneNode(!0)}
  function ya (line 20) | function ya(b){zb(b);for(var a=0,b=b.childNodes||[];a<b.length;a++)ya(b[...
  function Ab (line 20) | function Ab(b,a,c){var d=ca(b,"events");ca(b,"handle")&&(C(a)?n(d,functi...
  function zb (line 21) | function zb(b){var a=b[Ja],c=Ka[a];c&&(c.handle&&(c.events.$destroy&&c.h...
  function ca (line 21) | function ca(b,a,c){var d=b[Ja],d=Ka[d||-1];if(B(c))d||(b[Ja]=d=++wc,d=Ka...
  function Bb (line 21) | function Bb(b,a,c){var d=ca(b,"data"),e=B(c),g=!e&&B(a),i=g&&!L(a);!d&&!...
  function La (line 21) | function La(b,a){return(" "+b.className+" ").replace(/[\n\t]/g,
  function Cb (line 22) | function Cb(b,a){a&&n(a.split(" "),function(a){b.className=U((" "+b.clas...
  function Db (line 22) | function Db(b,a){a&&n(a.split(" "),function(a){if(!La(b,a))b.className=U...
  function eb (line 22) | function eb(b,a){if(a)for(var a=!a.nodeName&&B(a.length)&&!sa(a)?a:[a],c...
  function Eb (line 22) | function Eb(b,a){return Ma(b,"$"+(a||"ngController")+"Controller")}
  function Ma (line 22) | function Ma(b,a,c){b=w(b);for(b[0].nodeType==9&&(b=b.find("html"));b.len...
  function Fb (line 23) | function Fb(b,a){var c=Na[a.toLowerCase()];return c&&Gb[b.nodeName]&&c}
  function xc (line 23) | function xc(b,a){var c=function(c,e){if(!c.preventDefault)c.preventDefau...
  function la (line 24) | function la(b){var a=typeof b,c;if(a=="object"&&b!==null)if(typeof(c=b.$...
  function za (line 24) | function za(b){n(b,this.put,this)}
  function Hb (line 24) | function Hb(b){var a,c;if(typeof b=="function"){if(!(a=b.$inject))a=
  function yb (line 25) | function yb(b){function a(a){return function(b,c){if(L(b))n(b,rb(a));els...
  function Cc (line 30) | function Cc(){var b=!0;this.disableAutoScrolling=function(){b=!1};this.$...
  function Ib (line 31) | function Ib(b){this.register=function(a,c){b.factory(Ia(a)+"Animation",c...
  function Dc (line 31) | function Dc(b,a,c,d){function e(a){try{a.apply(null,ka.call(arguments,1)...
  function Ec (line 34) | function Ec(){this.$get=
  function Fc (line 35) | function Fc(){this.$get=function(){function b(b,d){function e(a){if(a!=k...
  function Gc (line 36) | function Gc(){this.$get=["$cacheFactory",function(b){return b("templates...
  function Jb (line 36) | function Jb(b){var a=
  function da (line 53) | function da(b){return Ia(b.replace(Ic,""))}
  function Jc (line 53) | function Jc(){var b={},a=/^(\S+)(\s+as\s+(\w+))?$/;this.register=functio...
  function Kc (line 53) | function Kc(){this.$get=
  function Lc (line 54) | function Lc(){this.$get=["$log",function(b){return function(a,c){b.error...
  function Mc (line 54) | function Mc(){var b="{{",a="}}";this.startSymbol=function(a){return a?(b...
  function Mb (line 55) | function Mb(b){for(var b=b.split("/"),
  function Nb (line 56) | function Nb(b,a){var c=jb.exec(b);a.$$protocol=c[1];a.$$host=c[3];a.$$po...
  function Ob (line 56) | function Ob(b,a){var c=Pb.exec(b);a.$$path=decodeURIComponent(c[1]);a.$$...
  function fa (line 56) | function fa(b,a,c){return a.indexOf(b)==0?a.substr(b.length):c}
  function Ca (line 56) | function Ca(b){var a=b.indexOf("#");return a==-1?b:b.substr(0,a)}
  function kb (line 56) | function kb(b){return b.substr(0,Ca(b).lastIndexOf("/")+
  function Qb (line 57) | function Qb(b,a){var a=a||"",c=kb(b);this.$$parse=function(a){var b={};N...
  function lb (line 58) | function lb(b,a){var c=kb(b);this.$$parse=function(d){Nb(d,this);var e=f...
  function Rb (line 59) | function Rb(b,a){lb.apply(this,arguments);var c=kb(b);this.$$rewrite=fun...
  function Pa (line 59) | function Pa(b){return function(){return this[b]}}
  function Sb (line 59) | function Sb(b,a){return function(c){if(C(c))return this[b];this[b]=a(c);...
  function Nc (line 59) | function Nc(){var b="",a=!1;this.hashPrefix=function(a){return B(a)?(b=a...
  function Oc (line 62) | function Oc(){var b=!0,a=this;this.debugEnabled=function(a){return B(a)?...
  function Pc (line 63) | function Pc(b,a){function c(a){return a.indexOf(r)!=-1}function d(a){a=a...
  function Rc (line 68) | function Rc(b,a,c,d){function e(a,c){throw Error("Syntax Error: Token '"...
  function Ub (line 75) | function Ub(b,a,c){for(var a=a.split("."),d=0;a.length>1;d++){var e=a.sh...
  function ib (line 75) | function ib(b,a,c){if(!a)return b;for(var a=a.split("."),d,e=b,g=a.lengt...
  function Vb (line 76) | function Vb(b,a,c,d,e){return function(g,i){var f=i&&i.hasOwnProperty(b)...
  function Tb (line 77) | function Tb(b,a){if(mb.hasOwnProperty(b))return mb[b];var c=b.split(".")...
  function Sc (line 78) | function Sc(){var b={};this.$get=["$filter","$sniffer",function(a,c){ret...
  function Tc (line 78) | function Tc(){this.$get=
  function Uc (line 79) | function Uc(b,a){function c(a){return a}function d(a){return i(a)}var e=...
  function Vc (line 82) | function Vc(){var b={};this.when=function(a,c){b[a]=t({reloadOnSearch:!0...
  function Wc (line 86) | function Wc(){this.$get=S({})}
  function Xc (line 86) | function Xc(){var b=10;this.digestTtl=function(a){arguments.length&&(b=a...
  function Yc (line 94) | function Yc(){this.$get=["$window","$document",function(b,a){var c=
  function Zc (line 96) | function Zc(){this.$get=S(M)}
  function Wb (line 96) | function Wb(b){var a={},c,d,e;if(!b)return a;n(b.split("\n"),function(b)...
  function $c (line 96) | function $c(b,a){var c=ad.exec(b);if(c==null)return!0;var d={protocol:c[...
  function Xb (line 97) | function Xb(b){var a=L(b)?b:p;return function(c){a||(a=Wb(b));return c?a...
  function Yb (line 97) | function Yb(b,a,c){if(H(c))return c(b,a);n(c,function(c){b=c(b,a)});retu...
  function bd (line 97) | function bd(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d...
  function cd (line 103) | function cd(){this.$get=["$browser","$window","$document",function(b,a,c...
  function dd (line 104) | function dd(b,a,c,d,e,g){function i(a,b){var c=e.createElement("script")...
  function fd (line 106) | function fd(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DEC...
  function gd (line 108) | function gd(){this.$get=["$rootScope","$browser","$q","$exceptionHandler...
  function Zb (line 109) | function Zb(b){function a(a,e){return b.factory(a+c,e)}var c="Filter";th...
  function hd (line 109) | function hd(){return function(b,a,c){if(!F(b))return b;var d=[];d.check=...
  function $b (line 111) | function $b(b){var a=b.NUMBER_FORMATS;return function(b,
  function bc (line 112) | function bc(b){var a=b.NUMBER_FORMATS;return function(b,d){return dc(b,a...
  function dc (line 112) | function dc(b,a,c,d,e){if(isNaN(b)||!isFinite(b))return"";var g=b<0,b=Ma...
  function nb (line 113) | function nb(b,a,c){var d="";b<0&&(d="-",b=-b);for(b=""+b;b.length<a;)b="...
  function Q (line 114) | function Q(b,a,c,d){c=c||0;return function(e){e=e["get"+b]();if(c>0||e>-...
  function Qa (line 114) | function Qa(b,a){return function(c,d){var e=c["get"+b](),g=oa(a?"SHORT"+...
  function ac (line 114) | function ac(b){function a(a){var b;if(b=a.match(c)){var a=new Date(0),g=...
  function id (line 116) | function id(){return function(b){return ha(b,!0)}}
  function jd (line 116) | function jd(){return function(b,a){if(!F(b)&&!E(b))return b;a=N(a);if(E(...
  function cc (line 116) | function cc(b){return function(a,c,d){function e(a,b){return ua(b)?funct...
  function aa (line 117) | function aa(b){H(b)&&(b={link:b});b.restrict=b.restrict||
  function fc (line 118) | function fc(b,a){function c(a,c){c=c?"-"+bb(c,"-"):"";b.removeClass((a?R...
  function X (line 120) | function X(b){return C(b)||b===""||b===null||b!==b}
  function Va (line 120) | function Va(b,a,c,d,e,g){var i=function(){var e=a.val();if(ua(c.ngTrim||...
  function ob (line 122) | function ob(b,a){b="ngClass"+b;return aa(function(c,d,e){function g(b){i...
  function a (line 125) | function a(){c||(c=!0,b())}
  function j (line 134) | function j(j,k,o){return function(m,r,p){function x(a){var c=0,a=E(a)?a....
  function m (line 136) | function m(a,c,d){d?d.after(a):c.append(a)}
  function i (line 149) | function i(a,c){c=c?"-"+bb(c,"-"):"";
  function k (line 165) | function k(){var f=d.current&&d.current.locals,k=f&&f.$template;if(k){o....
  function j (line 168) | function j(a,c,d,e){d.$render=function(){var a=d.$viewValue;e.hasOption(...
  function m (line 168) | function m(a,c,d){var e;d.$render=function(){var a=new za(d.$viewValue);...
  function k (line 169) | function k(e,f,g){function i(){var a={"":[]},c=[""],d,h,q,v,s;q=g.$model...

FILE: assets/client/static/js/base64.js
  function StringBuffer (line 25) | function StringBuffer()
  function Utf8EncodeEnumerator (line 122) | function Utf8EncodeEnumerator(input)
  function Base64DecodeEnumerator (line 178) | function Base64DecodeEnumerator(input)

FILE: assets/client/static/js/jquery.timeago.js
  function substitute (line 82) | function substitute(stringOrFunction, number) {
  function refresh (line 156) | function refresh() {
  function prepareData (line 168) | function prepareData(element) {
  function inWords (line 182) | function inWords(date) {
  function distance (line 186) | function distance(date) {

FILE: assets/client/static/js/ngrok.js
  function textNodes (line 272) | function textNodes(node) {

FILE: assets/client/static/js/vkbeautify.js
  function createShiftArr (line 50) | function createShiftArr(step) {
  function vkbeautify (line 80) | function vkbeautify(){
  function isSubquery (line 201) | function isSubquery(str, parenthesisLevel) {
  function split_sql (line 205) | function split_sql(str, tab) {

FILE: src/ngrok/cache/lru.go
  type LRUCache (line 22) | type LRUCache struct
    method Get (line 62) | func (lru *LRUCache) Get(key string) (v Value, ok bool) {
    method Set (line 74) | func (lru *LRUCache) Set(key string, value Value) {
    method SetIfAbsent (line 85) | func (lru *LRUCache) SetIfAbsent(key string, value Value) {
    method Delete (line 96) | func (lru *LRUCache) Delete(key string) bool {
    method Clear (line 111) | func (lru *LRUCache) Clear() {
    method SetCapacity (line 120) | func (lru *LRUCache) SetCapacity(capacity uint64) {
    method Stats (line 128) | func (lru *LRUCache) Stats() (length, size, capacity uint64, oldest ti...
    method StatsJSON (line 137) | func (lru *LRUCache) StatsJSON() string {
    method Keys (line 145) | func (lru *LRUCache) Keys() []string {
    method Items (line 156) | func (lru *LRUCache) Items() []Item {
    method SaveItems (line 168) | func (lru *LRUCache) SaveItems(w io.Writer) error {
    method SaveItemsToFile (line 174) | func (lru *LRUCache) SaveItemsToFile(path string) error {
    method LoadItems (line 183) | func (lru *LRUCache) LoadItems(r io.Reader) error {
    method LoadItemsFromFile (line 204) | func (lru *LRUCache) LoadItemsFromFile(path string) error {
    method updateInplace (line 213) | func (lru *LRUCache) updateInplace(element *list.Element, value Value) {
    method moveToFront (line 223) | func (lru *LRUCache) moveToFront(element *list.Element) {
    method addNew (line 228) | func (lru *LRUCache) addNew(key string, value Value) {
    method checkCapacity (line 236) | func (lru *LRUCache) checkCapacity() {
  type Value (line 38) | type Value interface
  type Item (line 42) | type Item struct
  type entry (line 47) | type entry struct
  function NewLRUCache (line 54) | func NewLRUCache(capacity uint64) *LRUCache {

FILE: src/ngrok/client/cli.go
  constant usage1 (line 10) | usage1 string = `Usage: %s [OPTIONS] <local port or address>
  constant usage2 (line 14) | usage2 string = `
  type Options (line 38) | type Options struct
  function ParseArgs (line 51) | func ParseArgs() (opts *Options, err error) {

FILE: src/ngrok/client/config.go
  type Configuration (line 18) | type Configuration struct
  type TunnelConfiguration (line 29) | type TunnelConfiguration struct
  function LoadConfiguration (line 37) | func LoadConfiguration(opts *Options) (config *Configuration, err error) {
  function defaultPath (line 204) | func defaultPath() string {
  function normalizeAddress (line 219) | func normalizeAddress(addr string, propName string) (string, error) {
  function validateProtocol (line 237) | func validateProtocol(proto, propName string) (err error) {
  function SaveAuthToken (line 247) | func SaveAuthToken(configPath, authtoken string) (err error) {

FILE: src/ngrok/client/controller.go
  type command (line 14) | type command interface
  type cmdQuit (line 16) | type cmdQuit struct
  type cmdPlayRequest (line 21) | type cmdPlayRequest struct
  type Controller (line 30) | type Controller struct
    method State (line 66) | func (ctl *Controller) State() mvc.State {
    method Update (line 70) | func (ctl *Controller) Update(state mvc.State) {
    method Updates (line 74) | func (ctl *Controller) Updates() *util.Broadcast {
    method Shutdown (line 78) | func (ctl *Controller) Shutdown(message string) {
    method PlayRequest (line 82) | func (ctl *Controller) PlayRequest(tunnel mvc.Tunnel, payload []byte) {
    method Go (line 86) | func (ctl *Controller) Go(fn func()) {
    method doShutdown (line 101) | func (ctl *Controller) doShutdown() {
    method AddView (line 125) | func (ctl *Controller) AddView(v mvc.View) {
    method GetWebInspectAddr (line 129) | func (ctl *Controller) GetWebInspectAddr() string {
    method SetupModel (line 133) | func (ctl *Controller) SetupModel(config *Configuration) *ClientModel {
    method GetModel (line 139) | func (ctl *Controller) GetModel() *ClientModel {
    method Run (line 143) | func (ctl *Controller) Run(config *Configuration) {
  function NewController (line 54) | func NewController() *Controller {

FILE: src/ngrok/client/debug.go
  function useInsecureSkipVerify (line 9) | func useInsecureSkipVerify() bool {

FILE: src/ngrok/client/main.go
  function init (line 14) | func init() {
  function Main (line 25) | func Main() {

FILE: src/ngrok/client/metrics.go
  constant sampleSize (line 8) | sampleSize  int     = 1028
  constant sampleAlpha (line 9) | sampleAlpha float64 = 0.015
  type ClientMetrics (line 12) | type ClientMetrics struct
  function NewClientMetrics (line 24) | func NewClientMetrics() *ClientMetrics {

FILE: src/ngrok/client/model.go
  constant defaultServerAddr (line 24) | defaultServerAddr   = "ngrokd.ngrok.com:443"
  constant defaultInspectAddr (line 25) | defaultInspectAddr  = "127.0.0.1:4040"
  constant pingInterval (line 26) | pingInterval        = 20 * time.Second
  constant maxPongLatency (line 27) | maxPongLatency      = 15 * time.Second
  constant updateCheckInterval (line 28) | updateCheckInterval = 6 * time.Hour
  constant BadGateway (line 29) | BadGateway          = `<html>
  type ClientModel (line 37) | type ClientModel struct
    method GetProtocols (line 136) | func (c ClientModel) GetProtocols() []proto.Protocol { return c.protoc...
    method GetClientVersion (line 137) | func (c ClientModel) GetClientVersion() string       { return version....
    method GetServerVersion (line 138) | func (c ClientModel) GetServerVersion() string       { return c.server...
    method GetTunnels (line 139) | func (c ClientModel) GetTunnels() []mvc.Tunnel {
    method GetConnStatus (line 146) | func (c ClientModel) GetConnStatus() mvc.ConnStatus     { return c.con...
    method GetUpdateStatus (line 147) | func (c ClientModel) GetUpdateStatus() mvc.UpdateStatus { return c.upd...
    method GetConnectionMetrics (line 149) | func (c ClientModel) GetConnectionMetrics() (metrics.Meter, metrics.Ti...
    method GetBytesInMetrics (line 153) | func (c ClientModel) GetBytesInMetrics() (metrics.Counter, metrics.His...
    method GetBytesOutMetrics (line 157) | func (c ClientModel) GetBytesOutMetrics() (metrics.Counter, metrics.Hi...
    method SetUpdateStatus (line 160) | func (c ClientModel) SetUpdateStatus(updateStatus mvc.UpdateStatus) {
    method PlayRequest (line 166) | func (c *ClientModel) PlayRequest(tunnel mvc.Tunnel, payload []byte) {
    method Shutdown (line 180) | func (c *ClientModel) Shutdown() {
    method update (line 183) | func (c *ClientModel) update() {
    method Run (line 187) | func (c *ClientModel) Run() {
    method control (line 212) | func (c *ClientModel) control() {
    method proxy (line 341) | func (c *ClientModel) proxy() {
    method heartbeat (line 413) | func (c *ClientModel) heartbeat(lastPongAddr *int64, conn conn.Conn) {
  function newClientModel (line 57) | func newClientModel(config *Configuration, ctl mvc.Controller) *ClientMo...
  function serverName (line 124) | func serverName(addr string) string {

FILE: src/ngrok/client/mvc/controller.go
  type Controller (line 7) | type Controller interface

FILE: src/ngrok/client/mvc/model.go
  type Model (line 3) | type Model interface

FILE: src/ngrok/client/mvc/state.go
  type UpdateStatus (line 8) | type UpdateStatus
  constant UpdateNone (line 11) | UpdateNone = -1 * iota
  constant UpdateInstalling (line 12) | UpdateInstalling
  constant UpdateReady (line 13) | UpdateReady
  constant UpdateAvailable (line 14) | UpdateAvailable
  type ConnStatus (line 17) | type ConnStatus
  constant ConnConnecting (line 20) | ConnConnecting = iota
  constant ConnReconnecting (line 21) | ConnReconnecting
  constant ConnOnline (line 22) | ConnOnline
  type Tunnel (line 25) | type Tunnel struct
  type ConnectionContext (line 31) | type ConnectionContext struct
  type State (line 36) | type State interface

FILE: src/ngrok/client/mvc/view.go
  type View (line 3) | type View interface

FILE: src/ngrok/client/release.go
  function useInsecureSkipVerify (line 9) | func useInsecureSkipVerify() bool {

FILE: src/ngrok/client/tls.go
  function LoadTLSConfig (line 12) | func LoadTLSConfig(rootCertPaths []string) (*tls.Config, error) {

FILE: src/ngrok/client/update_debug.go
  function autoUpdate (line 10) | func autoUpdate(state mvc.State, token string) {

FILE: src/ngrok/client/update_release.go
  constant appId (line 16) | appId          = "ap_pJSFC5wQYkAyI0FIVwKYs9h1hW"
  constant updateEndpoint (line 17) | updateEndpoint = "https://api.equinox.io/1/Updates"
  constant publicKey (line 20) | publicKey = `-----BEGIN PUBLIC KEY-----
  function autoUpdate (line 30) | func autoUpdate(s mvc.State, token string) {
  function applyUpdate (line 87) | func applyUpdate(s mvc.State, result *check.Result) {

FILE: src/ngrok/client/views/term/area.go
  constant fgColor (line 10) | fgColor = termbox.ColorWhite
  constant bgColor (line 11) | bgColor = termbox.ColorDefault
  type area (line 14) | type area struct
    method Clear (line 29) | func (a *area) Clear() {
    method APrintf (line 37) | func (a *area) APrintf(fg termbox.Attribute, x, y int, arg0 string, ar...
    method Printf (line 44) | func (a *area) Printf(x, y int, arg0 string, args ...interface{}) {
  function NewArea (line 25) | func NewArea(x, y, w, h int) *area {

FILE: src/ngrok/client/views/term/http.go
  constant size (line 13) | size          = 10
  constant pathMaxLength (line 14) | pathMaxLength = 25
  type HttpView (line 17) | type HttpView struct
    method Run (line 53) | func (v *HttpView) Run() {
    method Render (line 68) | func (v *HttpView) Render() {
    method Shutdown (line 83) | func (v *HttpView) Shutdown() {
  function colorFor (line 27) | func colorFor(status string) termbox.Attribute {
  function newTermHttpView (line 40) | func newTermHttpView(ctl mvc.Controller, termView *TermView, proto *prot...
  function truncatePath (line 87) | func truncatePath(path string) string {

FILE: src/ngrok/client/views/term/view.go
  type TermView (line 13) | type TermView struct
    method draw (line 58) | func (v *TermView) draw() {
    method run (line 121) | func (v *TermView) run() {
    method Shutdown (line 147) | func (v *TermView) Shutdown() {
    method Flush (line 152) | func (v *TermView) Flush() {
    method NewHttpView (line 156) | func (v *TermView) NewHttpView(p *proto.Http) *HttpView {
    method input (line 160) | func (v *TermView) input() {
  function NewTermView (line 24) | func NewTermView(ctl mvc.Controller) *TermView {
  function connStatusRepr (line 46) | func connStatusRepr(status mvc.ConnStatus) (string, termbox.Attribute) {

FILE: src/ngrok/client/views/web/http.go
  type SerializedTxn (line 21) | type SerializedTxn struct
  type SerializedBody (line 31) | type SerializedBody struct
  type SerializedRequest (line 41) | type SerializedRequest struct
  type SerializedResponse (line 50) | type SerializedResponse struct
  type WebHttpView (line 58) | type WebHttpView struct
    method updateHttp (line 145) | func (whv *WebHttpView) updateHttp() {
    method register (line 211) | func (whv *WebHttpView) register() {
    method Shutdown (line 268) | func (whv *WebHttpView) Shutdown() {
  type SerializedUiState (line 69) | type SerializedUiState struct
  type SerializedPayload (line 73) | type SerializedPayload struct
  function newWebHttpView (line 78) | func newWebHttpView(ctl mvc.Controller, wv *WebView, proto *proto.Http) ...
  type XMLDoc (line 92) | type XMLDoc struct
  function makeBody (line 96) | func makeBody(h http.Header, body []byte) SerializedBody {

FILE: src/ngrok/client/views/web/view.go
  type WebView (line 15) | type WebView struct
    method NewHttpView (line 73) | func (wv *WebView) NewHttpView(proto *proto.Http) *WebHttpView {
    method Shutdown (line 77) | func (wv *WebView) Shutdown() {
  function NewWebView (line 24) | func NewWebView(ctl mvc.Controller, addr string) *WebView {

FILE: src/ngrok/conn/conn.go
  type Conn (line 18) | type Conn interface
  type loggedConn (line 26) | type loggedConn struct
    method StartTLS (line 164) | func (c *loggedConn) StartTLS(tlsCfg *tls.Config) {
    method Close (line 168) | func (c *loggedConn) Close() (err error) {
    method Id (line 175) | func (c *loggedConn) Id() string {
    method SetType (line 179) | func (c *loggedConn) SetType(typ string) {
    method CloseRead (line 187) | func (c *loggedConn) CloseRead() error {
  type Listener (line 34) | type Listener struct
  function wrapConn (line 39) | func wrapConn(conn net.Conn, typ string) *loggedConn {
  function Listen (line 55) | func Listen(addr, typ string, tlsCfg *tls.Config) (l *Listener, err erro...
  function Wrap (line 86) | func Wrap(conn net.Conn, typ string) *loggedConn {
  function Dial (line 90) | func Dial(addr, typ string, tlsCfg *tls.Config) (conn *loggedConn, err e...
  function DialHttpProxy (line 106) | func DialHttpProxy(proxyUrl, addr, typ string, tlsCfg *tls.Config) (conn...
  function Join (line 195) | func Join(c Conn, c2 Conn) (int64, int64) {

FILE: src/ngrok/conn/tee.go
  type Tee (line 24) | type Tee struct
    method ReadBuffer (line 53) | func (c *Tee) ReadBuffer() *bufio.Reader {
    method WriteBuffer (line 57) | func (c *Tee) WriteBuffer() *bufio.Reader {
    method Read (line 61) | func (c *Tee) Read(b []byte) (n int, err error) {
    method ReadFrom (line 69) | func (c *Tee) ReadFrom(r io.Reader) (n int64, err error) {
    method Write (line 77) | func (c *Tee) Write(b []byte) (n int, err error) {
  function NewTee (line 38) | func NewTee(conn Conn) *Tee {

FILE: src/ngrok/log/logger.go
  function LogTo (line 10) | func LogTo(target string, level_name string) {
  type Logger (line 50) | type Logger interface
  type PrefixLogger (line 59) | type PrefixLogger struct
    method pfx (line 74) | func (pl *PrefixLogger) pfx(fmtstr string) interface{} {
    method Debug (line 78) | func (pl *PrefixLogger) Debug(arg0 string, args ...interface{}) {
    method Info (line 82) | func (pl *PrefixLogger) Info(arg0 string, args ...interface{}) {
    method Warn (line 86) | func (pl *PrefixLogger) Warn(arg0 string, args ...interface{}) error {
    method Error (line 90) | func (pl *PrefixLogger) Error(arg0 string, args ...interface{}) error {
    method AddLogPrefix (line 94) | func (pl *PrefixLogger) AddLogPrefix(prefix string) {
    method ClearLogPrefixes (line 102) | func (pl *PrefixLogger) ClearLogPrefixes() {
  function NewPrefixLogger (line 64) | func NewPrefixLogger(prefixes ...string) Logger {
  function Debug (line 107) | func Debug(arg0 string, args ...interface{}) {
  function Info (line 111) | func Info(arg0 string, args ...interface{}) {
  function Warn (line 115) | func Warn(arg0 string, args ...interface{}) error {
  function Error (line 119) | func Error(arg0 string, args ...interface{}) error {

FILE: src/ngrok/main/ngrok/ngrok.go
  function main (line 7) | func main() {

FILE: src/ngrok/main/ngrokd/ngrokd.go
  function main (line 7) | func main() {

FILE: src/ngrok/msg/conn.go
  function readMsgShared (line 10) | func readMsgShared(c conn.Conn) (buffer []byte, err error) {
  function ReadMsg (line 36) | func ReadMsg(c conn.Conn) (msg Message, err error) {
  function ReadMsgInto (line 45) | func ReadMsgInto(c conn.Conn, msg Message) (err error) {
  function WriteMsg (line 53) | func WriteMsg(c conn.Conn, msg interface{}) (err error) {

FILE: src/ngrok/msg/msg.go
  function init (line 10) | func init() {
  type Message (line 25) | type Message interface
  type Envelope (line 27) | type Envelope struct
  type Auth (line 34) | type Auth struct
  type AuthResp (line 54) | type AuthResp struct
  type ReqTunnel (line 65) | type ReqTunnel struct
  type NewTunnel (line 85) | type NewTunnel struct
  type ReqProxy (line 95) | type ReqProxy struct
  type RegProxy (line 100) | type RegProxy struct
  type StartProxy (line 106) | type StartProxy struct
  type Ping (line 114) | type Ping struct
  type Pong (line 119) | type Pong struct

FILE: src/ngrok/msg/pack.go
  function unpack (line 10) | func unpack(buffer []byte, msgIn Message) (msg Message, err error) {
  function UnpackInto (line 34) | func UnpackInto(buffer []byte, msg Message) (err error) {
  function Unpack (line 39) | func Unpack(buffer []byte) (msg Message, err error) {
  function Pack (line 43) | func Pack(payload interface{}) ([]byte, error) {

FILE: src/ngrok/proto/http.go
  type HttpRequest (line 21) | type HttpRequest struct
  type HttpResponse (line 26) | type HttpResponse struct
  type HttpTxn (line 31) | type HttpTxn struct
  type Http (line 40) | type Http struct
    method GetName (line 62) | func (h *Http) GetName() string { return "http" }
    method WrapConn (line 64) | func (h *Http) WrapConn(c conn.Conn, ctx interface{}) conn.Conn {
    method readRequests (line 72) | func (h *Http) readRequests(tee *conn.Tee, lastTxn chan *HttpTxn, conn...
    method readResponses (line 109) | func (h *Http) readResponses(tee *conn.Tee, lastTxn chan *HttpTxn) {
  function NewHttp (line 47) | func NewHttp() *Http {
  function extractBody (line 56) | func extractBody(r io.Reader) ([]byte, io.ReadCloser, error) {
  function drainBody (line 172) | func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
  type dumpConn (line 184) | type dumpConn struct
    method Close (line 189) | func (c *dumpConn) Close() error                       { return nil }
    method LocalAddr (line 190) | func (c *dumpConn) LocalAddr() net.Addr                { return nil }
    method RemoteAddr (line 191) | func (c *dumpConn) RemoteAddr() net.Addr               { return nil }
    method SetDeadline (line 192) | func (c *dumpConn) SetDeadline(t time.Time) error      { return nil }
    method SetReadDeadline (line 193) | func (c *dumpConn) SetReadDeadline(t time.Time) error  { return nil }
    method SetWriteDeadline (line 194) | func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil }
  type neverEnding (line 196) | type neverEnding
    method Read (line 198) | func (b neverEnding) Read(p []byte) (n int, err error) {
  function DumpRequestOut (line 208) | func DumpRequestOut(req *http.Request, body bool) ([]byte, error) {
  type delegateReader (line 286) | type delegateReader struct
    method Read (line 291) | func (r *delegateReader) Read(p []byte) (int, error) {
  function valueOrDefault (line 299) | func valueOrDefault(value, def string) string {

FILE: src/ngrok/proto/interface.go
  type Protocol (line 7) | type Protocol interface

FILE: src/ngrok/proto/tcp.go
  type Tcp (line 7) | type Tcp struct
    method GetName (line 13) | func (h *Tcp) GetName() string { return "tcp" }
    method WrapConn (line 15) | func (h *Tcp) WrapConn(c conn.Conn, ctx interface{}) conn.Conn {
  function NewTcp (line 9) | func NewTcp() *Tcp {

FILE: src/ngrok/server/cli.go
  type Options (line 7) | type Options struct
  function parseArgs (line 18) | func parseArgs() *Options {

FILE: src/ngrok/server/control.go
  constant pingTimeoutInterval (line 16) | pingTimeoutInterval = 30 * time.Second
  constant connReapInterval (line 17) | connReapInterval    = 10 * time.Second
  constant controlWriteTimeout (line 18) | controlWriteTimeout = 10 * time.Second
  constant proxyStaleDuration (line 19) | proxyStaleDuration  = 60 * time.Second
  constant proxyMaxPoolSize (line 20) | proxyMaxPoolSize    = 10
  type Control (line 23) | type Control struct
    method registerTunnel (line 129) | func (c *Control) registerTunnel(rawTunnelReq *msg.ReqTunnel) {
    method manager (line 160) | func (c *Control) manager() {
    method writer (line 204) | func (c *Control) writer() {
    method reader (line 226) | func (c *Control) reader() {
    method stopper (line 255) | func (c *Control) stopper() {
    method RegisterProxy (line 294) | func (c *Control) RegisterProxy(conn conn.Conn) {
    method GetProxy (line 312) | func (c *Control) GetProxy() (proxyConn conn.Conn, err error) {
    method Replaced (line 347) | func (c *Control) Replaced(replacement *Control) {
  function NewControl (line 63) | func NewControl(ctlConn conn.Conn, authMsg *msg.Auth) {

FILE: src/ngrok/server/http.go
  constant NotAuthorized (line 15) | NotAuthorized = `HTTP/1.0 401 Not Authorized
  constant NotFound (line 22) | NotFound = `HTTP/1.0 404 Not Found
  constant BadRequest (line 28) | BadRequest = `HTTP/1.0 400 Bad Request
  function startHttpListener (line 36) | func startHttpListener(addr string, tlsCfg *tls.Config) (listener *conn....
  function httpHandler (line 59) | func httpHandler(c conn.Conn, proto string) {

FILE: src/ngrok/server/main.go
  constant registryCacheSize (line 16) | registryCacheSize uint64        = 1024 * 1024
  constant connReadTimeout (line 17) | connReadTimeout   time.Duration = 10 * time.Second
  function NewProxy (line 30) | func NewProxy(pxyConn conn.Conn, regPxy *msg.RegProxy) {
  function tunnelListener (line 58) | func tunnelListener(addr string, tlsConfig *tls.Config) {
  function Main (line 101) | func Main() {

FILE: src/ngrok/server/metrics.go
  function init (line 18) | func init() {
  type Metrics (line 28) | type Metrics interface
  type LocalMetrics (line 36) | type LocalMetrics struct
    method OpenTunnel (line 94) | func (m *LocalMetrics) OpenTunnel(t *Tunnel) {
    method CloseTunnel (line 116) | func (m *LocalMetrics) CloseTunnel(t *Tunnel) {
    method OpenConnection (line 119) | func (m *LocalMetrics) OpenConnection(t *Tunnel, c conn.Conn) {
    method CloseConnection (line 123) | func (m *LocalMetrics) CloseConnection(t *Tunnel, c conn.Conn, start t...
    method Report (line 128) | func (m *LocalMetrics) Report() {
  function NewLocalMetrics (line 62) | func NewLocalMetrics(reportInterval time.Duration) *LocalMetrics {
  type KeenIoMetric (line 157) | type KeenIoMetric struct
  type KeenIoMetrics (line 162) | type KeenIoMetrics struct
    method AuthedRequest (line 221) | func (k *KeenIoMetrics) AuthedRequest(method, path string, body *bytes...
    method OpenConnection (line 252) | func (k *KeenIoMetrics) OpenConnection(t *Tunnel, c conn.Conn) {
    method CloseConnection (line 255) | func (k *KeenIoMetrics) CloseConnection(t *Tunnel, c conn.Conn, start ...
    method OpenTunnel (line 292) | func (k *KeenIoMetrics) OpenTunnel(t *Tunnel) {
    method CloseTunnel (line 299) | func (k *KeenIoMetrics) CloseTunnel(t *Tunnel) {
  function NewKeenIoMetrics (line 170) | func NewKeenIoMetrics(batchInterval time.Duration) *KeenIoMetrics {
  type KeenStruct (line 295) | type KeenStruct struct

FILE: src/ngrok/server/registry.go
  constant cacheSaveInterval (line 14) | cacheSaveInterval time.Duration = 10 * time.Minute
  type cacheUrl (line 17) | type cacheUrl
    method Size (line 19) | func (url cacheUrl) Size() int {
  type TunnelRegistry (line 24) | type TunnelRegistry struct
    method SaveCacheThread (line 61) | func (r *TunnelRegistry) SaveCacheThread(path string, interval time.Du...
    method Register (line 80) | func (r *TunnelRegistry) Register(url string, t *Tunnel) error {
    method cacheKeys (line 93) | func (r *TunnelRegistry) cacheKeys(t *Tunnel) (ip string, id string) {
    method GetCachedRegistration (line 102) | func (r *TunnelRegistry) GetCachedRegistration(t *Tunnel) (url string) {
    method RegisterAndCache (line 117) | func (r *TunnelRegistry) RegisterAndCache(url string, t *Tunnel) (err ...
    method RegisterRepeat (line 131) | func (r *TunnelRegistry) RegisterRepeat(urlFn func() string, t *Tunnel...
    method Del (line 151) | func (r *TunnelRegistry) Del(url string) {
    method Get (line 157) | func (r *TunnelRegistry) Get(url string) *Tunnel {
  function NewTunnelRegistry (line 31) | func NewTunnelRegistry(cacheSize uint64, cacheFile string) *TunnelRegist...
  type ControlRegistry (line 164) | type ControlRegistry struct
    method Get (line 177) | func (r *ControlRegistry) Get(clientId string) *Control {
    method Add (line 183) | func (r *ControlRegistry) Add(clientId string, ctl *Control) (oldCtl *...
    method Del (line 197) | func (r *ControlRegistry) Del(clientId string) error {
  function NewControlRegistry (line 170) | func NewControlRegistry() *ControlRegistry {

FILE: src/ngrok/server/tls.go
  function LoadTLSConfig (line 9) | func LoadTLSConfig(crtPath string, keyPath string) (tlsConfig *tls.Confi...

FILE: src/ngrok/server/tunnel.go
  type Tunnel (line 29) | type Tunnel struct
    method Shutdown (line 189) | func (t *Tunnel) Shutdown() {
    method Id (line 211) | func (t *Tunnel) Id() string {
    method listenTcp (line 216) | func (t *Tunnel) listenTcp(listener *net.TCPListener) {
    method HandlePublicConnection (line 245) | func (t *Tunnel) HandlePublicConnection(publicConn conn.Conn) {
  function registerVhost (line 53) | func registerVhost(t *Tunnel, protocol string, servingPort int) (err err...
  function NewTunnel (line 97) | func NewTunnel(m *msg.ReqTunnel, ctl *Control) (t *Tunnel, err error) {

FILE: src/ngrok/util/broadcast.go
  type Broadcast (line 3) | type Broadcast struct
    method In (line 47) | func (b *Broadcast) In() chan interface{} {
    method Reg (line 51) | func (b *Broadcast) Reg() chan interface{} {
    method UnReg (line 57) | func (b *Broadcast) UnReg(listener chan interface{}) {
  function NewBroadcast (line 10) | func NewBroadcast() *Broadcast {

FILE: src/ngrok/util/errors.go
  constant crashMessage (line 8) | crashMessage = `panic: %v
  function MakePanicTrace (line 17) | func MakePanicTrace(err interface{}) string {
  function PanicToError (line 25) | func PanicToError(fn func()) (err error) {

FILE: src/ngrok/util/id.go
  function RandomSeed (line 10) | func RandomSeed() (seed int64, err error) {
  function RandId (line 16) | func RandId(idlen int) string {
  function SecureRandId (line 30) | func SecureRandId(idlen int) (id string, err error) {
  function SecureRandIdOrPanic (line 47) | func SecureRandIdOrPanic(idlen int) string {

FILE: src/ngrok/util/ring.go
  type Ring (line 8) | type Ring struct
    method Add (line 18) | func (r *Ring) Add(item interface{}) interface{} {
    method Slice (line 34) | func (r *Ring) Slice() []interface{} {
  function NewRing (line 14) | func NewRing(capacity int) *Ring {

FILE: src/ngrok/util/shutdown.go
  type Shutdown (line 8) | type Shutdown struct
    method Begin (line 22) | func (s *Shutdown) Begin() {
    method WaitBegin (line 33) | func (s *Shutdown) WaitBegin() {
    method Complete (line 37) | func (s *Shutdown) Complete() {
    method WaitComplete (line 41) | func (s *Shutdown) WaitComplete() {
  function NewShutdown (line 15) | func NewShutdown() *Shutdown {

FILE: src/ngrok/version/version.go
  constant Proto (line 8) | Proto = "2"
  constant Major (line 9) | Major = "1"
  constant Minor (line 10) | Minor = "7"
  function MajorMinor (line 13) | func MajorMinor() string {
  function Full (line 17) | func Full() string {
  function Compat (line 21) | func Compat(client string, server string) bool {
Condensed preview — 66 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (301K chars).
[
  {
    "path": ".gitignore",
    "chars": 150,
    "preview": "*.swp\nbin/\npkg/\nsrc/code.google.com\nsrc/github.com\nsrc/bitbucket.org\nsrc/launchpad.net\nsrc/gopkg.in\nsrc/ngrok/client/ass"
  },
  {
    "path": ".travis.yml",
    "chars": 156,
    "preview": "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  "
  },
  {
    "path": "CONTRIBUTORS",
    "chars": 199,
    "preview": "Contributors to ngrok, both large and small:\n\n- Alan Shreve \n- Brandon Philips \n- Caleb Spare \n- Jay Hayes \n- Kevin Burk"
  },
  {
    "path": "LICENSE",
    "chars": 551,
    "preview": "Copyright 2013 Alan Shreve\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file ex"
  },
  {
    "path": "Makefile",
    "chars": 1433,
    "preview": ".PHONY: default server client deps fmt clean all release-all assets client-assets server-assets contributors\nexport GOPA"
  },
  {
    "path": "README.md",
    "chars": 2640,
    "preview": "# ngrok - Unified Ingress for Developers\n\n[https://ngrok.com](https://ngrok.com)\n\n## ngrok Community on GitHub\n\nIf you a"
  },
  {
    "path": "assets/client/page.html",
    "chars": 7255,
    "preview": "<html>\n    <head>\n        <title>ngrok</title>\n        <link href=\"/static/css/highlight.min.css\" rel=\"stylesheet\">\n    "
  },
  {
    "path": "assets/client/static/js/angular.js",
    "chars": 88802,
    "preview": "/*\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';fun"
  },
  {
    "path": "assets/client/static/js/base64.js",
    "chars": 6206,
    "preview": "/*\nCopyright (c) 2008 Fred Palmer fred.palmer_at_gmail.com\n\nPermission is hereby granted, free of charge, to any person\n"
  },
  {
    "path": "assets/client/static/js/jquery.timeago.js",
    "chars": 6111,
    "preview": "/**\n * Timeago is a jQuery plugin that makes it easy to support automatically\n * updating fuzzy timestamps (e.g. \"4 minu"
  },
  {
    "path": "assets/client/static/js/ngrok.js",
    "chars": 11600,
    "preview": "var ngrok = angular.module(\"ngrok\", [\"ngSanitize\"]);\n\nvar hexRepr = function(bytes) {\n    var buf = [];\n    var ascii = "
  },
  {
    "path": "assets/client/static/js/vkbeautify.js",
    "chars": 10469,
    "preview": "/**\r\n* vkBeautify - javascript plugin to pretty-print or minify text in XML, JSON, CSS and SQL formats.\r\n*  \r\n* Version "
  },
  {
    "path": "assets/client/tls/ngrokroot.crt",
    "chars": 1521,
    "preview": "-----BEGIN CERTIFICATE-----\nMIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU\nMBIGA1UEChMLQWRkVHJ1c3QgQUI"
  },
  {
    "path": "assets/client/tls/snakeoilca.crt",
    "chars": 1822,
    "preview": "-----BEGIN CERTIFICATE-----\nMIIFFDCCAvwCCQCkbN0RG/o15DANBgkqhkiG9w0BAQUFADBMMQswCQYDVQQGEwJV\nUzETMBEGA1UECBMKQ2FsaWZvcm5"
  },
  {
    "path": "assets/server/tls/snakeoil.crt",
    "chars": 1809,
    "preview": "-----BEGIN CERTIFICATE-----\nMIIFDDCCAvQCAQEwDQYJKoZIhvcNAQEFBQAwTDELMAkGA1UEBhMCVVMxEzARBgNV\nBAgTCkNhbGlmb3JuaWExEjAQBgN"
  },
  {
    "path": "assets/server/tls/snakeoil.key",
    "chars": 3243,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIJKAIBAAKCAgEA2i5cLrAlpw8gVgSIogYaGWJKHGzLXzUsnTrhlMPcBExOL040\n6xGQddOgzQJXVk0OeI+nckg"
  },
  {
    "path": "contrib/com.ngrok.client.plist",
    "chars": 868,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- \nThis is an example launchd script for MacOS. It was written under 10.9.1. \n"
  },
  {
    "path": "docs/CHANGELOG.md",
    "chars": 5834,
    "preview": "# Changelog\n## 1.7 - 6/6/2014\n- IMPROVEMENT: Print a better help message when run without any arguments\n- IMPROVEMENT: D"
  },
  {
    "path": "docs/DEVELOPMENT.md",
    "chars": 5638,
    "preview": "# Developer's guide to ngrok\n\n\n## Components\nThe ngrok project is composed of two components, the ngrok client (ngrok) a"
  },
  {
    "path": "docs/SELFHOSTING.md",
    "chars": 3092,
    "preview": "# How to run your own ngrokd server\n\nRunning your own ngrok server is really easy! The instructions below will guide you"
  },
  {
    "path": "src/ngrok/cache/lru.go",
    "chars": 5501,
    "preview": "// Copyright 2012, Google Inc. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license tha"
  },
  {
    "path": "src/ngrok/client/cli.go",
    "chars": 3141,
    "preview": "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"
  },
  {
    "path": "src/ngrok/client/config.go",
    "chars": 7123,
    "preview": "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\""
  },
  {
    "path": "src/ngrok/client/controller.go",
    "chars": 3948,
    "preview": "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\""
  },
  {
    "path": "src/ngrok/client/debug.go",
    "chars": 192,
    "preview": "// +build !release\n\npackage client\n\nvar (\n\trootCrtPaths = []string{\"assets/client/tls/ngrokroot.crt\", \"assets/client/tls"
  },
  {
    "path": "src/ngrok/client/main.go",
    "chars": 904,
    "preview": "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\""
  },
  {
    "path": "src/ngrok/client/metrics.go",
    "chars": 935,
    "preview": "package client\n\nimport (\n\tmetrics \"github.com/rcrowley/go-metrics\"\n)\n\nconst (\n\tsampleSize  int     = 1028\n\tsampleAlpha f"
  },
  {
    "path": "src/ngrok/client/model.go",
    "chars": 11374,
    "preview": "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\"ng"
  },
  {
    "path": "src/ngrok/client/mvc/controller.go",
    "chars": 563,
    "preview": "package mvc\n\nimport (\n\t\"ngrok/util\"\n)\n\ntype Controller interface {\n\t// how the model communicates that it has changed st"
  },
  {
    "path": "src/ngrok/client/mvc/model.go",
    "chars": 103,
    "preview": "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",
    "chars": 830,
    "preview": "package mvc\n\nimport (\n\tmetrics \"github.com/rcrowley/go-metrics\"\n\t\"ngrok/proto\"\n)\n\ntype UpdateStatus int\n\nconst (\n\tUpdate"
  },
  {
    "path": "src/ngrok/client/mvc/view.go",
    "chars": 49,
    "preview": "package mvc\n\ntype View interface {\n\tShutdown()\n}\n"
  },
  {
    "path": "src/ngrok/client/release.go",
    "chars": 156,
    "preview": "// +build release\n\npackage client\n\nvar (\n\trootCrtPaths = []string{\"assets/client/tls/ngrokroot.crt\"}\n)\n\nfunc useInsecure"
  },
  {
    "path": "src/ngrok/client/tls.go",
    "chars": 617,
    "preview": "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)"
  },
  {
    "path": "src/ngrok/client/update_debug.go",
    "chars": 165,
    "preview": "// +build !release,!autoupdate\n\npackage client\n\nimport (\n\t\"ngrok/client/mvc\"\n)\n\n// no auto-updating in debug mode\nfunc a"
  },
  {
    "path": "src/ngrok/client/update_release.go",
    "chars": 2784,
    "preview": "// +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\"gopk"
  },
  {
    "path": "src/ngrok/client/views/term/area.go",
    "chars": 902,
    "preview": "// shared internal functions for handling output to the terminal\npackage term\n\nimport (\n\t\"fmt\"\n\ttermbox \"github.com/nsf/"
  },
  {
    "path": "src/ngrok/client/views/term/http.go",
    "chars": 2635,
    "preview": "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"
  },
  {
    "path": "src/ngrok/client/views/term/view.go",
    "chars": 3723,
    "preview": "// interactive terminal interface for local clients\npackage term\n\nimport (\n\ttermbox \"github.com/nsf/termbox-go\"\n\t\"ngrok/"
  },
  {
    "path": "src/ngrok/client/views/web/http.go",
    "chars": 6537,
    "preview": "// interactive web user interface\npackage web\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"encoding/xml\"\n\t\"html/templ"
  },
  {
    "path": "src/ngrok/client/views/web/view.go",
    "chars": 1783,
    "preview": "// interactive web user interface\npackage web\n\nimport (\n\t\"github.com/gorilla/websocket\"\n\t\"net/http\"\n\t\"ngrok/client/asset"
  },
  {
    "path": "src/ngrok/conn/conn.go",
    "chars": 4653,
    "preview": "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\"i"
  },
  {
    "path": "src/ngrok/conn/tee.go",
    "chars": 1803,
    "preview": "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"
  },
  {
    "path": "src/ngrok/log/logger.go",
    "chars": 2415,
    "preview": "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(t"
  },
  {
    "path": "src/ngrok/main/ngrok/ngrok.go",
    "chars": 73,
    "preview": "package main\n\nimport (\n\t\"ngrok/client\"\n)\n\nfunc main() {\n\tclient.Main()\n}\n"
  },
  {
    "path": "src/ngrok/main/ngrokd/ngrokd.go",
    "chars": 73,
    "preview": "package main\n\nimport (\n\t\"ngrok/server\"\n)\n\nfunc main() {\n\tserver.Main()\n}\n"
  },
  {
    "path": "src/ngrok/msg/conn.go",
    "chars": 1187,
    "preview": "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 []byt"
  },
  {
    "path": "src/ngrok/msg/msg.go",
    "chars": 3539,
    "preview": "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("
  },
  {
    "path": "src/ngrok/msg/pack.go",
    "chars": 895,
    "preview": "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 Mes"
  },
  {
    "path": "src/ngrok/proto/http.go",
    "chars": 8241,
    "preview": "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/"
  },
  {
    "path": "src/ngrok/proto/interface.go",
    "chars": 131,
    "preview": "package proto\n\nimport (\n\t\"ngrok/conn\"\n)\n\ntype Protocol interface {\n\tGetName() string\n\tWrapConn(conn.Conn, interface{}) c"
  },
  {
    "path": "src/ngrok/proto/tcp.go",
    "chars": 227,
    "preview": "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) GetNa"
  },
  {
    "path": "src/ngrok/server/cli.go",
    "chars": 1251,
    "preview": "package server\n\nimport (\n\t\"flag\"\n)\n\ntype Options struct {\n\thttpAddr   string\n\thttpsAddr  string\n\ttunnelAddr string\n\tdoma"
  },
  {
    "path": "src/ngrok/server/control.go",
    "chars": 8079,
    "preview": "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\"strin"
  },
  {
    "path": "src/ngrok/server/http.go",
    "chars": 2772,
    "preview": "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\"ngro"
  },
  {
    "path": "src/ngrok/server/main.go",
    "chars": 3361,
    "preview": "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\"ru"
  },
  {
    "path": "src/ngrok/server/metrics.go",
    "chars": 7998,
    "preview": "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"
  },
  {
    "path": "src/ngrok/server/registry.go",
    "chars": 5276,
    "preview": "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\tcacheSave"
  },
  {
    "path": "src/ngrok/server/tls.go",
    "chars": 770,
    "preview": "package server\n\nimport (\n\t\"crypto/tls\"\n\t\"io/ioutil\"\n\t\"ngrok/server/assets\"\n)\n\nfunc LoadTLSConfig(crtPath string, keyPath"
  },
  {
    "path": "src/ngrok/server/tunnel.go",
    "chars": 7322,
    "preview": "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/u"
  },
  {
    "path": "src/ngrok/util/broadcast.go",
    "chars": 1228,
    "preview": "package util\n\ntype Broadcast struct {\n\tlisteners []chan interface{}\n\treg       chan (chan interface{})\n\tunreg     chan ("
  },
  {
    "path": "src/ngrok/util/errors.go",
    "chars": 676,
    "preview": "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 "
  },
  {
    "path": "src/ngrok/util/id.go",
    "chars": 997,
    "preview": "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"
  },
  {
    "path": "src/ngrok/util/ring.go",
    "chars": 657,
    "preview": "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 New"
  },
  {
    "path": "src/ngrok/util/shutdown.go",
    "chars": 676,
    "preview": "package util\n\nimport (\n\t\"sync\"\n)\n\n// A small utility class for managing controlled shutdowns\ntype Shutdown struct {\n\tsyn"
  },
  {
    "path": "src/ngrok/version/version.go",
    "chars": 312,
    "preview": "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"
  }
]

About this extraction

This page contains the full source code of the inconshreveable/ngrok GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 66 files (271.5 KB), approximately 88.5k tokens, and a symbol index with 489 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!