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; domain?: string;
/**
* Url port.
*/
port?: string;
/** /**
* Url path. * Url path.
*/ */
@ -49,39 +54,69 @@ interface UrlParts {
*/ */
export class CoreUrl { export class CoreUrl {
// Avoid creating singleton instances // Avoid creating singleton instances.
private constructor() {} private constructor() {}
/** /**
* Parse parts of a url, using an implicit protocol if it is missing from the url. * Parse parts of a url, using an implicit protocol if it is missing from the url.
* *
* @param url Url. * @param url Url.
* @param implicitProtocol Protocol to be used if the url doesn't have any.
* @return Url parts. * @return Url parts.
*/ */
static parse(url: string, implicitProtocol?: string): UrlParts | null { static parse(url: string): UrlParts | null {
// Prepare url before parsing // Parse url with regular expression taken from RFC 3986: https://tools.ietf.org/html/rfc3986#appendix-B.
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
const match = url.trim().match(/^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/); const match = url.trim().match(/^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/);
if (!match) { if (!match) {
return null; 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 { return {
protocol: match[2] || undefined, protocol: match[2] || undefined,
domain: match[4] || undefined, domain: domain || undefined,
port: port || undefined,
path: match[5] || undefined, path: match[5] || undefined,
query: match[7] || undefined, query: match[7] || undefined,
fragment: match[9] || 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; return;
} }
url = url.trim();
const modal = this.domUtils.showModalLoading(), const modal = this.domUtils.showModalLoading(),
siteData = this.sitesProvider.getDemoSiteData(url); siteData = this.sitesProvider.getDemoSiteData(url);
@ -113,18 +115,12 @@ export class CoreLoginSitePage {
} else { } else {
// Not a demo site. // Not a demo site.
this.sitesProvider.checkSite(url) this.sitesProvider.checkSite(url)
// Attempt parsing the domain after initial check failed
.catch((error) => { .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) { return domain ? this.sitesProvider.checkSite(domain) : Promise.reject(error);
throw error;
}
return this.sitesProvider.checkSite(urlParts.domain);
}) })
.then((result) => this.login(result)) .then((result) => this.login(result))
.catch((error) => this.showLoginIssue(url, error)) .catch((error) => this.showLoginIssue(url, error))
.finally(() => modal.dismiss()); .finally(() => modal.dismiss());
@ -177,7 +173,7 @@ export class CoreLoginSitePage {
* *
* @return Promise resolved after logging in. * @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(() => { return this.sitesProvider.checkRequiredMinimumVersion(response.config).then(() => {
if (response.warning) { if (response.warning) {
this.domUtils.showErrorModal(response.warning, true, 4000); this.domUtils.showErrorModal(response.warning, true, 4000);