Set proxy for XMLHttpRequest to edit form data - javascript

How can I edit all POST requests from a client? My research says that it should be possible with a proxy object on XMLHttpRequest. How can I inspect a POST request and edit the form data before it gets sent to the server?
I've tried this approach but the data getting sent through is just responses.
var _XMLHttpRequest = XMLHttpRequest;
XMLHttpRequest = function() {
var xhr = new _XMLHttpRequest();
// augment/wrap/modify here
var _open = xhr.open;
xhr.open = function() {
// custom stuff
return _open.apply(this, arguments);
}
return xhr;
}

Here's an IIFE that overloads XMLHttpRequest prototype methods that will allow you to intercept and modify data being sent. I'll leave it up to you to sort out parsing your data
(function(xhr) {
var
proto = xhr.prototype,
_send = proto.send,
_open = proto.open;
// overload open() to access url and request method
proto.open = function() {
// store type and url to use in other methods
this._method = arguments[0];
this._url = arguments[1];
_open.apply(this, arguments);
}
// overload send to intercept data and modify
proto.send = function() {
// using properties stored in open()
if (this._method.toLowerCase() === 'post') {
console.log('USERS DATA :: ', arguments[0]);
console.log('URL :: ', this._url);
// modify data to send
arguments[0] = 'item=beer&id=3';
}
_send.apply(this, arguments);
}
})(XMLHttpRequest);
// use jQuery ajax to demonstrate
$.post('http://httpbin.org/post', { item: 'test', id: 2})
.then(data => console.log('RESPONSE ::', data.form))
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Related

javascript - Intercept and modify XHR response in chrome plugin

I'm writing a plugin that will intercept all the requests and responses and will extract data and if needed also modify the response. Below is the code I'm using to intercept the request, but it seems I can only read the response and not modify it. The code is injected into the page by manifest.json.
(function(xhr)
{
var XHR = XMLHttpRequest.prototype;
var send = XHR.send;
XHR.send = function(postData)
{
this.addEventListener('load', function()
{
if (postData)
{
if (typeof postData === 'string')
{
try
{
this._requestHeaders = postData;
} catch (err)
{
console.log(err);
}
}
else if (typeof postData === 'object' || typeof postData === 'array' || typeof postData === 'number' || typeof postData === 'boolean')
{
var enc = new TextDecoder("utf-8");
requestdata = enc.decode(postData);
console.log("postData");
var json = JSON.parse(requestdata);
console.log(json);
// Extract data from request
var req = this.responseText
// Change req, this does nothing!
this.responseText = req;
}
}
});
return send.apply(this, arguments);
};
})(XMLHttpRequest);
I understand this is because responseText is actually read only, and then property itself returns a cloned string, rather than a reference to actual response text. Is there any way around it? The only other way I see is using CEF, opening a web site from my CEF application and intercepting the requests there, which is nice, I can enhance the web site inside my application, but on the other hand it's cumbersome and I want my users to be able to download the plugin instead of having to use an exe.
Thanks!

Monkey-patching XMLHttpRequest.send for special url

I'm trying to create an http-interceptor that will allow to add header to requests sent from within third-party app. I'm monkey-patching XMLHttpRequest.send
const origSend = window.XMLHttpRequest.prototype.send;
window.XMLHttpRequest.prototype.send = function() {
this.setRequestHeader("A-Header", "Value");
return origSend.apply(this, [].slice.call(arguments));
};
The problem is that I don't need that header in other requests, still I don't see how can I access request url (to check if request is made from third-party lib). How can I make this interceptor work only in case there's a substring in url?
If you also monkey-patch the .open() method, you can store the passed url in the instance and read it later:
const origOpen = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function() {
this.url = arguments[1];
return origOpen.apply(this, [].slice.call(arguments));
};
const origSend = window.XMLHttpRequest.prototype.send;
window.XMLHttpRequest.prototype.send = function() {
if (this.url) {
console.log("url found:", this.url);
this.setRequestHeader("A-Header", "Value");
}
// prevent error in snippet, uncomment next line
// return origSend.apply(this, [].slice.call(arguments));
};
const xhr = new XMLHttpRequest();
xhr.open("get", "https://stackoverflow.com");
xhr.send();

