├── .gitignore ├── HttpRequester.iml ├── README.md ├── build ├── build.xml └── launchFFSavePID.sh └── src ├── chrome.manifest ├── chrome └── icons │ └── default │ ├── httprequester-window.ico │ └── httprequester-window.xpm ├── content ├── Base64.js ├── GoogleLogin.js ├── HTTP.js ├── Response.js ├── about.xul ├── google-login.xul ├── httprequester-window.xul ├── httprequester.js ├── loadRequest.js ├── loadRequest.xul ├── overlay.js ├── overlay.xul ├── progress.xul ├── request-entry.js └── request-entry.xul ├── defaults └── preferences │ └── defaults.js ├── install.rdf ├── readme.txt └── skin ├── default.css ├── httprequester.png ├── httprequester16x16.png └── overlay.css /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /HttpRequester.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HttpRequester 2 | ============= 3 | HttpRequester is a tool for Firefox for easily making HTTP requests (GET/PUT/POST/DELETE), viewing the responses, and keeping a history of transactions. 4 | 5 | This tool is useful when doing web or REST development, or when you need to make HTTP requests that are not easily done via the browser (PUT/POST/DELETE). 6 | 7 | This is based off of Alex Milowski's excellent Poster addon, with a large focus on keeping a history of transactions, allowing you to go back and review, re-execute, load, and save HTTP requests. 8 | 9 | Developed by Tom Mutdosch 10 | 11 | Distributed under the BSD License 12 | http://www.opensource.org/licenses/bsd-license.php 13 | 14 | Overview 15 | 25 | Usage 26 | 36 | Advanced Preferences: (via about:config): 37 | Increasing number of items in history 38 | extensions.httprequester.maxhistory - maximum number of requests to keep 39 | extensions.httprequester.url.maxhistory - maximum number of URLs to keep 40 | extensions.httprequester.contenttype.maxhistory - maximum number of content types to keep 41 | extensions.httprequester.header.maxhistory - maximum number of header names to keep 42 | 43 |
  • You can add custom methods to the list of available HTTP methods. 44 | To add new Methods: enter "about:config" in your URL bar. Then filter on: 45 | extensions.httprequester.http.methods.custom.write 46 | 47 | Double-click the value to modify it. You can change it to a list of comma-separated values like: ["PROPFIND", "PATCH"] 48 | You can also add read-only custom methods too (these methods will not send any entity body) via extensions.httprequester.http.methods.custom.read
    49 | 50 | If you have any questions/comments/suggestions, shoot me a note. 51 | 52 | -------------------------------------------------------------------------------- /build/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /build/launchFFSavePID.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | value=`cat /Users/tommut/dev/ff-addons/pid.txt` 4 | echo "$value" 5 | 6 | kill -9 $value 7 | 8 | /Applications/Firefox.app/Contents/MacOS/firefox-bin -p reminderfox-dev & echo $! > /Users/tommut/dev/ff-addons/pid.txt 9 | -------------------------------------------------------------------------------- /src/chrome.manifest: -------------------------------------------------------------------------------- 1 | content httprequester content/ 2 | overlay chrome://browser/content/browser.xul chrome://httprequester/content/overlay.xul 3 | 4 | locale httprequester en-US locale/en-US/ 5 | 6 | skin httprequester classic/1.0 skin/ 7 | style chrome://global/content/customizeToolbar.xul chrome://httprequester/skin/overlay.css 8 | -------------------------------------------------------------------------------- /src/chrome/icons/default/httprequester-window.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tommut/HttpRequester/d62a6ea6f32406ab44d494458e652c189d2d5fe0/src/chrome/icons/default/httprequester-window.ico -------------------------------------------------------------------------------- /src/chrome/icons/default/httprequester-window.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static char * httprequester_xpm[] = { 3 | "32 32 3 1", 4 | " c None", 5 | ". c #00A900", 6 | "+ c #F20000", 7 | " ", 8 | " ... ", 9 | " ...... ", 10 | " ........ ", 11 | " ....................... ", 12 | " .......................... ", 13 | " .......................... ", 14 | " ....................... ", 15 | " ........ ", 16 | " ...... ", 17 | " .... ", 18 | " ", 19 | " ", 20 | " ", 21 | " ", 22 | " ", 23 | " ", 24 | " ", 25 | " ", 26 | " ", 27 | " + ", 28 | " +++ ", 29 | " +++++ ", 30 | " +++++++ ", 31 | " +++++++++++++++++++++++ ", 32 | " +++++++++++++++++++++++++ ", 33 | " +++++++++++++++++++++++ ", 34 | " +++++++ ", 35 | " +++++ ", 36 | " ++++ ", 37 | " + ", 38 | " "}; 39 | -------------------------------------------------------------------------------- /src/content/Base64.js: -------------------------------------------------------------------------------- 1 | function Base64() { 2 | this._keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 3 | } 4 | 5 | Base64.prototype.encode = function(input) { 6 | var output = ""; 7 | var chr1, chr2, chr3, enc1, enc2, enc3, enc4; 8 | var i = 0; 9 | 10 | //input = this._utf8_encode(input); 11 | 12 | while (i < input.length) { 13 | 14 | chr1 = input.charCodeAt(i++); 15 | chr2 = input.charCodeAt(i++); 16 | chr3 = input.charCodeAt(i++); 17 | 18 | enc1 = chr1 >> 2; 19 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 20 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 21 | enc4 = chr3 & 63; 22 | 23 | if (isNaN(chr2)) { 24 | enc3 = enc4 = 64; 25 | } else if (isNaN(chr3)) { 26 | enc4 = 64; 27 | } 28 | 29 | output = output + 30 | this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + 31 | this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4); 32 | 33 | } 34 | 35 | return output; 36 | } 37 | 38 | Base64.prototype.decode = function(input) { 39 | var output = ""; 40 | var chr1, chr2, chr3; 41 | var enc1, enc2, enc3, enc4; 42 | var i = 0; 43 | 44 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 45 | 46 | while (i < input.length) { 47 | 48 | enc1 = this._keyStr.indexOf(input.charAt(i++)); 49 | enc2 = this._keyStr.indexOf(input.charAt(i++)); 50 | enc3 = this._keyStr.indexOf(input.charAt(i++)); 51 | enc4 = this._keyStr.indexOf(input.charAt(i++)); 52 | 53 | chr1 = (enc1 << 2) | (enc2 >> 4); 54 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 55 | chr3 = ((enc3 & 3) << 6) | enc4; 56 | 57 | output = output + String.fromCharCode(chr1); 58 | 59 | if (enc3 != 64) { 60 | output = output + String.fromCharCode(chr2); 61 | } 62 | if (enc4 != 64) { 63 | output = output + String.fromCharCode(chr3); 64 | } 65 | 66 | } 67 | 68 | //output = this._utf8_decode(output); 69 | 70 | return output; 71 | } 72 | 73 | Base64.prototype._utf8_encode = function (string) { 74 | string = string.replace(/\r\n/g,"\n"); 75 | var utftext = ""; 76 | 77 | for (var n = 0; n < string.length; n++) { 78 | 79 | var c = string.charCodeAt(n); 80 | 81 | if (c < 128) { 82 | utftext += String.fromCharCode(c); 83 | } 84 | else if((c > 127) && (c < 2048)) { 85 | utftext += String.fromCharCode((c >> 6) | 192); 86 | utftext += String.fromCharCode((c & 63) | 128); 87 | } 88 | else { 89 | utftext += String.fromCharCode((c >> 12) | 224); 90 | utftext += String.fromCharCode(((c >> 6) & 63) | 128); 91 | utftext += String.fromCharCode((c & 63) | 128); 92 | } 93 | 94 | } 95 | 96 | return utftext; 97 | } 98 | 99 | // private method for UTF-8 decoding 100 | Base64.prototype._utf8_decode = function (utftext) { 101 | var string = ""; 102 | var i = 0; 103 | var c = c1 = c2 = 0; 104 | 105 | while ( i < utftext.length ) { 106 | 107 | c = utftext.charCodeAt(i); 108 | 109 | if (c < 128) { 110 | string += String.fromCharCode(c); 111 | i++; 112 | } 113 | else if((c > 191) && (c < 224)) { 114 | c2 = utftext.charCodeAt(i+1); 115 | string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); 116 | i += 2; 117 | } 118 | else { 119 | c2 = utftext.charCodeAt(i+1); 120 | c3 = utftext.charCodeAt(i+2); 121 | string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); 122 | i += 3; 123 | } 124 | 125 | } 126 | 127 | return string; 128 | } 129 | -------------------------------------------------------------------------------- /src/content/GoogleLogin.js: -------------------------------------------------------------------------------- 1 | var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; 2 | var GoogleLogin = { 3 | data: null, 4 | init: function(data) { 5 | this.data = data; 6 | document.getElementById('username').value = data.username; 7 | document.getElementById('password').value = data.password; 8 | document.getElementById('service').value = data.service; 9 | }, 10 | authenticate: function() { 11 | //document.getElementById('failure').value = ""; 12 | var current = this; 13 | var service = document.getElementById('service').value; 14 | if (!service || service.length==0) { 15 | this.showError("The service is missing (e.g. blogger)."); 16 | return; 17 | } 18 | var username = document.getElementById('username').value; 19 | if (!username || username.length==0) { 20 | this.showError("The username is missing."); 21 | return; 22 | } 23 | var password = document.getElementById('password').value; 24 | if (!password) { 25 | password = ""; 26 | } 27 | var loginBody = "service="+service+"&Email="+username+"&Passwd="+password; 28 | HTTP("POST","https://www.google.com/accounts/ClientLogin",{ 29 | timeout: 30*1000, 30 | contentType: "application/x-www-form-urlencoded", 31 | onSuccess: function(status,doc,text) { 32 | var regex = /Auth=.+/; 33 | var match = regex.exec(text); 34 | var auth=match[0].substring(5); 35 | current.onSuccess(auth); 36 | }, 37 | onFailure: function(status,doc,text) { 38 | current.data.success = false; 39 | if (text.length>30) { 40 | text = text.substring(0,30)+"..."; 41 | } 42 | current.showError("Authnetication failed: ("+status+") "+text); 43 | }, 44 | body: loginBody 45 | }); 46 | }, 47 | showError: function(msg) { 48 | document.getElementById('failure').value = msg; 49 | }, 50 | onSuccess: function(auth) { 51 | this.data.auth = auth; 52 | this.data.success = true; 53 | this.data.username = document.getElementById('username').value; 54 | this.data.password = document.getElementById('password').value; 55 | this.data.service = document.getElementById('service').value; 56 | setTimeout(function(){ window.close(); },200); 57 | }, 58 | cancel: function() { 59 | this.data.success = false; 60 | this.data.username = document.getElementById('username').value; 61 | this.data.password = document.getElementById('password').value; 62 | this.data.service = document.getElementById('service').value; 63 | window.close(); 64 | } 65 | 66 | 67 | 68 | } -------------------------------------------------------------------------------- /src/content/HTTP.js: -------------------------------------------------------------------------------- 1 | var _HTTP_HEADER_NAME = new RegExp("^([a-zA-Z0-9_-]+):"); 2 | 3 | function _HTTP_parseHeaders(headerText) 4 | { 5 | var headers = {}; 6 | if (headerText) { 7 | var eol = headerText.indexOf("\n"); 8 | while (eol>=0) { 9 | var line = headerText.substring(0,eol); 10 | headerText = headerText.substring(eol+1); 11 | while (headerText.length>0 && !headerText.match(_HTTP_HEADER_NAME)) { 12 | eol = headerText.indexOf("\n"); 13 | var nextLine = eol<0 ? headerText : headerText.substring(0,eol); 14 | line = line+' '+nextLine; 15 | headerText = eol<0 ? "" : headerText.substring(eol+1); 16 | } 17 | // Parse the name value pair 18 | var colon = line.indexOf(':'); 19 | var name = line.substring(0,colon); 20 | var value = line.substring(colon+1); 21 | headers[name] = value; 22 | eol = headerText.indexOf("\n"); 23 | } 24 | if (headerText.length>0) { 25 | var colon = headerText.indexOf(':'); 26 | var name = headerText.substring(0,colon); 27 | var value = headerText.substring(colon+1); 28 | headers[name] = value; 29 | } 30 | } 31 | return headers; 32 | } 33 | 34 | /** 35 | * The following keys can be sent: 36 | * onSuccess (required) a function called when the response is 2xx 37 | * onFailure a function called when the response is not 2xx 38 | * username The username for basic auth 39 | * password The password for basic auth 40 | * overrideMimeType The mime type to use for non-XML response mime types 41 | * timeout A timeout value in milliseconds for the response 42 | * onTimeout A function to call if the request times out. 43 | * body A string containing the entity body of the request 44 | * contentType The content type of the entity body of the request 45 | * headers A hash of optional headers 46 | */ 47 | function HTTP(method,url,options) 48 | { 49 | var requester = new XMLHttpRequest(); 50 | var timeout = null; 51 | if (!options.synchronizedRequest) { 52 | 53 | requester.onreadystatechange = function() { 54 | switch (requester.readyState) { 55 | case 0: 56 | if (options.onUnsent) { 57 | options.onUnsent(requester); 58 | } 59 | break; 60 | case 1: 61 | if (options.onOpened) { 62 | options.onOpened(requester); 63 | } 64 | break; 65 | case 2: 66 | if (options.onHeaders) { 67 | options.onHeaders(requester); 68 | } 69 | break; 70 | case 3: 71 | if (options.onLoading) { 72 | options.onLoading(requester); 73 | } 74 | break; 75 | case 4: 76 | if (timeout) { 77 | clearTimeout(timeout); 78 | } 79 | if (requester.status==0 || (requester.status>=200 && requester.status<300)) { 80 | options.onSuccess( 81 | requester.status, 82 | requester.responseXML, 83 | requester.responseText, 84 | options.returnHeaders ? _HTTP_parseHeaders(requester.getAllResponseHeaders()) : null, 85 | requester.statusText, 86 | options.id 87 | ); 88 | } else { 89 | if (options.onFailure) { 90 | options.onFailure( 91 | requester.status, 92 | requester.responseXML, 93 | requester.responseText, 94 | options.returnHeaders ? _HTTP_parseHeaders(requester.getAllResponseHeaders()) : null, 95 | requester.statusText, 96 | options.id 97 | ); 98 | } 99 | } 100 | break; 101 | } 102 | } 103 | } 104 | 105 | if (options.overrideMimeType) { 106 | requester.overrideMimeType(options.overrideMimeType); 107 | } 108 | 109 | // username/password can not accurately be set on XMLHttpRequest parameter, as it is not 110 | // fully supported; instead the Authorization header is added previously 111 | options.username = null; 112 | options.password = null; 113 | // for (var key in options.headers) { 114 | // alert("key: " + key + " = " + options.headers[key]) 115 | // } 116 | 117 | 118 | if (options.username) { 119 | requester.open(method,url,!options.synchronizedRequest,options.username,options.password); 120 | } else { 121 | requester.open(method,url,!options.synchronizedRequest); 122 | } 123 | if (options.timeout && !options.synchronizedRequest) { 124 | timeout = setTimeout( 125 | function() { 126 | var callback = options.onTimeout ? options.onTimeout : options.onFailure; 127 | callback(0,"Operation timeout."); 128 | }, 129 | options.timeout 130 | ); 131 | } 132 | if (options.headers) { 133 | for (var name in options.headers) { 134 | requester.setRequestHeader(name,options.headers[name]); 135 | } 136 | } 137 | if (options.body || (options.contentType != null && options.contentType.length > 0)) { 138 | requester.setRequestHeader("Content-Type",options.contentType); 139 | } 140 | if (options.body) { 141 | requester.send(options.body); 142 | } else { 143 | requester.send(null); 144 | } 145 | if (options.synchronizedRequest) { 146 | if (requester.status==0 || (requester.status>=200 && requester.status<300)) { 147 | 148 | options.onSuccess( 149 | requester.status, 150 | requester.responseXML, 151 | requester.responseText, 152 | options.returnHeaders ? _HTTP_parseHeaders(requester.getAllResponseHeaders()) : null, 153 | requester.statusText, 154 | options.id 155 | ); 156 | } else { 157 | if (options.onFailure) { 158 | options.onFailure( 159 | requester.status, 160 | requester.responseXML, 161 | requester.responseText, 162 | options.returnHeaders ? _HTTP_parseHeaders(requester.getAllResponseHeaders()) : null, 163 | requester.statusText, 164 | options.id 165 | ); 166 | } 167 | } 168 | return { 169 | abort: function() { 170 | } 171 | }; 172 | } else { 173 | return { 174 | abort: function() { 175 | clearTimeout(timeout); 176 | requester.abort(); 177 | } 178 | }; 179 | } 180 | } 181 | 182 | 183 | -------------------------------------------------------------------------------- /src/content/Response.js: -------------------------------------------------------------------------------- 1 | var Response = { 2 | init: function(data) { 3 | 4 | // create a new response object 5 | 6 | // add it to the Transactions array 7 | 8 | // update the transactions list with last entry (and remove oldest entry) 9 | 10 | // select latest entry in the list; should populate request/response sections based on that. 11 | var titleE = document.getElementById("title"); 12 | while (titleE.hasChildNodes()) { 13 | titleE.removeChild(titleE.firstChild); 14 | } 15 | 16 | titleE.appendChild(document.createTextNode(data.title)); 17 | this.setResponseStatus(data.status+" "+data.statusText); 18 | this.setResponseContent(data.content, data.responseHeaders['Content-Type'], data.url); 19 | 20 | var grid = document.getElementById("headers"); 21 | while (grid.hasChildNodes()) { 22 | grid.removeChild(grid.firstChild); 23 | } 24 | for (var name in data.responseHeaders) { 25 | this.addResponseHeader(name,data.responseHeaders[name]); 26 | } 27 | }, 28 | setResponseStatus: function(status) { 29 | document.getElementById("code").value =status ? status : ""; 30 | }, 31 | 32 | formatXmlFast : function(xml) { 33 | var formatted = ''; 34 | var reg = /(>)(<)(\/*)/g; 35 | xml = xml.toString().replace(reg, '$1\r\n$2$3'); 36 | var pad = 0; 37 | var nodes = xml.split('\r\n'); 38 | for(var n in nodes) { 39 | var node = nodes[n]; 40 | var indent = 0; 41 | if (node.match(/.+<\/\w[^>]*>$/)) { 42 | indent = 0; 43 | } else if (node.match(/^<\/\w/)) { 44 | if (pad !== 0) { 45 | pad -= 1; 46 | } 47 | } else if (node.match(/^<\w[^>]*[^\/]>.*$/)) { 48 | indent = 1; 49 | } else { 50 | indent = 0; 51 | } 52 | var padding = ''; 53 | for (var i = 0; i < pad; i++) { 54 | padding += ' '; // use 4 spaces for tab padding 55 | } 56 | formatted += padding + node + '\r\n'; 57 | pad += indent; 58 | } 59 | return formatted; 60 | //return formatted.replace(/&/g,'&').replace(//g,'>').replace(/ /g, ' '); 61 | }, 62 | 63 | formatXml : function (xml) { 64 | var reg = /(>)(<)(\/*)/g; 65 | var wsexp = / *(.*) +\n/g; 66 | var contexp = /(<.+>)(.+\n)/g; 67 | xml = xml.replace(reg, '$1\n$2$3').replace(wsexp, '$1\n').replace(contexp, '$1\n$2'); 68 | var pad = 0; 69 | var formatted = ''; 70 | var lines = xml.split('\n'); 71 | var indent = 0; 72 | var lastType = 'other'; 73 | // 4 types of tags - single, closing, opening, other (text, doctype, comment) - 4*4 = 16 transitions 74 | var transitions = { 75 | 'single->single': 0, 76 | 'single->closing': -1, 77 | 'single->opening': 0, 78 | 'single->other': 0, 79 | 'closing->single': 0, 80 | 'closing->closing': -1, 81 | 'closing->opening': 0, 82 | 'closing->other': 0, 83 | 'opening->single': 1, 84 | 'opening->closing': 0, 85 | 'opening->opening': 1, 86 | 'opening->other': 1, 87 | 'other->single': 0, 88 | 'other->closing': -1, 89 | 'other->opening': 0, 90 | 'other->other': 0 91 | }; 92 | 93 | for (var i = 0; i < lines.length; i++) { 94 | var ln = lines[i]; 95 | // ignore first line if it is a standalone directive 96 | if ( i == 0 && ln.indexOf( "/)); // is this line a single tag? ex.
    102 | var closing = Boolean(ln.match(/<\/.+>/)); // is this a closing tag? ex. 103 | var opening = Boolean(ln.match(/<[^!].*>/)); // is this even a tag (that's not ) 104 | var type = single ? 'single' : closing ? 'closing' : opening ? 'opening' : 'other'; 105 | var fromTo = lastType + '->' + type; 106 | lastType = type; 107 | var padding = ''; 108 | 109 | indent += transitions[fromTo]; 110 | for (var j = 0; j < indent; j++) { 111 | padding += '\t'; 112 | } 113 | if (fromTo == 'opening->closing') 114 | formatted = formatted.substr(0, formatted.length - 1) + ln + '\n'; // substr removes line break (\n) from prev loop 115 | else 116 | formatted += padding + ln + '\n'; 117 | } 118 | 119 | return formatted; 120 | }, 121 | 122 | getFileExtensionFromRequestUri: function (requestUrl) { 123 | var fileExtension = null; 124 | var checkFileExtension = App.getPreferenceBool("checkFileExtensionForRenderType"); 125 | if (checkFileExtension && requestUrl != null) { 126 | var index = requestUrl.lastIndexOf("/"); 127 | if (index > -1) { 128 | var lastRequestSegment = requesturl.substring(index); 129 | index = lastRequestSegment.lastIndexOf("."); 130 | if (index > -1 && index < lastRequestSegment.length - 1) { 131 | fileExtension = lastRequestSegment.substring(index + 1); 132 | } 133 | } 134 | } 135 | }, 136 | getFileExtensionFromContentType: function (contentType) { 137 | // determine file extension from content type 138 | var fileExtension = null; 139 | //alert( "Content type: " + contentType ) 140 | if (contentType && contentType.search('json') > -1) { 141 | fileExtension = "json"; 142 | } 143 | else if (contentType && contentType.search('xml') > -1) { 144 | fileExtension = "xml"; 145 | } 146 | else if (contentType && contentType.search('html') > -1) { 147 | fileExtension = "html"; 148 | } 149 | else { 150 | fileExtension = "txt"; 151 | } 152 | //alert( "RETURNING THIS: " + fileExtension ); 153 | return fileExtension; 154 | }, 155 | 156 | saveResponseToFile: function (fileExtension, formattedContent) { 157 | // write out to file 158 | var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsIFile); 159 | 160 | var xFile = Components.classes["@mozilla.org/file/directory_service;1"] 161 | .getService(Components.interfaces.nsIProperties) 162 | .get("ProfD", Components.interfaces.nsIFile); 163 | xFile.append("httpRequester"); 164 | xFile.append("httprequester.response." + fileExtension); 165 | var defaultFilePath = xFile.path; 166 | file.initWithPath(defaultFilePath); 167 | if (file.exists() === false) { 168 | file.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 420); 169 | } 170 | this.writeStringToFile(formattedContent, file); 171 | 172 | return defaultFilePath; 173 | }, 174 | 175 | log : function( msg ) { 176 | var cs1 = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService); 177 | cs1.logStringMessage("httprequester: " + msg); 178 | console.error(msg) 179 | }, 180 | 181 | 182 | setResponseContent: function(content, contentType, requestUrl) { 183 | var renderWithBrowser = App.getPreferenceBool("renderResponseBrowser"); 184 | if ( renderWithBrowser ) { 185 | // if pref is set to checkFileExtension (user may want to turn this off if requests end 186 | // in .something and want to use the content type always instead), then look for a file 187 | // extension. If there is one, just write the file out to that. 188 | var fileExtension = this.getFileExtensionFromRequestUri(requestUrl); 189 | if ( fileExtension == null ) { 190 | fileExtension = this.getFileExtensionFromContentType(contentType); 191 | } 192 | 193 | // if there's no actual content, default to txt 194 | if ( content == null || content.length == 0 ) { 195 | fileExtension = "txt"; 196 | } 197 | 198 | var filePath = this.saveResponseToFile(fileExtension, content); 199 | 200 | // need to first change the url (we user a dummy url); otherwise, if navigating 201 | // between two XML or HTML files, the URL would be the same and changing it would 202 | // do nothing 203 | document.getElementById("browserIframe").setAttribute("src","file://tmp" ); 204 | document.getElementById("browserIframe").setAttribute("src","file://" + filePath); 205 | 206 | // we do the process again, as it seems on Windows when just setting the src the first 207 | // time it would sometimes renders as text (all the text from all xml elements) and not 208 | // as XML. Clearing the src and resetting it a second time seems to always work. 209 | document.getElementById("browserIframe").setAttribute("src","file://tmp" ); 210 | document.getElementById("browserIframe").setAttribute("src","file://" + filePath); 211 | // this.log("request: " + requestUrl + "\nIframe: " + document.getElementById("browserIframe").getAttribute("src") + " \nfilepath: " + filePath); 212 | } 213 | else { 214 | // output as Text 215 | formattedContent = content; 216 | if (formattedContent != null && formattedContent.length > 0) { 217 | var prettyPrint = App.getPreferenceBool("prettyPrintResponse"); 218 | if (prettyPrint) { 219 | // if pretty print is on, format the response before displaying in 220 | // response content field 221 | if (contentType && contentType.search(/.*\/.*json/i) > -1) { 222 | formattedContent = JSON.stringify(JSON.parse(content), null, 4); 223 | } 224 | else if (contentType && contentType.search(/.*\/.*xml/i) > -1) { 225 | // the formatXml method does a better job but does not perform as well as formatXmlFast 226 | var fastPrettyPrint = App.getPreferenceBool("useFastXMLPrettyPrint"); 227 | if ( fastPrettyPrint ) { 228 | formattedContent = this.formatXmlFast(content); 229 | } 230 | else { 231 | formattedContent = this.formatXml(content); 232 | } 233 | } 234 | // not currently pretty formatting HTML. Not sure if it makes sense to do so. 235 | // else if (contentType && contentType.search('text/html') > -1) { 236 | // // the formatXml method does a better job but does not perform as well 237 | // //formattedContent = this.formatXml(content); 238 | // formattedContent = this.formatXmlFast(content); 239 | // } 240 | } 241 | } 242 | 243 | // update the response content text field 244 | document.getElementById("response-content").value = formattedContent ? formattedContent : ""; 245 | } 246 | }, 247 | 248 | writeStringToFile : function(outputStr, file){ 249 | var outputStream = Components.classes["@mozilla.org/network/file-output-stream;1"] 250 | .createInstance(Components.interfaces.nsIFileOutputStream); 251 | outputStream.init(file, 0x04 | 0x08 | 0x20, 420, 0); 252 | 253 | var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"] 254 | .createInstance(Components.interfaces.nsIScriptableUnicodeConverter); 255 | converter.charset = "UTF-8"; 256 | 257 | var chunk = null; 258 | try { 259 | chunk = converter.ConvertFromUnicode(outputStr); 260 | } 261 | catch (e) { 262 | chunk = outputStr; 263 | } 264 | outputStream.write(chunk, chunk.length); 265 | 266 | var fin = converter.Finish(); 267 | if (fin.length > 0) 268 | outputStream.write(fin, fin.length); 269 | outputStream.close(); 270 | 271 | }, 272 | 273 | addResponseHeader: function(name,value) { 274 | var grid = document.getElementById("headers"); 275 | var row = grid.ownerDocument.createElement("row"); 276 | grid.appendChild(row); 277 | var nameLabel = row.ownerDocument.createElement("label"); 278 | nameLabel.setAttribute("value",name); 279 | row.appendChild(nameLabel); 280 | var valueLabel = row.ownerDocument.createElement("textbox"); 281 | valueLabel.setAttribute("value",value); 282 | row.appendChild(valueLabel); 283 | }, 284 | clearResponseview: function() { 285 | var titleE = document.getElementById("title"); 286 | while (titleE.hasChildNodes()) { 287 | titleE.removeChild(titleE.firstChild); 288 | } 289 | this.setResponseStatus(null); 290 | this.setResponseContent(null); 291 | 292 | var grid = document.getElementById("headers"); 293 | while (grid.hasChildNodes()) { 294 | grid.removeChild(grid.firstChild); 295 | } 296 | } 297 | 298 | } -------------------------------------------------------------------------------- /src/content/about.xul: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | HttpRequester 8 | 9 | 10 | 11 | Tom Mutdosch 12 | 13 | 14 | 15 | 16 | 17 | Alex Milowski is the creator of the excellent Poster addon, which is the basis for HttpRequester. 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |