Browse Source

base64 encoded all raw bytes transferred to the web interface, and expose a primitive hex viewing interface for all requests/responses

Alan Shreve 12 years ago
parent
commit
f1b17a7987

+ 1 - 0
Makefile

@@ -41,6 +41,7 @@ bindata-client:
 	./bin/go-bindata -b release -i assets/vkbeautify.js -o src/ngrok/client/views/web/static/vkbeautify.js.go -m -p static -f VkBeautifyJs
 	./bin/go-bindata -b release -i assets/vkbeautify.js -o src/ngrok/client/views/web/static/vkbeautify.js.go -m -p static -f VkBeautifyJs
 	./bin/go-bindata -b release -i assets/angular.js -o src/ngrok/client/views/web/static/angular.js.go -m -p static -f AngularJs
 	./bin/go-bindata -b release -i assets/angular.js -o src/ngrok/client/views/web/static/angular.js.go -m -p static -f AngularJs
 	./bin/go-bindata -b release -i assets/ngrok.js -o src/ngrok/client/views/web/static/ngrok.js.go -m -p static -f NgrokJs
 	./bin/go-bindata -b release -i assets/ngrok.js -o src/ngrok/client/views/web/static/ngrok.js.go -m -p static -f NgrokJs
+	./bin/go-bindata -b release -i assets/base64.js -o src/ngrok/client/views/web/static/base64.js.go -m -p static -f Base64Js
 
 
 all: fmt client server
 all: fmt client server
 
 

+ 223 - 0
assets/base64.js

