CacheBust in dojoConfig breaks paths - javascript

I have the same problem someone reported a year ago on ESRI forum: the query string appears before the file name, just after the last slash, like this:
http://js.arcgis.com/3.13/esri/images/symbol/sfs/?1430314495556diagonalcross.png
For me it's the same, except for my cacheBust doesn't break .png images, but manifest.json files (but not config.json). Seems that somewhere is a switch of supported extensions/filenames with "add query string after the last slash" rule as the default. Adding a query string after the filename didn't help - dojo added another querystring where it shouldn't be.
If this is really caused by an unsensible whitelist, I need to find and change it. I checked the Web AppBuilder (version 1.4) and found no mention of the cacheBust. Another possible culprit is the ArcGIS JavaScript API (3.15 in my case) - it contains a reference for cacheBust on line 11 in its obfuscated init.js, but I didn't manage to track the place where the query string is placed to the URL. The bug being in Dojo itself (1.10) seems unlikely, but it still seems possible.
Is there any solution available? Using downloaded code for the API is OK. If not, can anyone help me locate the right place in the code, or refute that the problem is there?

I downloaded the API, looked at it a little and realized that there's no restrictive whitelist. The querystring is just assigned too early, to a base URL to which the filenames are added later. So the solution would be to find where are the filenames added and make this process querystring-aware.
So I found where the manifest.json was assigned (in the Web AppBuilder, file WidgetManager) and changed it to handle the querystring:
if (widgetJson.folderUrl.indexOf("?") > -1) {
url = widgetJson.folderUrl.substr(0, widgetJson.folderUrl.indexOf("?")) + 'manifest.json' + widgetJson.folderUrl.substr(widgetJson.folderUrl.indexOf("?"));
} else {
//this is how it looked before
url = widgetJson.folderUrl + 'manifest.json';
}
The code might be slightly more elegant, but it works. There were some other files garbled by the cacheBust, but this algorithm worked.

I did some checking into the code as mentioned in another answer:
if (widgetJson.folderUrl.indexOf("?") > -1) {
url = widgetJson.folderUrl.substr(0, widgetJson.folderUrl.indexOf("?")) + 'manifest.json' + widgetJson.folderUrl.substr(widgetJson.folderUrl.indexOf("?"));
} else {
//this is how it looked before
url = widgetJson.folderUrl + 'manifest.json';
}
I do not recommend that approach. I'm using v2.3.
The code to determine the url to use differs in the 3d and 2d portions of the product. This code only fixes the url problem with manifest.json files. It does not fix the same problem with the icon files (or possibly other file types).
In addition, when you change the web appbuilder application code, it gets copied into each new application that you create. Conversely, if you have already created an application, you will have to make the change again, once per existing application. Each new file type issue that crops up will cause one more fix per web appbuilder product installation and per application built with it. Each new upgrade from Esri might require the exact same changes to be made all over again.
I would recommend not to set CacheBust to true until Esri fixes the underlying code problem rather than attempting to patch this.

Related

How to Prevent Browser from Adding User Parameter in URL Of Apps Script Based Web App?

I have a web app functioning from the last two years but after updating some features and adding a new page I starting getting an error that Page not found.
When I looked further I got to know it is because of User Parameter in the URL which can be seen in screenshots '/u/1' is being added in my script URL Automatically as click on a link.
I tried removing it manually but it doesn't help, I have tried several modes of deployment.
And if I roll back to my older version it still works fine or if I log in only with a single account it works with the new version as well.
So I wanted some light is possible to overcome this issue?
I am just a part-time coder who can create something based on my needs so even after looking for the whole day I couldn't get it working so as of now I have rolled back to the old version.
Thanks in advance I am more than happy to share any more info required.
Screenshots attached with URL difference and error.
So I was able to find a workaround after roaming around in Google Forum as well they stated that it can not be solved since its intended behavior.
so what I did was done some modification in my function which was being used to get script URL and added some desired string to overcome user specific error when logged in with multiple users.
Here is the code
function getScriptUrl() {
var url = ScriptApp.getService().getUrl();
var stringToBeChanged = 'macros/s';
var newString = 'a/authentic.co.in/macros/s';
url = url.replace(stringToBeChanged,newString);
return url;
}
I hope this helps someone with a similar issue thank you for your support.

