MOBILE-3286 login: Improve guessing site domains

main
Noel De Martin 2020-01-23 10:33:43 +01:00
parent 2e69497c13
commit a476a773f1
2 changed files with 54 additions and 23 deletions

View File

@ -27,6 +27,11 @@ interface UrlParts {
*/
domain?: string;
/**
* Url port.
*/
port?: string;
/**
* Url path.
*/
@ -49,39 +54,69 @@ interface UrlParts {
*/
export class CoreUrl {
// Avoid creating singleton instances
// Avoid creating singleton instances.
private constructor() {}
/**
* Parse parts of a url, using an implicit protocol if it is missing from the url.
*
* @param url Url.
* @param implicitProtocol Protocol to be used if the url doesn't have any.
* @return Url parts.
*/
static parse(url: string, implicitProtocol?: string): UrlParts | null {
// Prepare url before parsing
url = url.trim();
if (implicitProtocol && !url.match(/^[a-zA-Z]+:\/\//)) {
url = `${implicitProtocol}://${url}`;
}
// Regular expression taken from RFC 3986: https://tools.ietf.org/html/rfc3986#appendix-B
static parse(url: string): UrlParts | null {
// Parse url with regular expression taken from RFC 3986: https://tools.ietf.org/html/rfc3986#appendix-B.
const match = url.trim().match(/^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/);
if (!match) {
return null;
}
// Prepare parts replacing empty strings with undefined
// Split host into domain and port.
const host = match[4] || '';
const [domain, port]: string[] = host.indexOf(':') === -1 ? [host] : host.split(':');
// Prepare parts replacing empty strings with undefined.
return {
protocol: match[2] || undefined,
domain: match[4] || undefined,
domain: domain || undefined,
port: port || undefined,
path: match[5] || undefined,
query: match[7] || undefined,
fragment: match[9] || undefined,
};
}
/**
* Guess the Moodle domain from a site url.
*
* @param url Site url.
* @return Guessed Moodle domain.
*/
static guessMoodleDomain(url: string): string | null {
// Add protocol if it was missing. Moodle can only be served through http or https, so this is a fair assumption to make.
if (!url.match(/^https?:\/\//)) {
url = `https://${url}`;
}
// Match using common suffixes.
const knownSuffixes = [
'\/my\/?',
'\/\\\?redirect=0',
'\/index\\\.php',
'\/course\/view\\\.php',
'\/login\/index\\\.php',
'\/mod\/page\/view\\\.php',
];
const match = url.match(new RegExp(`^https?:\/\/(.*?)(${knownSuffixes.join('|')})`));
if (match) {
return match[1];
}
// If nothing else worked, parse the domain.
const urlParts = CoreUrl.parse(url);
return urlParts && urlParts.domain ? urlParts.domain : null;
}
}

View File

@ -87,6 +87,8 @@ export class CoreLoginSitePage {
return;
}
url = url.trim();
const modal = this.domUtils.showModalLoading(),
siteData = this.sitesProvider.getDemoSiteData(url);
@ -113,18 +115,12 @@ export class CoreLoginSitePage {
} else {
// Not a demo site.
this.sitesProvider.checkSite(url)
// Attempt parsing the domain after initial check failed
.catch((error) => {
const urlParts = CoreUrl.parse(url, 'http');
// Attempt guessing the domain if the initial check failed
const domain = CoreUrl.guessMoodleDomain(url);
if (!urlParts || !urlParts.domain) {
throw error;
}
return this.sitesProvider.checkSite(urlParts.domain);
return domain ? this.sitesProvider.checkSite(domain) : Promise.reject(error);
})
.then((result) => this.login(result))
.catch((error) => this.showLoginIssue(url, error))
.finally(() => modal.dismiss());
@ -177,7 +173,7 @@ export class CoreLoginSitePage {
*
* @return Promise resolved after logging in.
*/
private async login(response: CoreSiteCheckResponse): Promise<void> {
protected async login(response: CoreSiteCheckResponse): Promise<void> {
return this.sitesProvider.checkRequiredMinimumVersion(response.config).then(() => {
if (response.warning) {
this.domUtils.showErrorModal(response.warning, true, 4000);