Empty response when pushing arrays to JSON

I can't figure out why my multidimensional array in JSON always is empty in the response. If I declare my JSON static like this..
var data = {
foo: 123,
bar: 456,
cars: [
{ name:"Ford", test: 4},
{ name:"BMW" },
{ name:"Fiat"}
]
};
Response:
(index):78 Success
(index):79 {"foo":123,"bar":456,"cars":[{"name":"Ford","test":4},{"name":"BMW"},{"name":"Fiat"}]}
So this works, but when I add arrays dynamically the response is empty..
var data = {
foo: 123,
bar: 456,
};
data.cars: [];
function getMousePos(e) {
return {x:e.clientX,y:e.clientY};
}
document.onmousemove=function(e) {
var mousePos = getMousePos(e);
data.cars.push({x: mousePos.x, y: mousePos.y});
console.log(JSON.stringify(data));
};
var createCORSRequest = function(method, url) {
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
// Most browsers.
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined") {
// IE8 & IE9
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
// CORS not supported.
xhr = null;
}
return xhr;
};
var url = 'http://localhost:80/t';
var method = 'POST';
var xhr = createCORSRequest(method, url);
xhr.onload = function() {
console.log("Success");
console.log(xhr.responseText);
};
xhr.onerror = function() {
console.log("Error");
};
xhr.send(JSON.stringify(data));
The console before I send..
{"foo":123,"bar":456,"cars":[{"x":320,"y":8},{"x":321,"y":20}]}
The response I get..
(index):79 Success
(index):80 {"foo":123,"bar":456,"cars":[]}
The "cars" array always ends up empty in the response when I push arrays to the JSON string. I have read every stackoverflow thread I can find about this but can't figure out the problem.
Response code on server
public function getJson(Request $request) {
$content = $request->json()->all();
return $content;
}
I should also point out that i'm using Laravel 5.4 on the response server.
I could see 2 mistakes:
Define Cars object like data.cars = []; rather using data.cars: [];
Ajax calls are asynchronous in nature, based on the code which you have written xhr.send will be called before document.onmousemove function.
onmousemove requires mousemove event to trigger but xhr.send is not inside any function and hence getting called as soon as page is getting loaded.
So you will have to make 2 changes:
Define Cars object like data.cars = [];
Call xhr.send method after assignment of data in mousemove function i.e. inside mousemove or other function

overwitting the XMLHttpRequest causes the load failure

I am trying to count the number of ajax calls. I want to do this to wait until all the ajax calls return.
I have written the following code:
var xmlreqc=XMLHttpRequest;
XMLHttpRequest = function() {
this.xhr = new xmlreqc();
return this;
};
XMLHttpRequest.prototype.open = function (method, url, async) {
return this.xhr.open(method, url, async); //send it on
};
XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
this.xhr.setRequestHeader(header, value);
};
XMLHttpRequest.prototype.getAllResponseHeaders = function() {
console.log( this.xhr.getAllResponseHeaders());
return this.xhr.getAllResponseHeaders();
};
XMLHttpRequest.prototype.send = function(postBody) {
// steal the request
nRemAjax++;
// do the real transmission
var myXHR = this;
this.xhr.onreadystatechange = function() { myXHR.onreadystatechangefunction();};
this.xhr.send(postBody);
};
XMLHttpRequest.prototype.onreadystatechangefunction = function()
{
try {
this.readyState = this.xhr.readyState;
this.responseText = this.xhr.responseText;
console.log(this.xhr.responseText); // this line log json data though
this.responseXML = this.xhr.responseXML;
this.status = this.xhr.status;
this.statusText = this.xhr.statusText;
}
catch(e){
}
if (this.onreadystatechange)
this.onreadystatechange();
//do my logging
if (this.xhr.readyState == 4)
{
nRemAjax--;
// only when done steal the response
consoleLog("I'm finished");
}
};
I have injected above code into the browser.
This works fine for most of the Websites, except for http://demo.opencart.com/index.php?route=account/register.
For some reasons, the Region/state field is not loaded properly on page load.
What I found that the Region/Field is populated with JSON data that has been send as a response from ajax call.
Please note that I am adding this script in the head.

Why does onsuccess sometimes get called before onupgradeneeded when connecting to indexedDB?