selenium + chrome.fileSystem.chooseEntry = invalid calling page error

I am writing a Selenium script to test a Chrome app that uses the Chrome.fileSystem.chooseEntry API to select a directory. When I do this manually, it works fine. But when I do this in a Selenium script, I get back this error:
Unchecked runtime.lastError while running fileSystem.chooseEntry: Invalid calling page. This function can't be called from a background page.
Any ideas on how to make Selenium and chooseEntry play nicely together?
I updated to the latest Chromedriver, but still no luck. I also looked at ChromeOptions, but didn't see anything that looked like it would be helpful. The interwebs doesn't seem to have much to say about Selenium and chooseEntry. I'm on version 51 of Chrome.
I'm down to thinking I'll need a special javascript entry point to set the path values for testing instead of using chooseEntry. But I would strongly prefer to not have a separate code execution path for my tests. Anybody have a cleaner solution?
EDIT: per commenter's request, here's the offending code:
chrome.fileSystem.chooseEntry({type:'openDirectory'},function(entry) {
chrome.fileSystem.getWritableEntry(entry,function(writeable_entry) {
console.log("got writeable entry");
});
}, function(e) { errorHandler(e); });
EDIT #2: I've gone with the special javascript entry point hack. In manual mode -- i.e., not running under Selenium -- I run code that executes chooseEntry, and then use the retainEntry API to get the entry id. I added an entry point in my javascript to take an entry id and call the restoreEntry API to turn it back into an entry. I also modified my code so if this entry object is set, then use that as the file instead of calling chooseEntry. Lastly, I modified my Selenium script to call the restoreEntry entry point before running the rest of the script.
This is not ideal, since now my test code execution path is somewhat different from my actual live-human-being-at-the-controls code execution path. But at least it lets me use Selenium scripts now. Of course, if anyone can think of a non-horrible way to solve this solution, I'd love to hear about it.
EDIT #3: Per #Xan's comment, corrected my terminology from "extension" to "Chrome App."
I can only offer this horrible hack. For Chrome Apps under OSX I created folder favorites and use Robot keyPress to navigate and select the 'favorite' folders needed for the App. The only possible redeeming factor is that it does mirror a valid/possible actual human interaction with the file interface.
private void selectOSXFolderFavorite(int favorite) {
// With an OSX file folder dialog open, Shift-Tab to favorites list
robot.keyPress(KeyEvent.VK_SHIFT);
robot.keyPress(KeyEvent.VK_TAB);
robot.keyRelease(KeyEvent.VK_TAB);
robot.keyRelease(KeyEvent.VK_SHIFT);
// move to the top of favorites list
int i = 40;
while (i-- > 0) {
robot.keyPress(KeyEvent.VK_UP);
robot.keyRelease(KeyEvent.VK_UP);
}
while (favorite-- > 0) {
robot.keyPress(KeyEvent.VK_DOWN);
robot.keyRelease(KeyEvent.VK_DOWN);
}
// Send an enter key to Select the selected folder
robot.keyPress(KeyEvent.VK_ENTER);
robot.keyRelease(KeyEvent.VK_ENTER);
}

Need help reading a file in Javascript located in the same directory

