local xmlhttprequest from sandboxed page in chrome packaged app - javascript

I have a packaged app that embeds a app-local page in an iframe (to embed libraries doing forbidden stuff).
My sandboxed page wants to make an xmlhttprequest to a relative URL (so still in the same extension), but it is refused with the following message:
XMLHttpRequest cannot load
chrome-extension://nilibhiopchihkgnnecfblfjegmogpgn/libs/fonts/miss_fajardose/MissFajardose-Regular.ttf.
No 'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'null' is therefore not allowed access.
To tell the truth, I found the relevant documentations:
http://developer.chrome.com/apps/manifest/sandbox.html
http://www.whatwg.org/specs/web-apps/current-work/multipage/the-iframe-element.html#attr-iframe-sandbox
But they make no sense to me, maybe it's the mix of colors and ADD.
A bit of context: I am using the same code inside my chrome web app and on the internet, in this instance I am loading a font, typesetting something and computing a toolpath for contouring the text. If the page is the chrome app there is a button to send it to the router, if it is on the web you can just see the toolpath.

After about a half day of my life poking around, here's the best workaround I found. From sandboxed page, postmessage to parent asking for local file to be loaded:
window.top.postMessage({ XMLFile: "levels/foo.xml" }, "*");
In nonsandboxed page, initiate async load of file, then call back down to the sandboxed page with the string version of the file:
document.body.onload = function() {
// Listen for request messages from child window
window.addEventListener("message", function (event) {
if (event.data.XMLFile) {
// Load the requested XML file, must be async
var xhttp = new XMLHttpRequest();
xhttp.open("GET", event.data.XMLFile, true);
xhttp.send(null);
// After loading, pass the resulting XML back down to sandboxed page
xhttp.onload = function (e) {
document.getElementById("idSandbox").contentWindow.postMessage({ sResponseText: xhttp.responseText }, '*');
}
}
} );
}
Returning to the sandboxed page, when you receive the xml response text, convert it back into a DOM object for parsing:
// Receive messages from containing window
window.addEventListener("message", function (event) {
// XML file retrieved
if (event.data.sResponseText) {
parser = new DOMParser();
xmlDoc = parser.parseFromString(event.data.sResponseText, "text/xml");
onAfterLoadLevel(xmlDoc);
}
});
As an aside, the mix of colors on that whatwg page would trigger ADD in anybody that didn't already have it. The other reference pages were useless. There were a number of discussions about this issue but nobody has posted the code so I figured I'd do it here.

Related

How to get email content from documents app in app script

How can I get content from google docs for email body in Html format in app script as earlier I was using classic google sites for getting body content of the email and now the classic sites are shutting down. Or do you know any alternative for this . Earlier I was using code for getting content.
SitesApp.getPageByUrl(spSignURL).getHtmlContent()
You may export the document as HTML. To do so you may use the following function:
function exportAsHtml(documentId) {
DriveApp.getRootFolder() // Makes Apps Script get the right permissions
const result = UrlFetchApp.fetch(`https://www.googleapis.com/drive/v3/files/${documentId}/export?mimeType=text%2Fhtml`, {
headers: {
"Authorization": `Bearer ${ScriptApp.getOAuthToken()}`
},
muteHttpExceptions:true,
})
const content = result.getContentText()
if (result.getResponseCode() >= 400) {
console.error(JSON.parse(content))
throw new Error("Exception when exporting as HTML")
}
return content
}
The first line of the function makes it so Apps Script grants you the necessary permissions. Alternatively you can manually set all the permissions you are using at the manifest.
It's worth noting that email HTML is not 100% the same as web HTML, so you may need to clean the result a bit depending on your use case.
References
UrlFetchApp.fetch(url, params) (Apps Script reference)
Files: export (Google Drive API reference)

Get url from iframe when origin is not the same