@@ -0,0 +1,223 @@
+/*
+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;
+        }
+    }
+};

+ 63 - 3
assets/ngrok.js

@@ -1,5 +1,45 @@
 var ngrok = angular.module("ngrok", []);
 var ngrok = angular.module("ngrok", []);
 
 
+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.directive({
 ngrok.directive({
     "keyval": function() {
     "keyval": function() {
         return {
         return {
@@ -37,7 +77,7 @@ ngrok.directive({
             template: '' + 
             template: '' + 
             '<ul class="nav nav-pills">' +
             '<ul class="nav nav-pills">' +
                 '<li ng-repeat="tab in tabNames" ng-class="{\'active\': isTab(tab)}">' +
                 '<li ng-repeat="tab in tabNames" ng-class="{\'active\': isTab(tab)}">' +
-                    '<a ng-click="setTab(tab)" href="#">{{tab}}</a>' +
+                    '<a href="" ng-click="setTab(tab)">{{tab}}</a>' +
                 '</li>' +
                 '</li>' +
                 '<li ng-show="!!btn" class="pull-right"> <button class="btn btn-primary" ng-click="onbtnclick()">{{btn}}</button></li>' +
                 '<li ng-show="!!btn" class="pull-right"> <button class="btn btn-primary" ng-click="onbtnclick()">{{btn}}</button></li>' +
             '</ul>',
             '</ul>',
@@ -57,7 +97,8 @@ ngrok.directive({
     "body": function($timeout) {
     "body": function($timeout) {
         return {
         return {
             scope: {
             scope: {
-                "body": "="
+                "body": "=",
+                "binary": "="
             },
             },
             template: '' +
             template: '' +
             '<h6 ng-show="hasBody">' +
             '<h6 ng-show="hasBody">' +
@@ -65,7 +106,7 @@ ngrok.directive({
                 '{{ Body.RawContentType }}' +
                 '{{ Body.RawContentType }}' +
             '</h6>' +
             '</h6>' +
 '' +
 '' +
-            '<div ng-show="!isForm">' +
+            '<div ng-show="!isForm && !binary">' +
                 '<pre ng-show="hasBody"><code ng-class="syntaxClass">{{ Body.Text }}</code></pre>' +
                 '<pre ng-show="hasBody"><code ng-class="syntaxClass">{{ Body.Text }}</code></pre>' +
             '</div>' +
             '</div>' +
 '' +
 '' +
@@ -78,6 +119,11 @@ ngrok.directive({
 
 
             controller: function($scope) {
             controller: function($scope) {
                 var body = $scope.body;
                 var body = $scope.body;
+                if ($scope.binary) {
+                    body.Text = "";
+                } else {
+                    body.Text = Base64.decode(body.Text).text;
+                }
                 $scope.isForm = (body.ContentType == "application/x-www-form-urlencoded");
                 $scope.isForm = (body.ContentType == "application/x-www-form-urlencoded");
                 $scope.hasBody = (body.Length > 0);
                 $scope.hasBody = (body.Length > 0);
                 $scope.hasError = !!body.Error;
                 $scope.hasError = !!body.Error;
@@ -185,6 +231,13 @@ ngrok.controller({
                 data: { txnid: $scope.txn.Id }
                 data: { txnid: $scope.txn.Id }
             });
             });
         }
         }
+
+        var decoded = Base64.decode($scope.Req.Raw);
+        $scope.Req.RawBytes = hexRepr(decoded.bytes);
+
+        if (!$scope.Req.Binary) {
+            $scope.Req.RawText = decoded.text;
+        }
     },
     },
 
 
     "HttpResponse": function($scope) {
     "HttpResponse": function($scope) {
@@ -195,5 +248,12 @@ ngrok.controller({
             '4': "text-warning",
             '4': "text-warning",
             '5': "text-error"
             '5': "text-error"
         }[$scope.Resp.Status[0]];
         }[$scope.Resp.Status[0]];
+
+        var decoded = Base64.decode($scope.Resp.Raw);
+        $scope.Resp.RawBytes = hexRepr(decoded.bytes);
+
+        if (!$scope.Resp.Binary) {
+            $scope.Resp.RawText = decoded.text;
+        }
     }
     }
 });
 });

+ 15 - 6
assets/page.html

@@ -8,6 +8,7 @@
         <script src="/static/jquery-1.9.1.min.js"></script>
         <script src="/static/jquery-1.9.1.min.js"></script>
         <script src="/static/angular.js"></script>
         <script src="/static/angular.js"></script>
         <script>hljs.initHighlightingOnLoad();</script>
         <script>hljs.initHighlightingOnLoad();</script>
+        <script src="/static/base64.js"></script>
         <script src="/static/ngrok.js"></script>
         <script src="/static/ngrok.js"></script>
         <script type="text/javascript">
         <script type="text/javascript">
             window.data = JSON.parse({% . %});
             window.data = JSON.parse({% . %});
@@ -48,12 +49,12 @@
                         <!-- Request -->
                         <!-- Request -->
                         <div class="span6" ng-controller="HttpRequest">
                         <div class="span6" ng-controller="HttpRequest">
                             <h3>{{ Req.MethodPath }}</h3>
                             <h3>{{ Req.MethodPath }}</h3>
-                            <div onbtnclick="replay()" btn="Replay" tabs="Summary,Headers,Raw">
+                            <div onbtnclick="replay()" btn="Replay" tabs="Summary,Headers,Raw,Binary">
                             </div>
                             </div>
 
 
                             <div ng-show="isTab('Summary')">
                             <div ng-show="isTab('Summary')">
                                 <keyval title="Query Params" tuples="Req.Params"></keyval>
                                 <keyval title="Query Params" tuples="Req.Params"></keyval>
-                                <div body="Req.Body"></div>
+                                <div body="Req.Body" binary="Req.Binary"></div>
                             </div>
                             </div>
 
 
                             <div ng-show="isTab('Headers')">
                             <div ng-show="isTab('Headers')">
@@ -61,7 +62,11 @@
                             </div>
                             </div>
 
 
                             <div ng-show="isTab('Raw')">
                             <div ng-show="isTab('Raw')">
-                                <pre><code class="http">{{ Req.Raw }}</code></pre>
+                                <pre><code class="http">{{ Req.RawText }}</code></pre>
+                            </div>
+
+                            <div ng-show="isTab('Binary')">
+                                <pre><code class="http">{{ Req.RawBytes }}</code></pre>
                             </div>
                             </div>
 
 
                         </div>
                         </div>
@@ -69,9 +74,9 @@
                         <div class="span6" ng-controller="HttpResponse">
                         <div class="span6" ng-controller="HttpResponse">
                             <h3 ng-class="statusClass">{{ Resp.Status }}</h3>
                             <h3 ng-class="statusClass">{{ Resp.Status }}</h3>
 
 
-                            <div tabs="Summary,Headers,Raw"></div>
+                            <div tabs="Summary,Headers,Raw,Binary"></div>
                             <div ng-show="isTab('Summary')">
                             <div ng-show="isTab('Summary')">
-                                <div body="Resp.Body"></div>
+                                <div body="Resp.Body" binary="Resp.Binary"></div>
                             </div>
                             </div>
 
 
                             <div ng-show="isTab('Headers')">
                             <div ng-show="isTab('Headers')">
@@ -79,7 +84,11 @@
                             </div>
                             </div>
 
 
                             <div ng-show="isTab('Raw')">
                             <div ng-show="isTab('Raw')">
-                                <pre><code class="http">{{ Resp.Raw }}</code></pre>
+                                <pre><code class="http">{{ Resp.RawText }}</code></pre>
+                            </div>
+
+                            <div ng-show="isTab('Binary')">
+                                <pre><code class="http">{{ Resp.RawBytes }}</code></pre>
                             </div>
                             </div>
                         </div>
                         </div>
                     </div>
                     </div>

+ 5 - 2
src/ngrok/client/views/term/view.go

@@ -115,7 +115,9 @@ func (v *TermView) run() {
 			termbox.Flush()
 			termbox.Flush()
 
 
 		case obj := <-v.updates:
 		case obj := <-v.updates:
-			v.state = obj.(ui.State)
+			if obj != nil {
+				v.state = obj.(ui.State)
+			}
 			v.Render()
 			v.Render()
 
 
 		case <-v.ctl.Shutdown:
 		case <-v.ctl.Shutdown:
@@ -137,7 +139,8 @@ func (v *TermView) input() {
 
 
 		case termbox.EventResize:
 		case termbox.EventResize:
 			v.Info("Resize event, redrawing")
 			v.Info("Resize event, redrawing")
-			v.Render()
+			// send nil to update channel to force re-rendering
+			v.updates <- nil
 			for _, sv := range v.subviews {
 			for _, sv := range v.subviews {
 				sv.Render()
 				sv.Render()
 			}
 			}

+ 9 - 3
src/ngrok/client/views/web/http.go

@@ -2,6 +2,7 @@
 package web
 package web
 
 
 import (
 import (
+	"encoding/base64"
 	"encoding/json"
 	"encoding/json"
 	"encoding/xml"
 	"encoding/xml"
 	"html/template"
 	"html/template"
@@ -14,6 +15,7 @@ import (
 	"ngrok/proto"
 	"ngrok/proto"
 	"ngrok/util"
 	"ngrok/util"
 	"strings"
 	"strings"
+	"unicode/utf8"
 )
 )
 
 
 type SerializedTxn struct {
 type SerializedTxn struct {
@@ -39,6 +41,7 @@ type SerializedRequest struct {
 	Params     url.Values
 	Params     url.Values
 	Header     http.Header
 	Header     http.Header
 	Body       SerializedBody
 	Body       SerializedBody
+	Binary     bool
 }
 }
 
 
 type SerializedResponse struct {
 type SerializedResponse struct {
@@ -46,6 +49,7 @@ type SerializedResponse struct {
 	Status string
 	Status string
 	Header http.Header
 	Header http.Header
 	Body   SerializedBody
 	Body   SerializedBody
+	Binary bool
 }
 }
 
 
 type WebHttpView struct {
 type WebHttpView struct {
@@ -90,7 +94,7 @@ type XMLDoc struct {
 func makeBody(h http.Header, body []byte) SerializedBody {
 func makeBody(h http.Header, body []byte) SerializedBody {
 	b := SerializedBody{
 	b := SerializedBody{
 		Length:      len(body),
 		Length:      len(body),
-		Text:        string(body),
+		Text:        base64.StdEncoding.EncodeToString(body),
 		ErrorOffset: -1,
 		ErrorOffset: -1,
 	}
 	}
 
 
@@ -165,10 +169,11 @@ func (whv *WebHttpView) updateHttp() {
 				HttpTxn: htxn,
 				HttpTxn: htxn,
 				Req: SerializedRequest{
 				Req: SerializedRequest{
 					MethodPath: htxn.Req.Method + " " + htxn.Req.URL.Path,
 					MethodPath: htxn.Req.Method + " " + htxn.Req.URL.Path,
-					Raw:        string(rawReq),
+					Raw:        base64.StdEncoding.EncodeToString(rawReq),
 					Params:     htxn.Req.URL.Query(),
 					Params:     htxn.Req.URL.Query(),
 					Header:     htxn.Req.Header,
 					Header:     htxn.Req.Header,
 					Body:       body,
 					Body:       body,
+					Binary:     !utf8.Valid(rawReq),
 				},
 				},
 			}
 			}
 
 
@@ -188,9 +193,10 @@ func (whv *WebHttpView) updateHttp() {
 			body := makeBody(htxn.Resp.Header, htxn.Resp.BodyBytes)
 			body := makeBody(htxn.Resp.Header, htxn.Resp.BodyBytes)
 			txn.Resp = SerializedResponse{
 			txn.Resp = SerializedResponse{
 				Status: htxn.Resp.Status,
 				Status: htxn.Resp.Status,
-				Raw:    string(rawResp),
+				Raw:    base64.StdEncoding.EncodeToString(rawResp),
 				Header: htxn.Resp.Header,
 				Header: htxn.Resp.Header,
 				Body:   body,
 				Body:   body,
+				Binary: !utf8.Valid(rawResp),
 			}
 			}
 
 
 			payload, err := json.Marshal(txn)
 			payload, err := json.Marshal(txn)

+ 2 - 0
src/ngrok/client/views/web/static/debug.go

@@ -39,6 +39,7 @@ func init() {
 		VkBeautifyJs,
 		VkBeautifyJs,
 		AngularJs,
 		AngularJs,
 		NgrokJs,
 		NgrokJs,
+		Base64Js,
 	}
 	}
 	for _, f := range fns {
 	for _, f := range fns {
 		f()
 		f()
@@ -61,3 +62,4 @@ func JqueryJs() []byte     { return ReadFileOrPanic("jquery-1.9.1.min.js") }
 func VkBeautifyJs() []byte { return ReadFileOrPanic("vkbeautify.js") }
 func VkBeautifyJs() []byte { return ReadFileOrPanic("vkbeautify.js") }
 func AngularJs() []byte    { return ReadFileOrPanic("angular.js") }
 func AngularJs() []byte    { return ReadFileOrPanic("angular.js") }
 func NgrokJs() []byte      { return ReadFileOrPanic("ngrok.js") }
 func NgrokJs() []byte      { return ReadFileOrPanic("ngrok.js") }
+func Base64Js() []byte     { return ReadFileOrPanic("base64.js") }

+ 1 - 0
src/ngrok/client/views/web/static/map.go

@@ -8,4 +8,5 @@ var AssetMap = map[string]func() []byte{
 	"vkbeautify.js":       VkBeautifyJs,
 	"vkbeautify.js":       VkBeautifyJs,
 	"angular.js":          AngularJs,
 	"angular.js":          AngularJs,
 	"ngrok.js":            NgrokJs,
 	"ngrok.js":            NgrokJs,
+	"base64.js":           Base64Js,
 }
 }