I am having trouble with IndexedDB. On Firefox 18, when I create a new database, the onsuccess method is called at the same time has onupgradeneeded. On Chrome 24 (this is the behavior I'd like to get), the onsuccess method is only called after the onupgradeneeded method has completed.
According to the MDN information on IndexedDB, I was under the impression that when the onsuccess method was called, it was safe to work with the database but this make it seems like it is not in Firefox.
(function(){
app = {};
// These will hold the data for each store.
app.objectstores = [
{ name: 'UNIVERSITIES',
keyPath: 'UID',
autoIncrement: false,
data_source: 'http://mysites.dev/nddery.ca_www/larelance/data/universite.json' },
];
// Some information pertaining to the DB.
app.indexedDB = {};
app.indexedDB.db = null
app.DB_NAME = 'testdb';
app.DB_VERSION = 1;
/**
* Attempt to open the database.
* If the version has changed, deleted known object stores and re-create them.
* We'll add the data later.
*
*/
app.indexedDB.open = function() {
// Everything is done through requests and transactions.
var request = window.indexedDB.open( app.DB_NAME, app.DB_VERSION );
// We can only create Object stores in a onupgradeneeded transaction.
request.onupgradeneeded = function( e ) {
app.indexedDB.db = e.target.result;
var db = app.indexedDB.db;
// Delete all object stores not to create confusion and re-create them.
app.objectstores.forEach( function( o ) {
if ( db.objectStoreNames.contains( o.name ) )
db.deleteObjectStore( o.name );
var store = db.createObjectStore(
o.name,
{ keyPath: o.keyPath, autoIncrement: o.autoIncrement }
);
app.indexedDB.addDataFromUrl( o.name, o.data_source );
});
}; // end request.onupgradeneeded()
// This method is called before the "onupgradeneeded" has finished..??
request.onsuccess = function( e ) {
app.indexedDB.db = e.target.result;
app.ui.updateStatusBar( 'Database initialized...' );
// ***
// Would like to query the database here but in Firefox the data has not
// always been added at this point... Works in Chrome.
//
}; // end request.onsuccess()
request.onerror = app.indexedDB.onerror;
}; // end app.indexedDB.open()
app.indexedDB.addDataFromUrl = function( store, url ) {
var xhr = new XMLHttpRequest();
xhr.open( 'GET', url, true );
xhr.onload = function( event ) {
if( xhr.status == 200 ) {
console.log('*** XHR successful');
// I would be adding the JSON data to the database object stores here.
}
else{
console.error("addDataFromUrl error:", xhr.responseText, xhr.status);
}
};
xhr.send();
}; // end app.indexedDB.addDataFromUrl()
})();
Thanks!
One of the things you are probably suffering with is the auto-commit functionality in the indexeddb. If an transaction becomes inactive for a short timespan, it will commit the transaction and close it.
In your case you are calling an async method to fill up the data, and that is why the transaction probably becomes inactive. if you add a console.write after app.indexedDB.addDataFromUrl( o.name, o.data_source ); you will see it will get called before your data is retrieved, causing the commit of the transaction. That is why the data isn't present when the success callback is called. It is possible that the timeout for transactions is higher in chrome than in Firefox. It isn't described in the spec so it can vary by vendor.
btw, if you want to add data in your ajax call, you will have to pass the object store as a parameter as well.
do what you have to do inside if( xhr.status == 200 ) {}
call a transaction from there, put the data to the objectStore and what more you need
edit:
I do it like this, works for me, the oncomplete function only gets called once all the data is inserted and the objectStore is ready for use:
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://...", true);
xhr.addEventListener("load", function(){
if(xhr.status === 200){
console.info("Data received with success");
var data = JSON.parse(xhr.responseText);
var transaction = db.transaction([STORe],'readwrite');
var objstore = transaction.objectStore(STORE);
for(i = 0; i < data.length; i++){
objstore.put(data[i]);
};
transaction.oncomplete = function(event){
//do what you need here
console.info("Inserted data: " + data.length);
};
transaction.onerror = function(event){
};
transaction.onabort = function(event){
};
transaction.ontimeout = function(event){
};
transaction.onblocked = function(event){
};
};
}, false);
xhr.send();

Categories

Resources