I want to get the URL from an iframe when the user redirects by clicking links in the iframe. The source of the iframe is not the same as the web application.
For example:
<iframe src="startingUrl" class="embed-responsive-item" id="iframe" sandbox="" allowfullscreen</iframe>
I add a load listener on the iframe to detect when the user redirects to other urls in this iframe:
const iframe = document.getElementById("iframe");
iframe.addEventListener("load", (evt) => {
const location = iframe.contentWindow.location;
console.log(location); // this gives me a Location object where I can see the href property
console.log(location.href); // this gives me a SecurityError: Permission denied to get property "href" on cross-origin object, I also tried to get a copy of the object but that doesn't work either.
});
I know what causes this problem and I also know it is not possible. But I need to find a way to get the current URL of the page. If this is a no go then I want that the user who uses this web application can copy the url of the iframe and put it in an input field.
Now they can do "View frame source" in chrome and This frame: view frame source or info in Firefox. But this is too complicated for the user. Is there a way they can see the URL in the iFrame or a way for the user to get the URL simpler.
The site in the iFrame is not mine.
All help is much appreciated!
Short answer: This is a no go, unless you have the support of the other site in your iframe and they are willing to add the code in #박상수 answer.
Longer answer: You could set up a proxy server to inject the required code to make this work, but then you will run into legal and ethical difficulties, so I am not going to explain how to do that in depth.
Another approach might be to create a browser extension and have your users install that. Again I should point out FaceBook has in the past ran into ethical difficulties taking this approach.
Ultimately their are very good security reasons why the browser stops you doing this and you should probably respect those reasons and not do it.
If you don't see the code below, check the link below.
console.log(iframe.src);
Check out this link
SecurityError: Blocked a frame with origin from accessing a cross-origin frame
let frame = document.getElementById('your-frame-id');
frame.contentWindow.postMessage(/*any variable or object here*/, 'http://your-second-site.com');
window.addEventListener('message', event => {
// IMPORTANT: check the origin of the data!
if (event.origin.startsWith('http://your-first-site.com')) {
// The data was sent from your site.
// Data sent with postMessage is stored in event.data:
console.log(event.data);
} else {
// The data was NOT sent from your site!
// Be careful! Do not use it. This else branch is
// here just for clarity, you usually shouldn't need it.
return;
}
});
You will want to override the error being automatically thrown:
const iframe = document.getElementById('iframe');
iframe.addEventListener('load', evt => {
const loc = iframe.contentWindow.location;
try{
loc.href;
}
catch(e){
if(e.name === 'SecurityError'){
console.log(iframe.src);
}
}
});
<iframe src='https://example.com' class='embed-responsive-item' id='iframe' sandbox='' allowfullscreen></iframe>

Can Chrome content and background scripts share access to blob: URLs?

I'm creating a getUserMedia stream in a Chrome extension content script, and I'd like to pass it to the background script.
You can't pass non-JSON'able data between them, so my strategy is to pass instead the generated blob URL to the stream.
Content script:
function get_stream() {
navigator.mediaDevices.getUserMedia({video: 1}).then(stream => {
chrome.runtime.sendMessage({action: 'got_stream', params: {stream_url: URL.createObjectURL(stream)}});
});
Background script:
chrome.runtime.onMessage.addListener(function(data) {
switch (data.action) {
case 'got_stream': got_stream(data.params); break;
}
});
function got_stream(params) {
let vid = document.createElement('video');
alert(params.stream_url); //blob:http://...
vid.src = params.stream_url; //error - file not found
}
This is fine... until I try to apply it to a generated <video /> element, at which point the console says the file is not found.
I assume it's because the background and content scripts are in sandboxed environments.
Is there any way around this without having to do something nuclear like transfer the stream literally via WebRTC or something?
I figured out this is an origins issue.
The content script runs in the context of the present webpage, whereas the background script runs in the context of the extension.
Blob URLs are grouped by origin, so, in the same way you can't ordinarily AJAX from one domain to another, two domains also can't share blob URLs.
This is solved by running the content script not in the current webpage (so not specified in the manifest under content_scripts) but in a new tab or pop-up.
Background:
window.open('content-page.html');
Content page:
<script src='content-script.js'></script>
Then, any blob URL generated by content-script.js will be readable to the background, as they are now both running in the context of the extension, i.e. a shared origin.
[EDIT]
If you don't like the idea of a pop-up window (after all, on Mac these are rendered as full tabs), you could instead inject an iframe into the current tab and run your content script from there.
To do this, call a content script from your manifest:
{
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content-script-curr-tab.js"]
}]
}
Then in that:
let ifr = document.createElement('iframe');
ifr.setAttribute('allow', 'microphone; camera'); //necessary for cross-origin frames that request permissions
ifr.style.display = 'none';
ifr.src = chrome.runtime.getURL('page-to-inject-into-iframe.html');
document.body.appendChild(ifr);
Note chrome.runtime.getURL() - that's the key to hosting and running a page in the context of the extension not the webpage.
Then, finally, in page-to-inject-into-iframe.html:
<script src='script-to-inject-into-iframe.js'></script>
Then do your thing in there!
blob urls are likely bound to the origin so I don't think this will work. See some discussion here in the adapter repository.
Have you tried creating a RTCPeerConnection between content and background script and send the stream that way? Not ideal for a number of reasons but better than nothing.

TypeError: browser is undefined (Web Extension Messaging)

