diff --git a/html/lib/base64.js b/html/lib/base64.js
new file mode 100644
index 000000000..2d8769902
--- /dev/null
+++ b/html/lib/base64.js
@@ -0,0 +1,142 @@
+/**
+*
+* Base64 encode / decode
+* http://www.webtoolkit.info/
+*
+**/
+
+var Base64 = {
+
+ // private property
+ _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
+
+ // public method for encoding
+ encode : function (input) {
+ var output = "";
+ var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+ var i = 0;
+
+ input = Base64._utf8_encode(input);
+
+ while (i < input.length) {
+
+ chr1 = input.charCodeAt(i++);
+ chr2 = input.charCodeAt(i++);
+ chr3 = input.charCodeAt(i++);
+
+ enc1 = chr1 >> 2;
+ enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+ enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+ enc4 = chr3 & 63;
+
+ if (isNaN(chr2)) {
+ enc3 = enc4 = 64;
+ } else if (isNaN(chr3)) {
+ enc4 = 64;
+ }
+
+ output = output +
+ this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
+ this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
+
+ }
+
+ return output;
+ },
+
+ // public method for decoding
+ decode : function (input) {
+ var output = "";
+ var chr1, chr2, chr3;
+ var enc1, enc2, enc3, enc4;
+ var i = 0;
+
+ input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
+
+ while (i < input.length) {
+
+ enc1 = this._keyStr.indexOf(input.charAt(i++));
+ enc2 = this._keyStr.indexOf(input.charAt(i++));
+ enc3 = this._keyStr.indexOf(input.charAt(i++));
+ enc4 = this._keyStr.indexOf(input.charAt(i++));
+
+ chr1 = (enc1 << 2) | (enc2 >> 4);
+ chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+ chr3 = ((enc3 & 3) << 6) | enc4;
+
+ output = output + String.fromCharCode(chr1);
+
+ if (enc3 != 64) {
+ output = output + String.fromCharCode(chr2);
+ }
+ if (enc4 != 64) {
+ output = output + String.fromCharCode(chr3);
+ }
+
+ }
+
+ output = Base64._utf8_decode(output);
+
+ return output;
+
+ },
+
+ // private method for UTF-8 encoding
+ _utf8_encode : function (string) {
+ string = string.replace(/\r\n/g,"\n");
+ var utftext = "";
+
+ for (var n = 0; n < string.length; n++) {
+
+ var c = string.charCodeAt(n);
+
+ if (c < 128) {
+ utftext += String.fromCharCode(c);
+ }
+ else if((c > 127) && (c < 2048)) {
+ utftext += String.fromCharCode((c >> 6) | 192);
+ utftext += String.fromCharCode((c & 63) | 128);
+ }
+ else {
+ utftext += String.fromCharCode((c >> 12) | 224);
+ utftext += String.fromCharCode(((c >> 6) & 63) | 128);
+ utftext += String.fromCharCode((c & 63) | 128);
+ }
+
+ }
+
+ return utftext;
+ },
+
+ // private method for UTF-8 decoding
+ _utf8_decode : function (utftext) {
+ var string = "";
+ var i = 0;
+ var c = c1 = c2 = 0;
+
+ while ( i < utftext.length ) {
+
+ c = utftext.charCodeAt(i);
+
+ if (c < 128) {
+ string += String.fromCharCode(c);
+ i++;
+ }
+ else if((c > 191) && (c < 224)) {
+ c2 = utftext.charCodeAt(i+1);
+ string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
+ i += 2;
+ }
+ else {
+ c2 = utftext.charCodeAt(i+1);
+ c3 = utftext.charCodeAt(i+2);
+ string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
+ i += 3;
+ }
+
+ }
+
+ return string;
+ }
+
+}
diff --git a/html/lib/cookies.js b/html/lib/cookies.js
new file mode 100644
index 000000000..646868103
--- /dev/null
+++ b/html/lib/cookies.js
@@ -0,0 +1,24 @@
+function createCookie(name,value,days) {
+ if (days) {
+ var date = new Date();
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ var expires = "; expires="+date.toGMTString();
+ }
+ else var expires = "";
+ document.cookie = name+"="+value+expires+"; path=/";
+}
+
+function readCookie(name) {
+ var nameEQ = name + "=";
+ var ca = document.cookie.split(';');
+ for(var i=0;i < ca.length;i++) {
+ var c = ca[i];
+ while (c.charAt(0)==' ') c = c.substring(1,c.length);
+ if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
+ }
+ return null;
+}
+
+function eraseCookie(name) {
+ createCookie(name,"",-1);
+}
diff --git a/html/views/history/history.js b/html/views/history/history.js
index 225e590fd..3a165716a 100644
--- a/html/views/history/history.js
+++ b/html/views/history/history.js
@@ -33,22 +33,22 @@ var Commit = function(obj) {
}
this.header = this.raw.substring(0, messageStart);
- if (typeof this.header !== 'undefined') {
- var match = this.header.match(/\nauthor (.*) <(.*@.*|.*)> ([0-9].*)/);
- if (typeof match !== 'undefined' && typeof match[2] !== 'undefined') {
- if (!(match[2].match(/@[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/)))
- this.author_email = match[2];
+ if (typeof this.header !== 'undefined') {
+ var match = this.header.match(/\nauthor (.*) <(.*@.*|.*)> ([0-9].*)/);
+ if (typeof match !== 'undefined' && typeof match[2] !== 'undefined') {
+ if (!(match[2].match(/@[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/)))
+ this.author_email = match[2];
if (typeof match[3] !== 'undefined')
- this.author_date = new Date(parseInt(match[3]) * 1000);
+ this.author_date = new Date(parseInt(match[3]) * 1000);
- match = this.header.match(/\ncommitter (.*) <(.*@.*|.*)> ([0-9].*)/);
+ match = this.header.match(/\ncommitter (.*) <(.*@.*|.*)> ([0-9].*)/);
if (typeof match[2] !== 'undefined')
this.committer_email = match[2];
if (typeof match[3] !== 'undefined')
this.committer_date = new Date(parseInt(match[3]) * 1000);
- }
- }
+ }
+ }
}
this.reloadRefs = function() {
@@ -66,10 +66,10 @@ var confirm_gist = function(confirmation_message) {
// Set optional confirmation_message
confirmation_message = confirmation_message || "Yes. Paste this commit.";
- var deleteMessage = Controller.getConfig_("github.token") ? " " : "You might not be able to delete it after posting.
";
+ var deleteMessage = Controller.getConfig_("github.token") ? " " : 'Since your github token is not set, you will not be able to delete it.
';
var publicMessage = Controller.isFeatureEnabled_("publicGist") ? "public" : "private";
// Insert the verification links into div#notification_message
- var notification_text = 'This will create a ' + publicMessage + ' paste of your commit to http://gist.github.com/
' +
+ var notification_text = 'This will create a ' + publicMessage + ' paste of your commit to http://gist.github.com/
' +
deleteMessage +
'Are you sure you want to continue?
' +
'No. Cancel. | ' +
@@ -80,51 +80,129 @@ var confirm_gist = function(confirmation_message) {
$("spinner").style.display = "none";
}
-var gistie = function() {
- notify("Uploading code to Gistie..", 0);
+var storage = {
+ setKey: function(key, value) {
+ return createCookie(key, value, 365 * 10);
+ },
- parameters = {
- "file_ext[gistfile1]": "patch",
- "file_name[gistfile1]": commit.object.subject.replace(/[^a-zA-Z0-9]/g, "-") + ".patch",
- "file_contents[gistfile1]": commit.object.patch(),
- };
+ getKey: function(key) {
+ return readCookie(key);
+ },
+
+ deleteKey: function(key) {
+ return eraseCookie(key);
+ },
+};
+
+// See http://developer.github.com/v3/oauth/
+var gistAuth = function() {
+ var login = Controller.getConfig_("github.user");
+ if (login) {
+ var password = prompt("Enter GitHub password for " + login + " to authroize GitX to post gists:");
+ if (!password || password.length == 0) {
+ return gistie(true);
+ }
+
+ notify("Authenticating...", 0);
+
+ var t = new XMLHttpRequest();
+ t.onload = function() {
+ if (t.status == 201) {
+ try {
+ var jsonResponse = JSON.parse(t.responseText);
+ storage.setKey('oauth2token', jsonResponse.token);
+ gistie();
+ } catch (e) {
+ authFailover("During parse: " + t.responseText);
+ }
+ } else {
+ authFailover("Wrong status code (" + t.status + "): " + t.responseText);
+ }
+ }
+
+ var jsonRequest = {
+ scopes: 'gist',
+ note: 'GitX'
+ };
+
+ t.open('POST', "https://api.github.com/authorizations");
+ t.setRequestHeader('Accept', 'application/json');
+ t.setRequestHeader('Authorization', makeBasicAuth(login, password));
+ t.send(JSON.stringify(jsonRequest));
+ }
+
+ function authFailover(errorMessage) {
+ notify("Authentication failed; creating an anonymous gist.", 0);
+ Controller.log_(errorMessage);
+ setTimeout(function() {
+ gistie(true);
+ }, 1000);
+ }
- // TODO: Replace true with private preference
- token = Controller.getConfig_("github.token");
- login = Controller.getConfig_("github.user");
- if (token && login) {
- parameters.login = login;
- parameters.token = token;
+ function makeBasicAuth(user, password) {
+ var tok = user + ':' + password;
+ var hash = Base64.encode(tok);
+ return "Basic " + hash;
}
- if (!Controller.isFeatureEnabled_("publicGist"))
- parameters.private = true;
- var params = [];
- for (var name in parameters)
- params.push(encodeURIComponent(name) + "=" + encodeURIComponent(parameters[name]));
- params = params.join("&");
+}
+
+var needsGithubPassword = function() {
+ var login = Controller.getConfig_("github.user");
+ var token = storage.getKey('oauth2token');
+
+ return login && !token;
+}
+
+var gistie = function(skipAuth) {
+ if (!skipAuth && needsGithubPassword())
+ return gistAuth();
+
+ notify("Creating a Gist...", 0);
+
+ // See API at http://developer.github.com/v3/gists/
+ var filename = commit.object.subject.replace(/[^a-zA-Z0-9]+/g, "-") + ".patch";
+ var files = {};
+ files[filename] = { content: commit.object.patch() };
+ var postdata = {
+ description: commit.object.subject + " : " + commit.object.realSha(),
+ public: Controller.isFeatureEnabled_("publicGist") ? 'true' : 'false',
+ files: files,
+ };
var t = new XMLHttpRequest();
- t.onreadystatechange = function() {
- if (t.readyState == 4 && t.status >= 200 && t.status < 300) {
- if (m = t.responseText.match(//))
- notify("Code uploaded to gistie #" + m[1] + "", 1);
- else {
- notify("Pasting to Gistie failed :(.", -1);
+ t.onload = function() {
+ if (t.status == 201) {
+ var responseJson = JSON.parse(t.responseText);
+ var gistURL = responseJson.html_url;
+ try {
+ notify("Gist posted: " + gistURL + "", 1);
+ } catch (e) {
+ notify("Gist creation failed: " + e + "; \n" + t.responseText, -1);
Controller.log_(t.responseText);
}
+ } else if (t.status == 401) { // Authentication fail
+ // Clear out our saved credentials, since they're not working
+ storage.deleteKey('oauth2token');
+ gistAuth();
+ } else {
+ notify("Gist creation failed with HTTP " + t.status + ": " + t.responseText, -1);
+ Controller.log_(t.status);
+ Controller.log_(t.responseText);
}
- }
+ };
- t.open('POST', "https://gist.github.com/gists");
- t.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
- t.setRequestHeader('Accept', 'text/javascript, text/html, application/xml, text/xml, */*');
- t.setRequestHeader('Content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
+ t.open('POST', "https://api.github.com/gists");
+ t.setRequestHeader('Accept', 'application/json');
+ var token = storage.getKey('oauth2token');
+ if (token) {
+ t.setRequestHeader('Authorization', 'token ' + token);
+ }
try {
- t.send(params);
+ t.send(JSON.stringify(postdata));
} catch(e) {
- notify("Pasting to Gistie failed: " + e, -1);
+ notify("Failed to send JSON when sending the Gist data: " + e, -1);
}
}
diff --git a/html/views/history/index.html b/html/views/history/index.html
index cc8d4a286..98973884b 100644
--- a/html/views/history/index.html
+++ b/html/views/history/index.html
@@ -4,6 +4,8 @@
+
+