I've written this function to try and read a file located in the same directory as my Javascript files and index.html file. I've read from files before, but normally I have the user select the file themselves, so I've never had to create the actual file object.
Does anyone know why the code below doesn't work?
function getFile()
{
var reader=new FileReader();
var file=new File("input.txt");
var str=reader.result;
reader.readAsText(file);
return str;
}
Update:
Some additional information (My apologies if I don't answer your questions, I'm really new to this, and everything I know is self-taught).
Server side or client side? I think this it is going to be hosted serverside - I have a domain that I'm going to upload the file to.
Not possible due to security restrictions. You must use a file that was selected by a user. Imagine what would happen if javascript could read any file on your harddrive - nightmare!
I had a similar code for a desktop app written in JS. It worked well on IE and FF until Chrome came along (yes, it was a very long time ago!) and started restricting the sandbox ever more. At some point, IE and FF also tightened permissions and the method didn't work anymore (I used this code which does not work anymore):
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
file = Components.classes["#mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
file.initWithPath(filename);
if (file.exists())
{
inStream = Components.classes["#mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
inStream.init(file, 0x01, 00004, null);
sInStream = Components.classes["#mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
sInStream.init(inStream);
content = sInStream.read(sInStream.available());
}
} catch (ex) {
// and so on...
}
At some point FF and Chrome had some startup flags that enabled the sandbox to be lax on certain security restrictions, but in the end I realized that this is simply old code.
Today, I use localStorage to store data on the local machine. The only disadvantage is that you have to implement an import/export functionality so that it is portable to other browsers/machines by copy/paste the raw text.
What I do is simply this. The maximum size of the database is said to be about 10 MB (but deviations are known to exist) and all I store is a single JSON string:
function store(data)
{
localStorage.data = JSON.stringify(data);
}
function retrieve()
{
return JSON.parse(localStorage.data);
}
Obviously, these two functions are an oversimplification, you need to handle edge cases like when no data exists or a non-JSON string was imported. But I hope you get the idea on how to store local data on modern browsers.
With the File() constructor you can only create new file objects, but cannot read files from lokal disk.
It is not quite clear what exactly you want to achieve.
You say, that the input.txt is located in the same directory as your html and js. Therefore it is located on the server.
If you want to read a file from the server you have to use an ajax request which is already explained in anoter question.

Anchor element's pathname returns undefined in Rhino with env.js

I have run into an issue that I believe is rooted in the implementation of anchor tags in Rhino. Although I am utilizing env.js, I suspect perhaps I am not configuring something correctly.
In particular, my issue occurs while I am attempting to write unit tests against code written for an angularjs application. When I include angular.js (versions 1.2.1 to present), I get the following error:
TypeError: Cannot call method "charAt" of undefined
I am convinced the error is the result of this call to urlParsingNode.pathname since a console.log call reveals that the pathname object is undefined.
I traced the instantiation of the urlParsingNode to this line where we see that it is the result of a call to document.createElement("a"); Further down, we see that they set the href attribute in this line in hopes that the created anchor tag will utilize the browser to correctly parse the URL.
I have to believe I'm not the first to attempt JS unit testing for angular via Rhino, but thus far I've not successfully Googled myself to a solution. Any tips will be greatly appreciated.
Found it and fixed it. The pathname getter/setter simply was undefined for HTMLAnchorElement in env.js.
I submitted a pull request, but unfortunately the project looks all but abandoned. I also couldn't figure out how to build it out to a single file. It appears perhaps someone has taken it upon themselves to break it apart into require.js modules. Not a battle worth fighting for my use case.
So for anyone else who hits this issue, I have the code you need below. It belongs in the HTMLAnchorElement.prototype. In my copy of env.js 1.2, this prototype begins on line 8075. I added the following at line 8118.
get pathname() {
var uri = Envjs.urlsplit(this.href);
return uri.path;
},
set pathname(val) {
var uri = Envjs.urlsplit(this.href);
uri.path = val
this.href(uri.urlunsplit(uri));
},
FYI, my particular issue is resolved with this pull request.

HTML5 History.pushState mangles URL's containing percent encoded non-Ascii (Unicode) chars

In an OSS web app, we have JS code that performs some Ajax update (uses jQuery, not relevant). After the page update, a call is made to the html5 history interface History.pushState, in the following code:
var updateHistory = function(url) {
var context = { state:1, rand:Math.random() };
/* -----> bedfore the problem call <------- */
History.pushState( context, "Questions", url );
/* -----> after the problem call <------- */
setTimeout(function (){
/* HACK: For some weird reson, sometimes something overrides the above pushState so we re-aplly it
This might be caused by some other JS plugin.
The delay of 10msec allows the other plugin to override the URL.
*/
History.replaceState( context, "Questions", url );
}, 10);
};
[Please note: the full code segment is provided for context, the HACK part is not the issue of this question]
The app is i18n'ed and is using URL encoded Unicode segments in the URL's, so just before the marked problem call in the above code, the URL argument contains (as inspected in Firebug):
"/%D8%A7%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9/scope:all/sort:activity-desc/page:1/"
The encoded segment is utf-8 in percent encoding. The URL in the browser window is: (just for completeness, doesn't really matter)
http://<base-url>/%D8%A7%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9/
Just after the call, the URL displayed in the browser window changes to:
http://<base-url>/%C3%98%C2%A7%C3%99%C2%84%C3%98%C2%A3%C3%98%C2%B3%C3%98%C2%A6%C3%99%C2%84%C3%98%C2%A9/scope:all/sort:activity-desc/page:1/
The URL encoded segment is just mojibake, the result of using the wrong encoding at some level. The correct URL would've been:
http://<base-url>/%D8%A7%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9/scope:all/sort:activity-desc/page:1/
This behavior has been tested on both FF and Chrome.
The history interface specs don't mention anything about encoded URL's, but I assume the default standard for URL formation (utf-8 and percent encoding etc) would apply when using URL's in function calls for the interface.
Any idea on what's going on here.
Edit:
I wasn't paying attention to the uppercase H in History - this code is actually using the History.js wrapper for the history interface. I replaced with a direct call to history.pushState (notice the lowercase h) without going through the wrapper, and the code is working as expected as far as I can tell. The issue with the original code still stands - so an issue with the History.js library it seems.
Update
As Doug S explains in the comments below, the latest version of History.js includes a fix for this behaviour. He also found that my solution caused double-encoding when used in browsers (such as IE 9 and below) which require the hash fallback, so I recommend that instead of using the fix detailed below, just download the latest version.
I've kept my original answer below, since it does explain what's going on in much more detail.
Basel found a resolution of sorts, but there's still some confusion about what's happening under the hood. This answer goes into detail about the problem and suggests a better fix. (You can skip straight to the fix if you want.)
The problem
First, open your browser's JS console and run this:
window.encodeURI(window.unescape('%D8%A7%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9'))
Does that look familiar? It should—that's what your URL is being mangled to. The problem lies in the implementation of History.unescapeString, specifically this line:
tmp = window.unescape(result);
window.unescape is a DOM Level 0 function—which is to say, an unstandardised relic from the hoary days of Netscape 2. It uses the escaping rules defined in RFC 2396, according to which characters outside of the unreserved range (alphanumerics and a small set of punctuation symbols) are encoded as octets.
This works fine for the US-ASCII range, but not all (indeed, the vast majority) of the characters in UTF-8 can be represented in a single byte. Since URIs do not have a built-in way of representing the character set being used, window.unescape just assumes each character maps to a single octet and blithely mangles any that don't.
In this example, the first letter in your URL is the Arabic letter alef (ا), represented by two bytes: 0xD8 0xA7. window.unescape interprets these as two separate characters: 0x00 0xD8 (Ø—capital O with stroke) and 0x00 0xA7 (§—section sign).
This is a known issue with History.js.
The fix
As noted above by the asker, the issue can be sidestepped by using the native implementation of the History API instead of the History.js wrapper, i.e. history.pushState instead of History.pushState.
This works for browsers that support the History API, but loses the benefit of having a polyfill for those that don't. Fortunately, there's a better fix. Open up the History.js source you're referencing and find this line (~1059 in my copy):
tmp = window.unescape(result);
Replace it with:
tmp = window.unescape(encodeURIComponent(result));
Or, if you're using the compressed source, replace a.unescape(c) with a.unescape(encodeURIComponent(c)).
To test this change, I ran the History.js HTML5 jQuery test suite on a local web server inside an Arabic-named directory. Before making the change, test 14 fails; after the change, all tests passed.
Credit
Though I found the problem and solution independently, Damien Antipa deserves credit for finding it first and making a pull request with the fix.
I'm still able to reproduce this in the following case:
History.pushState(null, null, "?" + some_Unicode_String_Or_A_String_With_Whitespace);
document.location.hash += "&someStuff";
In this case the _suid parameter gets removed and &someStuff as well. If the string is not unicode or doesn't have whitespaces (so no % chars) - this does not happen.
This workaround worked for me:
History.pushState(null, null, "?" + some_Unicode_String_Or_A_String_With_Whitespace + "&someStuff");

Categories