I am trying to communicate my web page script with my content script of my web extension with the code below
Web Page Script
const browser = window.browser || window.chrome;
browser.runtime.sendMessage(message,
function (response) {
console.log(response);
}
);
However, I keep getting the error TypeError: browser is undefined. The same goes if I use chrome.runtime.sendMessage() instead.
How am I supposed to use this method?
The issue here is that user/webpage scripts (unprivileged scripts) don't have access to JavaScript API for security purposes and browser, chrome are part of JavaScript APIs which can only be accessed by privileged scripts like web extension's background scripts and content scripts (again content scripts don't have access to all the JavaScript APIs). Basically, if you need to send data from web page script to background script, CustomEvent should be used to send data to a content script which acts as a bridge and from there send that data to background script using browser.runtime.sendMessage. PFB sample code
window.onload = function(){
document.dispatchEvent(new CustomEvent("myEvent",{
detail:["Hello","World"]
}));
}
contentscript.js
document.addEventListener("myEvent", function (event) {
browser.runtime.sendMessage({
data: event.detail
});
background.js
browser.runtime.onMessage.addListener(function (message) {
data = message.data;
// do stuff
});

ajax call to localhost from site loaded over https fails in chrome

-------------------- UPDATE 2 ------------------------
I see now that what I am trying to accomplish is not possible with chrome. But I am still curios, why is the policy set stricter with chrome than for example Firefox? Or is it perhaps that firefox doesn't actually make the call either, but javascript-wise it deems the call failed instead of all together blocked?
---------------- UPDATE 1 ----------------------
The issue indeed seems to be regarding calling http from https-site, this error is produced in the chrome console:
Mixed Content: The page at 'https://login.mysite.com/mp/quickstore1' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://localhost/biztv_local/video/video_check.php?video=253d01cb490c1cbaaa2b7dc031eaa9f5.mov&fullscreen=on'. This request has been blocked; the content must be served over HTTPS.
Then the question is why Firefox allows it, and whether there is a way to make chrome allow it. It has indeed worked fine until just a few months ago.
Original question:
I have some jQuery making an ajax call to http (site making the call is loaded over https).
Moreover, the call from my https site is to a script on the localhost on the clients machine, but the file starts with the
<?php header('Access-Control-Allow-Origin: *'); ?>
So that's fine. Peculiar setup you might say but the client is actually a mediaplayer.
It has always worked fine before, and still works fine in firefox, but since about two months back it isn't working in chrome.
Has there been a revision to policies in chrome regarding this type of call? Or is there an error in my code below that firefox manages to parse but chrome doesn't?
The error only occurs when the file is NOT present on the localhost (ie if a regular web user goes to this site with their own browser, naturally they won't have the file on their localhost, most won't even have a localhost) so one theory might be that since the file isn't there, the Access-Control-Allow-Origin: * is never encountered and therefore the call in its entirety is deemed insecure or not allowed by chrome, therefore it is never completed?
If so, is there an event handler I can attach to my jQuery.ajax method to catch that outcome instead? As of now, complete is never run if the file on localhost isn't there.
before : function( self ) {
var myself = this;
var data = self.slides[self.nextSlide-1].data;
var html = myself.getHtml(data);
$('#module_'+self.moduleId+'-slide_'+self.slideToCreate).html(html);
//This is the fullscreen-always version of the video template
var fullscreen = 'on';
//console.log('runnin beforeSlide method for a video template');
var videoCallStringBase = "http://localhost/biztv_local/video/video_check.php?"; //to call mediaplayers localhost
var videoContent='video='+data['filename_machine']+'&fullscreen='+fullscreen;
var videoCallString = videoCallStringBase + videoContent;
//TODO: works when file video_check.php is found, but if it isn't, it will wait for a video to play. It should skip then as well...
//UPDATE: Isn't this fixed already? Debug once env. is set up
console.log('checking for '+videoCallString);
jQuery.ajax({
url: videoCallString,
success: function(result) {
//...if it isn't, we can't playback the video so skip next slide
if (result != 1) {
console.log('found no video_check on localhost so skip slide '+self.nextSlide);
self.skip();
}
else {
//success, proceed as normal
self.beforeComplete();
}
},
complete: function(xhr, data) {
if (xhr.status != 200) {
//we could not find the check-video file on localhost so skip next slide
console.log('found no video_check on localhost so skip slide '+self.nextSlide);
self.skip();
}
else {
//success, proceed as normal
self.beforeComplete();
}
}, //above would cause a double-slide-skip, I think. Removed for now, that should be trapped by the fail clause anyways.
async: true
});

Categories