Angular replace url after hashbang - javascript

In our app, we are serving static angular app files out of foobar.com/public/angular. The home page (foobar.com) is configured to serve foobar.com/public/angular/index.html. Thus, in our index file's <head>, we have:
<base href="http://foobar.com/public/angular">
However, during development, we use foobar.com/debug, which causes the server to use this instead:
<base href="http://foobar.com/public/angular-debug">
The server returns unprocessed (not minified, uglified, etc) versions of the files from this url. We are also using angular-route for our views.
TLDR:
We have a production route: foobar.com
We have a dev route: foobar.com/debug
We have a base tag: <base href="http://foobar.com/public/whatever">
We are using hash-bang urls for angular routes
The trouble is generating urls on the client side. As discussed in comments, Click here does not work because the resulting url contains the base tag, which the server rejects and looks odd since the user starts somewhere else: foobar.com/public/angular/#/myRoute). The best I could think of to create the desired url was this:
/**
* Returns current url after replacing everything after the hashbang with a new string.
*
* #param {string} s - string to change route with (e.g. 'asdf')
* #return {string} - new url e.g. http://google.com/foobar/#!/asdf
*/
makeRouteUrl = function(s) {
var root = $location.absUrl().match(/.*#!/);
if (root && (root.length > 0)) {
root = root[0];
return( root + s );
} else {
// no hashbang in path? Choose home page (not intended use)
return "/";
}
}
However, this just seems a little gross. Is there a function in angular that makes the above function unnecessary? I have looked for functions in $location, $route, etc., but nothing seems to handle our complex setup correctly. We need to ultimately get the url as a string, not navigate (we are supporting open in new tab, so need to used ng-href). Since Angular apps often use hashbang urls for routing, I figured there must be something that allows you the just change the route after the hashbang while still preserving what's in front of it.

I'm not entirely sure if I understand the problem...
$location.url('/stuff/after/hash?params=hey');
Or if you only want to set the path after #:
$location.path('/stuff/after/hash');
Is what I usually use. But you need to return the url without hash after this?
How about
window.location.origin + window.location.pathname
For the current url without hashes and parameters?

Have you considered using the UI Router library?
In general, it's excellent.
Specific to your question, the UrlMatcher service provides a method format that- unless I'm misunderstanding what you're asking- does what you're trying to do.
new UrlMatcher('/your/route/here').format(optionalNamedParameters);
// returns a formatted URL

Related

Vue Router, GitHub Pages, and Custom Domain Not Working With Routed Links

My domain: myname.com
My GitHub repo: myname
My GitHub name: myname
Underlying GH-Pages URL: myname.github.io/myname
My Issue: I have the following pages set up with a History Vue Router: home, contact, projects.
Whenever I go to myname.com/contact, it routes me to 404 instead. When I click on a link to redirect me to /contacts, it pushes it to the address bar but if you refresh, it routes to 404. I have read posts such as: Deploy Vue to GitHub Pages. Error with vue-router and Vue router on page refresh gets 404 but the issue persists and the custom domain is an added complexity. I have also read this: Vue Router return 404 when revisit to the url but, there is an example of a React Router page with a Browser Router working on GH-Pages: https://milosrancic.github.io/reactjs-website/activities, repo: https://github.com/milosrancic/reactjs-website.
Here is what I have done:
My package.json has the following line: "homepage": "https://myname.com/"
My vue.config.js is exactly this:
module.exports = {
publicPath: '/'
}
My Router is on my path: /src/router/index.js, passed into main.js new Vue(), and is exported like this:
export default new VueRouter({
base: process.env.BASE_URL,
mode: 'history',
routes
})
Inside my App.vue (entry component), the template looks like this:
<template>
<div id="app">
<nav-bar></nav-bar>
<router-view></router-view>
</div>
</template>
I was unable to fix the problem through Vue but, as tao mentioned, this is likely an issue with GitHub and how they handle URLs. The simplest fix is to use a Hash Router instead so that the entry URL stays static. However, this will introduce that, arguably distasteful /#/ in your URL.
Given how catch-all routes are not supported natively, someone has found a workaround for this: https://github.com/rafgraph/spa-github-pages. Note: This is likely not good for SEO because the intended URLs do not actually exist. This is doing a trick with their 404 redirects and handling it on the index page. This was for a portfolio site and as such, I am ok with this for now. If I, or someone else, finds a better solution, this will be updated.
Workaround:
Inside /public add a file called 404.html and paste the following contents:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CHANGE THIS TO YOUR TITLE</title>
<script type="text/javascript">
// MIT License
// https://github.com/rafgraph/spa-github-pages
// This script takes the current url and converts the path and query
// string into just a query string, and then redirects the browser
// to the new url with only a query string and hash fragment,
// e.g. https://www.foo.tld/one/two?a=b&c=d#qwe, becomes
// https://www.foo.tld/?/one/two&a=b~and~c=d#qwe
// Note: this 404.html file must be at least 512 bytes for it to work
// with Internet Explorer (it is currently > 512 bytes)
// If you're creating a Project Pages site and NOT using a custom domain,
// then set pathSegmentsToKeep to 1 (enterprise users may need to set it to > 1).
// This way the code will only replace the route part of the path, and not
// the real directory in which the app resides, for example:
// https://username.github.io/repo-name/one/two?a=b&c=d#qwe becomes
// https://username.github.io/repo-name/?/one/two&a=b~and~c=d#qwe
// Otherwise, leave pathSegmentsToKeep as 0.
var pathSegmentsToKeep = 0;
var l = window.location;
l.replace(
l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') +
l.pathname.split('/').slice(0, 1 + pathSegmentsToKeep).join('/') + '/?/' +
l.pathname.slice(1).split('/').slice(pathSegmentsToKeep).join('/').replace(/&/g, '~and~') +
(l.search ? '&' + l.search.slice(1).replace(/&/g, '~and~') : '') +
l.hash
);
</script>
</head>
<body>
</body>
</html>
Inside /public/index.html, add the following inside the <head> right after <title>:
<script type="text/javascript">
// MIT License
// https://github.com/rafgraph/spa-github-pages
// This script checks to see if a redirect is present in the query string,
// converts it back into the correct url and adds it to the
// browser's history using window.history.replaceState(...),
// which won't cause the browser to attempt to load the new url.
// When the single page app is loaded further down in this file,
// the correct url will be waiting in the browser's history for
// the single page app to route accordingly.
(function(l) {
if (l.search[1] === '/' ) {
var decoded = l.search.slice(1).split('&').map(function(s) {
return s.replace(/~and~/g, '&')
}).join('?');
window.history.replaceState(null, null,
l.pathname.slice(0, -1) + decoded + l.hash
);
}
}(window.location))
</script>
This has worked for me and I can now visit the contacts page by simply entering myname.com/contact. This should also work if you have nested SPA inside /public, using the same trick.
If anyone is still looking for answers there is a simpler and neater workaround for this:
https://stackoverflow.com/a/65539760/11798104
Basically copy index.html and replace it with the name 404.html in the dist folder when you deploy
I don't totally understand your comment/confusion regarding why/how the re-routing works since you correctly summarized the first half which is Github sees it as an invalid URL and sends it to your custom 404 page. The 404 page then translates it into a query string pointed at the root page index.html.
Are you missing custom the index.html page in your set up? index.html itself decodes the query string back into "route param"-type representation so that when your app actually loads, it can initialize with the correct view and state.
I actually use this exact setup for my GH pages site: https://mirajp.github.io --> https://www.miraj.dev

Virtual paths from the client to real paths on the server

The client is supposed to see just a directory and its contents on the server (FS_ROOT).
And the server is supposed to convert the paths that it receives from the client to real paths that exist and do the file operations that the client requested on them:
I made these 2 functions to handle that and I want to ask if they are secure enough. I mean there should be no way for the client to fool the server to do something outside FS_ROOT
function fromVirtualPath(virtPath){
if(virtPath === '/' || virtPath === '.')
return FS_ROOT;
virtPath = virtPath.trim();
if(virtPath[0] === '/')
virtPath = virtPath.substr(1);
const absPath = path.resolve(FS_ROOT, virtPath);
if(absPath.indexOf(FS_ROOT) !== 0)
throw new Error('Outside root dir - no permissions!');
return absPath;
}
function toVirtualPath(absPath){
return '/' + path.relative(FS_ROOT, absPath);
}
Example real path: /www/site.com/public_html/yo
Client should see: /yo
About fromVirtualPath I would simply move the line virtPath = virtPath.trim(); to be the first line of the function, then it's ok.
If the values passed to toVirtualPath are always return values of fromVirtualPath, yes it is secure enough; other wise we could check if the value is a good absPath.
function fromVirtualPath(virtPath) {
virtPath = virtPath.trim();
if (virtPath === '/' || virtPath === '.')
return FS_ROOT;
if (virtPath[0] === '/')
virtPath = virtPath.substr(1);
const absPath = path.resolve(FS_ROOT, virtPath);
if (absPath.indexOf(FS_ROOT) !== 0)
throw new Error('Outside root dir - no permissions!');
return absPath;
}
function toVirtualPath(absPath) {
if (absPath.indexOf(FS_ROOT) !== 0)
throw new Error('Bad absolute path!');
return '/' + path.relative(FS_ROOT, absPath);
}
Your code is a bit insecure until you make use of the techniques provided by NODE.JS in the mentioned article. Try implementing the following code,
function fromVirtualPath(virtPath) {
virtPath = virtPath.trim();
if (virtPath === '/' || virtPath === '.')
return FS_ROOT;
if (virtPath.indexOf('\0') !== -1)
throw new Error('That was evil.');
const absPath = path.join(FS_ROOT, virtPath);
if (absPath.indexOf(FS_ROOT) !== 0)
throw new Error('Outside root dir - no permissions!');
return absPath;
}
function toVirtualPath(absPath) {
return '/' + path.relative(FS_ROOT, absPath);
}
The following article from NODE.JS will be really helpful to you.
"How can I secure my code?"
Poison Null Bytes
Poison null bytes are a way to trick your code into seeing another
filename than the one that will actually be opened.
if (filename.indexOf('\0') !== -1) {
return respond('That was evil.');
}
Preventing Directory Traversal
This example assumes that you already checked the
userSuppliedFilename variable as described in the "Poison Null
Bytes" section above.
var rootDirectory = '/var/www/'; // this is your FS_ROOT
Make sure that you have a slash at the end of the allowed folders name
you don't want people to be able to access /var/www-secret/, do you?.
var path = require('path');
var filename = path.join(rootDirectory, userSuppliedFilename);
Now filename contains an absolute path and doesn't contain ..
sequences anymore - path.join takes care of that. However, it might
be something like /etc/passwd now, so you have to check whether it
starts with the rootDirectory:
if (filename.indexOf(rootDirectory) !== 0) {
return respond('trying to sneak out of the web root?');
}
Now the filename variable should contain the name of a file or
directory that's inside the allowed directory (unless it doesn't
exist).
Security is a complex matter. And you can never be sure.
Despite the fact that I couldn't find any flows in #RahulVerma answer I'll add my 2 cents...
The link that #RahulVerma posted is official but not a documentation per se. And in the documentation there is nothing about Poison Null Bytes ...strange isn't it.
And that makes you think: maybe, just maybe, when the fs and/or path modules were written authors didn't put enough effort into security considerations, or just missed that. Yes, maybe there are some good reasons for you and not the fs/path to handle the \0. But also wouldn't it be better if everyone was protected from \0 by default? And only for some rear occasions you could explicitly set an option to allow \0 in paths.
So... what am I trying to say is: security is hard even for the best of us, and without proper peer review (currently, less than 100 views on this question do not strike me as a "proper peer review") or, better yet, a history of successful time in production, you should not be satisfied with these answers (my included) saying "It's OK, if you add this or that".
Why don't you use some code that already was tested in battles instead of trying to write a secure code by yourself?
E.g serve-static is used in Express.
(Probably it doesn't meet your needs - it's static after all, but you get the idea)
Even if you don't want another dependency in your project you can at least study and copy from the implementation that proved itself. (But, yes, it doesn't seem different from the #RahulVerma answer)
That said. I'd like to point out that:
If you'd copy the implementation, you can make a mistake while doing so.
Even if your code is safe, consider how safe do you manage your code. Will it be safe tomorrow?
Even well tested libraries and engines can, and often do, have bugs, and fall prey to 0day exploits
Oh! Just found: https://github.com/autovance/ftp-srv/issues/167
It's about the library that was suggested in another question of yours.
So, if you decide (or if you'll be assured) that now you code is surely safe don't stop on that! Add an extra layer of security to it anyway:
restrict the server's access to folders outside of the /www/site.com/public_html/ on an OS level.
The following principles can be applied to secure client access to paths relative to the web root:
Restrict access outside of your public web root folder to your
service. Rationale: begin with ZERO trust.
Split the path provided by the user into parts. This will remove leading '/' and all '/' separators leaving only the parts of the path. Better yet, use whitelisting for path parts to restrict acceptable characters in a path part using a regular expression. Rationale: sanitize user input
Validate each part sequentially for existence assuming that the first part starts from the web root as it is intended. Disallow .. (parent dir) in part names (to prevent traversal outside the web root folder). Rationale: sanitize user input and validate user input
Avoid using symbolic links under the web root folder (to prevent
traversal outside the web root folder). Rationale: reduce attack surface
Fail early with an error upon encountering the first invalid part. Rationale: reduce attack surface
To optimize system calls, you can do the check for .. and part whitelisting in one pass. If there are any .. in the path or offending parts, return an error. Otherwise, split the parts and rebuild the absolute path string by concatenating them with your web root and do one existence check instead of multiple folder existence checks along the path.
Instead of trying to validate every path yourself, let the operating system do it for you! This is a good example of an application that could use a chroot.
Here is an example of an npm library which creates a chroot.
> var chroot = require("chroot")
> var fs = require("fs")
> chroot('/virtual/root/here', 'nobody')
> fs.readdir(".", function(err, files) { console.log(files); }) // Lists virtual root
> fs.readdir("..", function(err, files) { console.log(files); }) // Also lists virtual root
> fs.readdir("/", function(err, files) { console.log(files); }) // ALSO lists virtual root
Should you run this script as root, it immediately changes the user to "nobody" and sandboxes you to your virtual root. This prevents the script from accessing anything outside it, and the program can't chroot out either, as it's no longer running as root.
Now that you are chrooted into your virtual root, using "/" will give you a directory listing of your virtual root - essentially, you can use your virtual path directly in fs.readdir()!
Need to access some specific files outside the new root? Use microservices! You can run a node.js instance in the background as your file accessor, and communicate between your main server and your file accessor. Having two nodejs instances not only allows your background task to sandbox itself, but also allows you to make use of multithreading.
Yours is a basic java code. In real time scenarios, these basic java code should not be deployed on server side and we can't expect
secuirty out of this.
To add the security check to this java code, many APIs come as part of Spring framework but since we are writing java code then we can
make use of java NIO package only, API name WatchService and WatchEvent
class DirectoryWatchTest {
public static void main(String[] args) {
try {
WatchService watchService = FileSystems.getDefault().newWatchService();
Path path = Paths.get("C:/");
/**
* The register() method of the Path class takes a WatchService object and an event type for which the
* application needs to get notified.
*
* The supported event types are:
* ENTRY_CREATE: indicates if a directory or file is created.
* ENTRY_DELETE: indicates if a directory or file is deleted.
* ENTRY_MODIFY: indicates if a directory or file is modified.
* OVERFLOW: indicates if the event might have been lost or discarded. This event is always implicitly
* registered so we don't need to explicitly specify it in the register() method. */
path.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
while (true) {
WatchKey key;
try {
key = watchService.take();
} catch (InterruptedException ex) {
return;
}
/**
* The whole work flow:
* A Watchable object is registered with a watch service by invoking its register method,
* returning a WatchKey to represent the registration.
*
* When an event for an object is detected, the key is signalled, and if not currently signalled,
* it is queued to the watch service so that it can be retrieved by consumers that invoke the poll or
* take methods to retrieve keys and process events.
*
* pollEvents List<WatchEvent<?>> pollEvents() method retrieves and removes all pending events for
* this watch key, returning a List of the events that were retrieved. Note that this method does not
* wait if there are no events pending. */
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
#SuppressWarnings("unchecked")
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path fileName = ev.context();
System.out.println(kind.name() + ": " + fileName);
if (kind == ENTRY_MODIFY && fileName.toString().equals("DirectoryWatchTest.java")) {
System.out.println("My source file has changed!!!");
System.out.println("My source file has changed!!! - Modified");
}
}
/**Once the events have been processed the consumer invokes the key's reset method to reset the
* key which allows the key to be signalled and re-queued with further events.*/
boolean valid = key.reset();
if (!valid) {
break;
}
}
} catch (IOException ex) {
System.err.println(ex);
}
}
}
This kind of basic security check can be put in java code. The user will be able to watch the url unless and until we don't get
hold of protocol and hide it via #PutMapping or implementing security based API's in this but for that we need framework based API's
enter code here

