Can I control the HTTP headers sent by window.open (cross browser)?
If not, can I somehow window.open a page that then issues my request with custom headers inside its popped-up window?
I need some cunning hacks.
Can I control the HTTP headers sent by window.open (cross browser)?
No
If not, can I somehow window.open a page that then issues my request with custom headers inside its popped-up window?
You can request a URL that triggers a server side program which makes the request with arbitrary headers and then returns the response
You can run JavaScript (probably saying goodbye to Progressive Enhancement) that uses XHR to make the request with arbitrary headers (assuming the URL fits within the Same Origin Policy) and then process the result in JS.
I need some cunning hacks...
It might help if you described the problem instead of asking if possible solutions would work.
Sadly you can't control headers when doing window.open()
Nice and easy, how I managed to open a file with custom headers:
const viewFile = async (url) => {
// Change this to use your HTTP client
fetch(url, {/*YOUR CUSTOM HEADER*/} ) // FETCH BLOB FROM IT
.then((response) => response.blob())
.then((blob) => { // RETRIEVE THE BLOB AND CREATE LOCAL URL
var _url = window.URL.createObjectURL(blob);
window.open(_url, "_blank").focus(); // window.open + focus
}).catch((err) => {
console.log(err);
});
};
Download file to cache
window.open to cache
If you are in control of server side, it might be possible to set header value in query string and send it like that?
That way you could parse it from query string if it's not found in the headers.
Just an idea... And you asked for a cunning hack :)
As the best anwser have writed using XMLHttpResponse except window.open, and I make the abstracts-anwser as a instance.
The main Js file is download.js Download-JS
// var download_url = window.BASE_URL+ "/waf/p1/download_rules";
var download_url = window.BASE_URL+ "/waf/p1/download_logs_by_dt";
function download33() {
var sender_data = {"start_time":"2018-10-9", "end_time":"2018-10-17"};
var x=new XMLHttpRequest();
x.open("POST", download_url, true);
x.setRequestHeader("Content-type","application/json");
// x.setRequestHeader("Access-Control-Allow-Origin", "*");
x.setRequestHeader("Authorization", "JWT " + localStorage.token );
x.responseType = 'blob';
x.onload=function(e){download(x.response, "test211.zip", "application/zip" ); }
x.send( JSON.stringify(sender_data) ); // post-data
}
You can also use an F5 load balancer, and map the cross-browser URL that you are trying to fetch to an URL inside your domain of origin.
Mapping can be something like:
companyA.com/api/of/interest----> companyB.com/api/of/interest
Assuming your domain of origin is "companyA.com" then the browser will not have any problems in sending all cookies on the header of that request, since it's towards the same domain.
The request hits the load balancer and is forwarded towards "companyB.com" with all headers responses will be sent to the from server side.
You can't directly add custom headers with window.open() in popup window
but to work that we have two possible solutions
Write Ajax method to call that particular URL with headers in a separate HTML file and use that HTML as url in<i>window.open()</i>
here is abc.html
$.ajax({
url: "ORIGIONAL_URL",
type: 'GET',
dataType: 'json',
headers: {
Authorization : 'Bearer ' + data.id_token,
AuthorizationCheck : 'AccessCode ' +data.checkSum ,
ContentType :'application/json'
},
success: function (result) {
console.log(result);
},
error: function (error) {
} });
call html
window.open('*\abc.html')
here CORS policy can block the request if CORS is not enabled in requested URL.
You can request a URL that triggers a server-side program which makes the request with custom headers and then returns the response redirecting to that particular url.
Suppose in Java Servlet(/requestURL) we'll make this request
`
String[] responseHeader= new String[2];
responseHeader[0] = "Bearer " + id_token;
responseHeader[1] = "AccessCode " + checkSum;
String url = "ORIGIONAL_URL";
URL obj = new URL(url);
HttpURLConnection urlConnection = (HttpURLConnection) obj.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
urlConnection.setRequestProperty("Content-Type", "application/json");
urlConnection.setRequestProperty("Accept", "application/json");
urlConnection.setRequestProperty("Authorization", responseHeader[0]);
urlConnection.setRequestProperty("AuthorizationCheck", responseHeader[1]);
int responseCode = urlConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new
InputStreamReader(urlConnection.getInputStream()));
String inputLine;
StringBuffer response1 = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response1.append(inputLine);
}
in.close();
response.sendRedirect(response1.toString());
// print result
System.out.println(response1.toString());
} else {
System.out.println("GET request not worked");
}
`
call servlet in window.open('/requestURL')
Use POST instead
Although it is easy to construct a GET query using window.open(), it's a bad idea (see below). One workaround is to create a form that submits a POST request. Like so:
<form id="helper" action="###/your_page###" style="display:none">
<inputtype="hidden" name="headerData" value="(default)">
</form>
<input type="button" onclick="loadNnextPage()" value="Click me!">
<script>
function loadNnextPage() {
document.getElementById("helper").headerData.value = "New";
document.getElementById("helper").submit();
}
</script>
Of course you will need something on the server side to handle this; as others have suggested you could create a "proxy" script that sends headers on your behalf and returns the results.
Problems with GET
Query strings get stored in browser history,
can be shoulder-surfed
copy-pasted,
and often you don't want it to be easy to "refresh" the same transaction.
Related
I'm trying to figure out how to send an image to my API, and also verify a generated token that is in the header of the request.
So far this is where I'm at:
#app.post("/endreProfilbilde")
async def endreProfilbilde(request: Request,file: UploadFile = File(...)):
token=request.headers.get('token')
print(token)
print(file.filename)
I have another function that triggers the change listener and upload function, passing the parameter: bildeFila
function lastOpp(bildeFila) {
var myHeaders = new Headers();
let data = new FormData();
data.append('file',bildeFila)
myHeaders.append('token', 'SOMEDATAHERE');
myHeaders.append('Content-Type','image/*');
let myInit = {
method: 'POST',
headers: myHeaders,
cache: 'default',
body: data,
};
var myRequest = new Request('http://127.0.0.1:8000/endreProfilbilde', myInit);
fetch(myRequest)//more stuff here, but it's irrelevant for the Q
}
The Problem:
This will print the filename of the uploaded file, but the token isn't passed and is printed as None. I suspect this may be due to the content-type, or that I'm trying to force FastAPI to do something that is not meant to be doing.
As per the documentation:
Warning: When using FormData to submit POST requests using XMLHttpRequest or the Fetch_API with the
multipart/form-data Content-Type (e.g. when uploading Files and
Blobs to the server), do not explicitly set the Content-Type
header on the request. Doing so will prevent the browser from being
able to set the Content-Type header with the boundary expression
it will use to delimit form fields in the request body.
Hence, you should remove the Content-Type header from your code. The same applies to sending requests through Python Requests, as described here and here. Read more about the boundary in multipart/form-data.
Working examples on how to upload file(s) using FastAPI in the backend and Fetch API in the frontend can be found here, here, as well as here and here.
So I figured this one out thanks to a helpful lad in Python's Discord server.
function lastOpp(bildeFila) {
let data = new FormData();
data.append('file',bildeFila)
data.append('token','SOMETOKENINFO')
}
#app.post("/endreProfilbilde")
async def endreProfilbilde(token: str = Form(...),file: UploadFile = File(...)):
print(file.filename)
print(token)
Sending the string value as part of the formData rather than as a header lets me grab the parameter.
I have a JavaScript-based browser client application that deals with a lot of state data. Occasionally that data must be gathered together into a well-formed object and dumped to a new window that accepts that data for processing.
A solution I have that already functions is the following: when the data needs to be sent, the target URL and data are passed to what is effectively this function, which leverages a virtual form element that submits the data:
function sendJson(targetUrl, jsonData) {
let form = document.createElement("form");
form.method = "POST";
form.action = targetUrl;
form.target = '_blank';
let element = document.createElement("input");
element.value = JSON.stringify(jsonData);
element.name = 'json';
form.appendChild(element);
document.body.appendChild(form);
form.submit();
form.parentNode.removeChild(form);
}
This effectively crams the well-formed data into a single form field and re-encodes it as application/x-www-form-urlencoded. A PHP script awaits on the other end that extracts the data more or less like this:
$json_data = json_decode($_POST['json'], true);
This method has some upsides I like, namely:
The new window has a distinct URL from the parent
The network request belongs to the new window, not the parent window that opened it
These mean I can re-POST the same request over and over by simply refreshing the window and re-sending POST variables, which comes in handy.
What I do not like is that the data over the wire is URL encoded now. My network debugging tools that neatly display JSON-hierarchical data fail to parse it and instead just dump out what it now is -- a single, excessively long string full of encoded symbols. If I ever need to inspect this, I have to run it through a URL decoder and a JSON prettifier, which I find incredibly inconvenient. It would be greatly beneficial if I could send the request as application/json instead of application/x-www-form-urlencoded.
So instead, I tried a solution like this, utilizing window.open():
function sendJson(targetUrl, jsonData) {
fetch(targetUrl, {
method: 'POST',
headers: {
'Content-type': 'application/json',
'Accept': 'text/html'
},
body: JSON.stringify(jsonData)
})
.then(response => response.text())
.then(text => {
let newWin = window.open(targetUrl, '_blank');
newWin.document.open();
newWin.document.write(text);
newWin.document.close();
});
}
And PHP on the other side picks it up like:
$json_data = json_decode(file_get_contents('php://input'), true);
Now the JSON is going over the wire in its native format, solving the encoding problem. But now the call to window.document.open() resets the URL of the new window to the URL of the parent. If the new window is refreshed, it redirects to the parent window. And even if that wasn't the case, there's no POST data to refresh, since the request "belongs" to the parent window. The data has simply been streamed to the parent and written to the new window manually.
What I'm left with are two functional, but less than ideal, solutions. My question is, does a solution exist that gives me all of the features I want?
Pure JavaScript implementation (no libraries, preferably no DOM trickery)
POST data sent as application/json over the wire
Request opens in a new window/tab
New window points to a URL distinct of parent window
New window can be re-POSTed by refreshing that window
No.
Only fetch and XMLHttpRequest can be used to make a request with an application/json body, and the response to those can only be handled with JS.
I want to know if it is possible to open a browser with header authentication by Javascript?
I can assign header to the request in Postman when calling API.
But I can't find if it is reasonable to expect browser can do the same thing?
If yes, any example to trigger the browser with authentication header?
Thanks
Assuming you can get the browser to load a page that runs JavaScript then you can execute follow-on API calls and attach headers to them. There are a variety of frameworks available or you can use the ES6 Fetch API that runs fine in most modern browsers.
Ref Fetch Docs:
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
Ref Headers specifically for Fetch:
https://developer.mozilla.org/en-US/docs/Web/API/Headers
Users could use a local file on their machine which has the script to fetch the data... or you could have users hit an endpoint that requires no authentication and merely serves to host up the page that has the API calls, gathers their credentials and then requests the authenticated data from the protected endpoints.
const myHeaders = new Headers();
myHeaders.set('Authentication', 'basic <insert base64 encoded basic auth string>');
const url = 'https://www.example.com/api/content';
const requestObj = {
method: 'GET',
headers: myHeaders,
mode: 'cors',
cache: 'default'
};
const myRequest = new Request(url, requestObj);
fetch(myRequest)
.then(response => response.json())
.then(jsonObj => {
//do stuff with whatever was returned in the json object
});
If you have an older browser that doesn't support JavaScript at the ES6 version or later, then you can revert to the older XMLHttpRequest.
Ref XMLHttpRequest Docs:
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest
var url = 'https://www.example.com/api/content';
var getRequest = new XMLHttpRequest();
getRequest.open('GET', url, false);
getRequest.setRequestHeader("Authentication", ''basic <insert base64 encoded basic auth string>'');
getRequest.send(null);
if (getRequest.status == 200){
var textData = req.responseText;
// do stuff with the response data
}
That is a VERY basic use case for XMLHttpRequest... you should definitely read the docs on setting it up to use call back functions instead of operating synchronously as in the example above. But this should give you enough detail to move forward
Is there a way to check if the page has changed when the page is on the local filesystem (file:/// not http:// or https://)?
There are answers here that use AJAX to check the headers for a "Last-Modified" header but this is not returned when using the file protocol.
Here's the headers returned in Firefox when testing locally:
// headers
Content-Type: text/xml
Content-Length: 20941
Update:
It looks like the response is set to the file and that has a lastModified property on response. I've added an answer.
Based off of your replies in the comments, I would use some form of caching the page.
Check this answer to get the page's current contents as a string and cache it/a hash of it. From there, perform an AJAX request to the current page and compare the contents/hashes. If they do not match, then you can reload the page.
let contents = document.documentElement.outerHTML;
let frequency = 5000; // How often to check for changes (ms)
setInterval(() => {
let xmlhttp = new XMLHttpRequest();
xmlhttp.addEventListener("load", (res) => {
if(res.responseText != contents)
window.location.reload(true);
});
xmlhttp.open("GET", window.location.href);
xmlhttp.send();
}, frequency);
What you could do is change the request header from HEAD to GET/POST, which'll return a responseText value. From there, it's very easy to check - have a value before the GET/POST call named original or something similar, compare it to the response, and change stuff if necessary.
After some debugging I noticed that the response is set to the file in the response property and that has a lastModified property on it!
Modifiying the code in #bugfroggy's answer this seems to work:
function checkForChanges() {
var contentDate = null;
var frequency = 5000;
setInterval(() => {
let xmlhttp = new XMLHttpRequest();
xmlhttp.addEventListener("load", (progressEvent) => {
console.log("Content loaded()");
if (contentDate==null) {
contentDate = xmlhttp.response.lastModified;
return;
}
if(xmlhttp.response.lastModified != contentDate) {
window.location.reload(true);
}
});
//xmlhttp.open("GET", window.location.href);
xmlhttp.open("HEAD", window.location.href);
//xmlhttp.responseType = "blob";
xmlhttp.send();
}, frequency);
}
checkForChanges();
Note: I think the code above uses ES5 or ES6 features and I don't know if this will work on a server (http:// or https://).
Use a dev webserver that auto reloads on changes, better to test with a web server over serving from the file system anyway.
I am attempting to download a file from Google Storage using the Javascript json api. I am able to retreive the object info by using the code below, however I'm not sure how to get the actual media. I'm familiar with the Java library method getMediaHttpDownloader, but I do not see an equivalent in JS. Any help would be appreciated!
gapi.client.storage.objects.get({"bucket":"bucketName","object":"objectName"});
The Javascript library does not currently support directly downloading media. You can still get to the data, but you'll have to access it another way.
Depending on the domain your website is hosted on and the bucket you're reading from, you'll need to set up CORS: https://developers.google.com/storage/docs/cross-origin
Then, you'll need to request the object directly via the XML API. For example, you could do something like this:
var accessToken = gapi.auth.getToken().access_token;
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://'+bucket+'.storage.googleapis.com/'+object);
xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
xhr.send();
I've ended up not using the api(not sure that you can download using api, interested if you do know how) and using XmlHttpRequest instead. To do this I had to setup CORS for my google storage bucket to allow my site cross domain access. Below is my code:
var myToken = gapi.auth.getToken();
var req = new XMLHttpRequest;
req.open('GET','https://storage.googleapis.com/bucket/object',
true);
req.setRequestHeader('Authorization', 'Bearer ' + myToken.access_token);
req.send(null);
I did it using gapi and jQuery.
In my case object is public. (pulbic link in storage browser must be checked). In case you don't want your object to be public, use $.post instead of $.get and provide assess_token as header exactly as it is done in other answers.
Storage.getObjectInfo retrieves object metadata.
Storage.getObjectMedia retrieves object content.
var Storage = function() {};
Storage.bucket = 'mybucket';
Storage.object = 'myfolder/myobject'; //object name, got by gapi.objects.list
Storage.getObjectMedia = function(object, callback) {
function loadObject(objectInfo) {
var mediaLink = objectInfo.mediaLink;
$.get(mediaLink, function(data) { //data is actually object content
console.log(data);
callback(data);
});
}
Storage.getObjectInfo(object, loadObject);
};
Storage.getObjectInfo = function(object, callback) {
var request = gapi.client.storage.objects.get({
'bucket' : Storage.bucket,
'object' : Storage.object
});
request.execute(function(resp) {
console.log(resp);
callback(resp);
});
};
It is also relatively rare case when we need to download the content of object. In most cases objects stored in Storage are media files like images and sounds and then all what we need is actually mediaLink, which must be inserted to src attribute value of appropriate dom element (img or audio).