From 7ddebe63c542bdd6ca7ffd0c1d27dd643d2a5c43 Mon Sep 17 00:00:00 2001 From: Steven Partridge Date: Wed, 22 Jan 2025 16:01:56 -0800 Subject: [PATCH 1/4] Uses TLD to check for subdomain status, rather than a hardcoded list of TLDs on the Front End --- .../connect-domain-step-suggested-start.jsx | 7 +++++-- client/components/domains/connect-domain-step/constants.ts | 1 + client/lib/domains/is-subdomain.ts | 7 ++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/client/components/domains/connect-domain-step/connect-domain-step-suggested-start.jsx b/client/components/domains/connect-domain-step/connect-domain-step-suggested-start.jsx index eb41a8749afb3e..ab3fb2f41939ec 100644 --- a/client/components/domains/connect-domain-step/connect-domain-step-suggested-start.jsx +++ b/client/components/domains/connect-domain-step/connect-domain-step-suggested-start.jsx @@ -26,16 +26,18 @@ export default function ConnectDomainStepSuggestedStart( { onNextStep, progressStepList, setPage, + domainSetupInfo, } ) { const { __ } = useI18n(); + const { data } = domainSetupInfo; const selectedSite = useSelector( getSelectedSite ); const goToDnsRecordsPage = () => page( domainManagementDns( selectedSite?.slug, domain ) ); - const firstStep = isSubdomain( domain ) + const firstStep = isSubdomain( domain, data?.tld ) ? stepSlug.SUBDOMAIN_ADVANCED_START : stepSlug.ADVANCED_START; const switchToAdvancedSetup = () => setPage( firstStep ); - const message = isSubdomain( domain ) + const message = isSubdomain( domain, data?.tld ) ? __( 'The easiest way to connect your subdomain is by changing NS records. But if you are unable to do this, then switch to our advanced setup, using A & CNAME records.' ) @@ -130,4 +132,5 @@ ConnectDomainStepSuggestedStart.propTypes = { onNextStep: PropTypes.func.isRequired, progressStepList: PropTypes.object.isRequired, setPage: PropTypes.func.isRequired, + domainSetupInfo: PropTypes.object.isRequired, }; diff --git a/client/components/domains/connect-domain-step/constants.ts b/client/components/domains/connect-domain-step/constants.ts index b0e1af78b64eb0..39ece1ed166112 100644 --- a/client/components/domains/connect-domain-step/constants.ts +++ b/client/components/domains/connect-domain-step/constants.ts @@ -58,6 +58,7 @@ export const defaultDomainSetupInfo = { data: { default_ip_addresses: [ '192.0.78.24', '192.0.78.25' ], wpcom_name_servers: [ 'ns1.wordpress.com', 'ns2.wordpress.com', 'ns3.wordpress.com' ], + tld: null, }, } as const; diff --git a/client/lib/domains/is-subdomain.ts b/client/lib/domains/is-subdomain.ts index 3e42776b768c24..dedd164eaba8e3 100644 --- a/client/lib/domains/is-subdomain.ts +++ b/client/lib/domains/is-subdomain.ts @@ -1,9 +1,14 @@ import { getRootDomain } from 'calypso/lib/domains/utils'; -export function isSubdomain( domainName: string ): boolean { +export function isSubdomain( domainName: string, tld?: string ): boolean { if ( ! domainName || domainName === '' ) { return false; } + + if ( tld && domainName.endsWith( tld ) ) { + return false; + } + const isValidSubdomain = Boolean( domainName.match( /^([a-z0-9_]([a-z0-9\-_]*[a-z0-9_])?\.)+[a-z0-9]([a-z0-9-]*[a-z0-9])?\.[a-z]{2,63}$/ From 54cd414f3a6e8af4984f89497a3db2d4c6fef6b6 Mon Sep 17 00:00:00 2001 From: Steven Partridge Date: Thu, 23 Jan 2025 13:33:53 -0800 Subject: [PATCH 2/4] Alternative approach to avoid false positives --- client/lib/domains/is-subdomain.ts | 60 +++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/client/lib/domains/is-subdomain.ts b/client/lib/domains/is-subdomain.ts index dedd164eaba8e3..0f2ceb651dae13 100644 --- a/client/lib/domains/is-subdomain.ts +++ b/client/lib/domains/is-subdomain.ts @@ -5,15 +5,65 @@ export function isSubdomain( domainName: string, tld?: string ): boolean { return false; } - if ( tld && domainName.endsWith( tld ) ) { - return false; - } - const isValidSubdomain = Boolean( domainName.match( /^([a-z0-9_]([a-z0-9\-_]*[a-z0-9_])?\.)+[a-z0-9]([a-z0-9-]*[a-z0-9])?\.[a-z]{2,63}$/ ) ); - return isValidSubdomain && getRootDomain( domainName ) !== domainName; + // If it doesn't even match the subdomain "shape", just return false. + if ( ! isValidSubdomain ) { + return false; + } + + // If the second param isn't a string, treat it as though no TLD was passed + // For situations where function is called like: map( domains, 'name' ).every( isSubdomain ) + let lowerTld: string | undefined; + if ( typeof tld === 'string' ) { + lowerTld = tld.toLowerCase(); + } + + if ( ! lowerTld ) { + // No TLD provided => original logic based on hard coded list of SLDs + return getRootDomain( domainName ) !== domainName; + } + + // ----- TLD provided: let’s verify the domain actually ends in that TLD. ----- + + const lowerDomain = domainName.toLowerCase(); + const domainParts = lowerDomain.split( '.' ); + const tldParts = lowerTld.split( '.' ); + + // If domain doesn't have at least as many parts as the TLD, can't match it. + if ( domainParts.length < tldParts.length ) { + // Fallback to your original logic + return getRootDomain( domainName ) !== domainName; + } + + // Compare trailing parts of the domain to see if they match exactly the TLD + const domainEnding = domainParts.slice( domainParts.length - tldParts.length ); + + if ( ! arraysMatch( domainEnding, tldParts ) ) { + // If it doesn't actually end with the TLD, fallback to original logic + return getRootDomain( domainName ) !== domainName; + } + + // At this point, the domain does end with the TLD. Count leftover labels. + // Example: + // subdomain.domain.edu.my + // domainParts = [ 'subdomain', 'domain', 'edu', 'my' ] + // tldParts = [ 'edu', 'my' ] + // leftover = [ 'subdomain', 'domain' ] => leftover.length = 2 => subdomain + const leftover = domainParts.slice( 0, domainParts.length - tldParts.length ); + + // If leftover is > 1, that means we have at least "sub.whatever.edu.my", so subdomain = true + // If leftover is <= 1, that’s effectively "domain.edu.my" => not a subdomain + return leftover.length > 1; +} + +function arraysMatch( a: string[], b: string[] ): boolean { + if ( a.length !== b.length ) { + return false; + } + return a.every( ( val, i ) => val === b[ i ] ); } From 7805c6c2406ed16174c090b9d3ebd7ad89fb050b Mon Sep 17 00:00:00 2001 From: Steven Partridge Date: Thu, 23 Jan 2025 16:58:06 -0800 Subject: [PATCH 3/4] Minor comments adjustment --- client/lib/domains/is-subdomain.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/lib/domains/is-subdomain.ts b/client/lib/domains/is-subdomain.ts index 0f2ceb651dae13..8a4a714cc61887 100644 --- a/client/lib/domains/is-subdomain.ts +++ b/client/lib/domains/is-subdomain.ts @@ -36,7 +36,7 @@ export function isSubdomain( domainName: string, tld?: string ): boolean { // If domain doesn't have at least as many parts as the TLD, can't match it. if ( domainParts.length < tldParts.length ) { - // Fallback to your original logic + // Fallback to list of SLDs logic return getRootDomain( domainName ) !== domainName; } @@ -44,7 +44,7 @@ export function isSubdomain( domainName: string, tld?: string ): boolean { const domainEnding = domainParts.slice( domainParts.length - tldParts.length ); if ( ! arraysMatch( domainEnding, tldParts ) ) { - // If it doesn't actually end with the TLD, fallback to original logic + // If it doesn't actually end with the TLD, fallback to list of SLDs logic return getRootDomain( domainName ) !== domainName; } From 5546128ebda1483670f7621d37fdb969bded2f9a Mon Sep 17 00:00:00 2001 From: Steven Partridge Date: Mon, 27 Jan 2025 12:45:26 -0800 Subject: [PATCH 4/4] Removes logic, backend already has this stuff nicely --- .../connect-domain-step-suggested-start.jsx | 5 +- .../domains/connect-domain-step/constants.ts | 2 +- client/lib/domains/is-subdomain.ts | 59 +------------------ 3 files changed, 5 insertions(+), 61 deletions(-) diff --git a/client/components/domains/connect-domain-step/connect-domain-step-suggested-start.jsx b/client/components/domains/connect-domain-step/connect-domain-step-suggested-start.jsx index ab3fb2f41939ec..0fa1ff7fcc7271 100644 --- a/client/components/domains/connect-domain-step/connect-domain-step-suggested-start.jsx +++ b/client/components/domains/connect-domain-step/connect-domain-step-suggested-start.jsx @@ -11,7 +11,6 @@ import { stepsHeading, stepSlug, } from 'calypso/components/domains/connect-domain-step/constants'; -import { isSubdomain } from 'calypso/lib/domains'; import { domainManagementDns } from 'calypso/my-sites/domains/paths'; import { getSelectedSite } from 'calypso/state/ui/selectors'; import ConnectDomainStepWrapper from './connect-domain-step-wrapper'; @@ -32,12 +31,12 @@ export default function ConnectDomainStepSuggestedStart( { const { data } = domainSetupInfo; const selectedSite = useSelector( getSelectedSite ); const goToDnsRecordsPage = () => page( domainManagementDns( selectedSite?.slug, domain ) ); - const firstStep = isSubdomain( domain, data?.tld ) + const firstStep = data?.is_subdomain ? stepSlug.SUBDOMAIN_ADVANCED_START : stepSlug.ADVANCED_START; const switchToAdvancedSetup = () => setPage( firstStep ); - const message = isSubdomain( domain, data?.tld ) + const message = data?.is_subdomain ? __( 'The easiest way to connect your subdomain is by changing NS records. But if you are unable to do this, then switch to our advanced setup, using A & CNAME records.' ) diff --git a/client/components/domains/connect-domain-step/constants.ts b/client/components/domains/connect-domain-step/constants.ts index 39ece1ed166112..132dee3fc4599c 100644 --- a/client/components/domains/connect-domain-step/constants.ts +++ b/client/components/domains/connect-domain-step/constants.ts @@ -58,7 +58,7 @@ export const defaultDomainSetupInfo = { data: { default_ip_addresses: [ '192.0.78.24', '192.0.78.25' ], wpcom_name_servers: [ 'ns1.wordpress.com', 'ns2.wordpress.com', 'ns3.wordpress.com' ], - tld: null, + is_subdomain: false, }, } as const; diff --git a/client/lib/domains/is-subdomain.ts b/client/lib/domains/is-subdomain.ts index 8a4a714cc61887..3e42776b768c24 100644 --- a/client/lib/domains/is-subdomain.ts +++ b/client/lib/domains/is-subdomain.ts @@ -1,69 +1,14 @@ import { getRootDomain } from 'calypso/lib/domains/utils'; -export function isSubdomain( domainName: string, tld?: string ): boolean { +export function isSubdomain( domainName: string ): boolean { if ( ! domainName || domainName === '' ) { return false; } - const isValidSubdomain = Boolean( domainName.match( /^([a-z0-9_]([a-z0-9\-_]*[a-z0-9_])?\.)+[a-z0-9]([a-z0-9-]*[a-z0-9])?\.[a-z]{2,63}$/ ) ); - // If it doesn't even match the subdomain "shape", just return false. - if ( ! isValidSubdomain ) { - return false; - } - - // If the second param isn't a string, treat it as though no TLD was passed - // For situations where function is called like: map( domains, 'name' ).every( isSubdomain ) - let lowerTld: string | undefined; - if ( typeof tld === 'string' ) { - lowerTld = tld.toLowerCase(); - } - - if ( ! lowerTld ) { - // No TLD provided => original logic based on hard coded list of SLDs - return getRootDomain( domainName ) !== domainName; - } - - // ----- TLD provided: let’s verify the domain actually ends in that TLD. ----- - - const lowerDomain = domainName.toLowerCase(); - const domainParts = lowerDomain.split( '.' ); - const tldParts = lowerTld.split( '.' ); - - // If domain doesn't have at least as many parts as the TLD, can't match it. - if ( domainParts.length < tldParts.length ) { - // Fallback to list of SLDs logic - return getRootDomain( domainName ) !== domainName; - } - - // Compare trailing parts of the domain to see if they match exactly the TLD - const domainEnding = domainParts.slice( domainParts.length - tldParts.length ); - - if ( ! arraysMatch( domainEnding, tldParts ) ) { - // If it doesn't actually end with the TLD, fallback to list of SLDs logic - return getRootDomain( domainName ) !== domainName; - } - - // At this point, the domain does end with the TLD. Count leftover labels. - // Example: - // subdomain.domain.edu.my - // domainParts = [ 'subdomain', 'domain', 'edu', 'my' ] - // tldParts = [ 'edu', 'my' ] - // leftover = [ 'subdomain', 'domain' ] => leftover.length = 2 => subdomain - const leftover = domainParts.slice( 0, domainParts.length - tldParts.length ); - - // If leftover is > 1, that means we have at least "sub.whatever.edu.my", so subdomain = true - // If leftover is <= 1, that’s effectively "domain.edu.my" => not a subdomain - return leftover.length > 1; -} - -function arraysMatch( a: string[], b: string[] ): boolean { - if ( a.length !== b.length ) { - return false; - } - return a.every( ( val, i ) => val === b[ i ] ); + return isValidSubdomain && getRootDomain( domainName ) !== domainName; }