How to reference documents in my website to be independent of the website location and usage location

I'm relatively new to client side development. I'm creating an angularJS directive which references a static html, in [root]/Static/template.html.
I guess the problem is not unique to angularJS.
Now I need this address to be root relative, so that it can be loaded regardless of where I use the directive. The problem is that I don't know where my site will be uploaded, so it might be put in www.mysite.com/ or might be www.mysite.com/system/
I also can't use relative path, as it will be sensitive to where I use the directive, so for instance if I use Static/template.html, it will be found by documents in the website root, but not in the inner folders.
What is the correct way to reference documents to be robust?
If your static folder is relative the place where your application is deployed, e.g.:
www.example.com/index.html
www.example.com/Static
www.example.com/root/index.html
www.example.com/root/Static
www.example.com/root/foobar-app/index.html
www.example.com/root/foobar-app/Static
Then you need to extract the base url and store it somewhere. window.location API could be used to do that.
E.g.:
<!-- index.html -->
<!-- should probably be placed into external script file -->
<script>
function extractPath(url) {
return url.match(/.*\//) // find all chars until the slash
}
var baseurl = window.location.origin + extractPath(window.location.pathname);
window.baseurl = baseurl; // store in global scope
</script>
This snippet shows the general idea. Now elsewhere in your code you can read the base url path to access static resources. E.g.:
var image_url = window.baseurl + "Static" + image_path;
In AngularJS you would normally store that variable in the main app controller. If you only have one factory to access static resources, you could consider storing the baseurl there.
URL that starts with / is the URL from the root.
So if you set /Static/template.html, you can access template.html from both paths(www.mysite.com/ and www.mysite.com/system/).

backbone router as /en/#xyz and not /#en/xyz

I'm trying to do something pretty simple.
Here's the scenario:
I have a whole site working great with pushstate enabled browsers. The site works off the basis that the language is the "actual page" for instance:
/en/whatever/etc = index.en.php with with english headers and encoding & tweaks
/ar/whatever/etc = index.ar.php with with arabic headers and encoding & tweaks
/ru/whatever/etc = index.ru.php with with russian headers and encoding & tweaks
It's really slick and works nicely with pushstate as I mentioned. The problem is when I try to use the same router code with the hash alternative.
Backbone's router seems to want to do this:
/#/en/whatever/etc = bad because it's not based correctly
/#/ar/whatever/etc = and so on
what I want it to do is:
/en/#whatever/etc
/ar/#whatever/etc
/ru/#whatever/etc
..and so on
or even:
/en/#/whatever/etc
/ar/#/whatever/etc
/ru/#/whatever/etc
..and so on
But i can't find a way without tweaking backbones source to implement this. I'm kind of against changing backbone.js unless I really have to because of the futureproofing factor.
Anyone have any thoughts?
You need to use the root option in the Backbone History object:
Quote the docs:
If your application is not being served from the root url / of your domain, be sure to tell History where the root really is, as an option: Backbone.history.start({pushState: true, root: "/public/search/"})
You're going to have to do some code that happens on your landing page that looks at what language they're using, and then sends them to /[LANG CODE]/. Then on your server, you just map requests for /[LANG CODE]/ to your .html file.
In your HTML file look at the location.pathname variable, strip off the first /[LANG CODE]/ part and use that as your root value for the options hash.
var lang = "/"+_.first(location.pathname.split('/')+"/";
Backbone.history.start({pushState: true, root: lang}); // if you're using pushState, otherwise omit or set to false
This will cause your URLs to be:
/en/#whatever/etc
/ru/#whatever/etc

How should I create relative paths in javascript using MVC3?

I am having some difficulty aligning my paths without a hardcode in javascript. I am running an asp.net MVC3 web application.
If my path is of the form
var url = 'http://serverNameHardcode/websiteNameHardcode/service/service?param1=' + param;
Then things work fine when I do
$.get(url,
{},
function (data) {alert('callback success');},'json');
I would like to create a relative path. I tried
var url = 'service/service?param1=' + param;
And this works when I run locally and also in Firefox, but not in IE7. When I publish to the server without the hardcode the callback never fires. I know MVC-3 adds some complexity to routing, but I do not know if it applies to this situation; so, I marked this question as such.
How should I setup my path so I don't need hardcodes?
Just write out the app path as a global js variable from your master view, then compose links as
APPPATH + "path/whatever"
Just had to solve this for one of my jQuery plugins, where it is preferable not to modify anything global (i.e. outside the scope of the plugin use) so I had to disregard the marked answer.
I also found that because I host DEV locally in IIS I could not use a root-relative path (as localhost is not the root).
The solution I came up with extended what I had already started with: a data-controller attribute specifying which controller to use in the element I am applying my plugin to. I find it preferable to data-drive the controller names so the components can be more easily reused.
Previous:
<div data-controller="Section">
Solution:
<div data-controller="#Url.Content("~/Section")">
This injects the server root (e.g. /Test.WindowsAzure.Apr2014/ before the controller name so I wind up with /Test.WindowsAzure.Apr2014/Section which is perfect for then appending actions and other parameters as you have. It also avoids having an absolute path in the output (which takes up extra bytes for no good reason).
In your case use something like:
// Assuming $element points to the element your plugin/code is attached to...
var baseUrl = $element.data('controller');
var url = baseUrl + '/service?param1=' + param;
Update:
Another approach we now use, when we do not mind injecting a global value, is Razor-inject a single global JavaScript variable onto window in the layout file with:
<script>
window.SiteRoot = "#Url.Content("~/")";
</script>
and use it with
var url = window.SiteRoot + '/service?param1=' + param;
One option:
var editLink = '#Url.Action("_EditActivity", "Home")';
$('#activities').load(editLink + "?activityID=" + id);
another example:
var actionURL = '#Url.Action("_DeleteActivity", "Home")';
$('#activities').load(actionURL + "?goalID=" + gID + "&activityID=" + aID);
If you don't need to add to the string:
$('#activities').load('#Url.Action("_Activities", "Home", new { goalID = Model.goalID},null)');
I really need the path to get this to work, maybe its IE7. Who knows. But this worked for me.
Grab the URL and store it somewhere. I chose to implement the data attribute from HTML5.
<div id="websitePath" data-websitePath='#Request.Url.GetLeftPart(System.UriPartial.Authority)#Request.ApplicationPath'></div>
Then when you need to perform some AJAX or otherwise use a URL in javascript you simply refer to the stored value. Also, there are differences in the versions of IIS (not cool if your devbox is IIS5 and your server is IIS7). #Request.ApplicationPath may or may not come back with a '/' appended to the end. So, as a workaround I also trim the last character if it is /. Then include / as part of the url.
var urlprefix = $('#websitePath').data('websitepath');
urlprefix = urlprefix.replace(/\/$/, "");
var url = urlprefix + '/service/service?param1=' + param;
While the accepted answer is correct I would like to add a suggestion (i.e. how I do it).
I am using MVC, and any ajax request goes to a controller. My controllers have services so if a service call is required the controller will take of that.
So what's my point? So if ajax always communicates with a controller, then i would like to let the MVC routing resolve the path for me. So what I write in Javascript for url is something like this:
url: 'controller/action'
This way there is no need for the root path etc...
Also, you can put this in a separate Javascript file and it will also work whereas #Url.Content will need to be called on the view.

Categories

Resources