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 @@ + +