فهرست منبع

Merge pull request #367 from Troglodyne-Internet-Widgets/pod-and-classes

Fix the change request token check, spell POD in American English, and name character classes
George S. Baugh 1 هفته پیش
والد
کامیت
d0d036feac

+ 4 - 4
.perlcriticrc

@@ -192,9 +192,9 @@ allow = re Exporter
 [ControlStructures::ProhibitUnreachableCode]
 [ControlStructures::ProhibitUnreachableCode]
 [ControlStructures::ProhibitUntilBlocks]
 [ControlStructures::ProhibitUntilBlocks]
 [ControlStructures::ProhibitYadaOperator]
 [ControlStructures::ProhibitYadaOperator]
-#[Documentation::PodSpelling]
-#spell_command = aspell --lang=en_US list
-#stop_words_file = .pod_stopwords
+[Documentation::PodSpelling]
+spell_command = aspell --lang=en_US list
+stop_words_file = .pod_stopwords
 [Documentation::RequirePackageMatchesPodName]
 [Documentation::RequirePackageMatchesPodName]
 [ErrorHandling::RequireCheckingReturnValueOfEval]
 [ErrorHandling::RequireCheckingReturnValueOfEval]
 [InputOutput::ProhibitExplicitStdin]
 [InputOutput::ProhibitExplicitStdin]
@@ -221,7 +221,7 @@ allow = re Exporter
 #[NamingConventions::Capitalization]
 #[NamingConventions::Capitalization]
 [RegularExpressions::ProhibitCaptureWithoutTest]
 [RegularExpressions::ProhibitCaptureWithoutTest]
 #[RegularExpressions::ProhibitComplexRegexes]
 #[RegularExpressions::ProhibitComplexRegexes]
-#[RegularExpressions::ProhibitEnumeratedClasses]
+[RegularExpressions::ProhibitEnumeratedClasses]
 [RegularExpressions::ProhibitFixedStringMatches]
 [RegularExpressions::ProhibitFixedStringMatches]
 [RegularExpressions::ProhibitSingleCharAlternation]
 [RegularExpressions::ProhibitSingleCharAlternation]
 [RegularExpressions::ProhibitUnusedCapture]
 [RegularExpressions::ProhibitUnusedCapture]

+ 151 - 1
.pod_stopwords

@@ -12,16 +12,23 @@
 # line.  PodSpelling reads the AUTHORS and COPYRIGHT sections that Pod::Weaver
 # line.  PodSpelling reads the AUTHORS and COPYRIGHT sections that Pod::Weaver
 # generates, and a surname is in no dictionary.  The licence words below are
 # generates, and a surname is in no dictionary.  The licence words below are
 # from the text weaver.ini puts in the COPYRIGHT section of every module.
 # from the text weaver.ini puts in the COPYRIGHT section of every module.
-
 accessor
 accessor
+accessors
 ACL
 ACL
+acl
+acls
 addon
 addon
+aes
 AIO
 AIO
+allowlist
 API
 API
 arg
 arg
 args
 args
 argv
 argv
 arrayref
 arrayref
+auth
+authdb
+authenticator
 autodetect
 autodetect
 autodetected
 autodetected
 autodetecting
 autodetecting
@@ -32,7 +39,12 @@ backends
 BackPAN
 BackPAN
 backtick
 backtick
 basename
 basename
+baz
+bool
 bugtracker
 bugtracker
+cacheable
+cfg
+checkboxes
 checksummed
 checksummed
 checksumming
 checksumming
 CHECKSUMS
 CHECKSUMS
@@ -51,71 +63,130 @@ chrooting
 chroots
 chroots
 CIDR
 CIDR
 CIDRs
 CIDRs
+ciphertext
 CLI
 CLI
 codename
 codename
 coderef
 coderef
+conf
 config
 config
 configs
 configs
+contactemail
 CPAN
 CPAN
 cpanm
 cpanm
 CPUs
 CPUs
 cron
 cron
 crons
 crons
+csp
+css
 CVE
 CVE
+datasource
+datasources
+datastore
 DDL
 DDL
+decrypt
+decrypted
+deduped
 deduplicate
 deduplicate
 deduplicated
 deduplicated
 deduplicates
 deduplicates
 deduplicating
 deduplicating
 dep
 dep
 deps
 deps
+dereference
 dev
 dev
 DHCP
 DHCP
 dir
 dir
+dirs
+displayname
 distro
 distro
 distros
 distros
 DKIM
 DKIM
 DMARC
 DMARC
 DN
 DN
 DNS
 DNS
+doctype
+dom
 dotfiles
 dotfiles
+dropdown
 dryrun
 dryrun
 DSN
 DSN
 dzil
 dzil
 ECDSA
 ECDSA
 ENV
 ENV
 env
 env
+etags
+favicon
+fb
 filehandle
 filehandle
 filesystem
 filesystem
 filetest
 filetest
+flatfile
+footbar
 formatter
 formatter
 formatters
 formatters
 FQDN
 FQDN
+fqdn
+fred
+frontend
+FTS
+fts
+gcm
 GiB
 GiB
+gitignore
 GraphQL
 GraphQL
 greppable
 greppable
+gzip
 hardcode
 hardcode
 hardcoded
 hardcoded
 hardcodes
 hardcodes
 hardcoding
 hardcoding
 hashref
 hashref
+hashrefs
 heredoc
 heredoc
 hostname
 hostname
 hotplug
 hotplug
+href
+hrefs
+hsts
+html
 HTTPS
 HTTPS
+https
+hup
+ico
+imager
 IMAP
 IMAP
+indexable
 INI
 INI
+ini
 init
 init
+inkscape
+inlined
+inlining
+inotify
+ipads
+iphones
 IPs
 IPs
 IPv
 IPv
+journald
+js
+jsalert
 JSON
 JSON
+json
+keepass
 keypair
 keypair
 keyspace
 keyspace
 KiB
 KiB
+kolon
+lang
 LDAP
 LDAP
 LDAPS
 LDAPS
 LDIF
 LDIF
+leftbar
+libmagic
+libravatar
+libvirt
 lifecycle
 lifecycle
+logname
 lookups
 lookups
 loopback
 loopback
 lowercased
 lowercased
@@ -123,6 +194,7 @@ lowercases
 lowercasing
 lowercasing
 LTS
 LTS
 MACs
 MACs
+Makefile
 makefile
 makefile
 makefiles
 makefiles
 memoize
 memoize
@@ -132,39 +204,68 @@ memoizing
 MERCHANTABILITY
 MERCHANTABILITY
 MetaCPAN
 MetaCPAN
 MiB
 MiB
+midtitle
+mimetype
+minify
 misconfigure
 misconfigure
 misconfigured
 misconfigured
 misconfigures
 misconfigures
 misconfiguring
 misconfiguring
 mistyped
 mistyped
+msg
 mtime
 mtime
+mtimes
+multiline
 MX
 MX
 nameserver
 nameserver
 nameservers
 nameservers
 namespace
 namespace
+nginx
+nginxproxy
 NIC
 NIC
+nocache
 NONINFRINGEMENT
 NONINFRINGEMENT
+nosniff
 NTP
 NTP
+nullary
 OAuth
 OAuth
 OOM
 OOM
+OpenAPIv
+openapiv
 overcommit
 overcommit
 overcommits
 overcommits
 overcommitted
 overcommitted
 overcommitting
 overcommitting
+paginator
+param
+params
 passwordless
 passwordless
+payor
 PCI
 PCI
 PCIe
 PCIe
+pdf
+pdfs
 PEM
 PEM
 perlcritic
 perlcritic
+perlcriticrc
 perltidy
 perltidy
 PKI
 PKI
 plaintext
 plaintext
+png
 POSIX
 POSIX
 pragmas
 pragmas
 preflight
 preflight
+prev
+provisioner
 proxied
 proxied
 proxying
 proxying
+psgi
+qemu
+queryable
+quotemeta'd
+ramdisk
 readdir
 readdir
+readme
 recurse
 recurse
 recursed
 recursed
 recurses
 recurses
@@ -173,32 +274,49 @@ regenerable
 renderer
 renderer
 repo
 repo
 repos
 repos
+reprovision
+reprovisioning
+requestid
+resetpass
+revalidate
+rightbar
 RPC
 RPC
 RSA
 RSA
+rss
 rsync
 rsync
 rsynced
 rsynced
 rsyncing
 rsyncing
 rsyncs
 rsyncs
+rsyslog
 ruleset
 ruleset
 runtime
 runtime
 scaffolded
 scaffolded
 scp
 scp
+sessid
+setsid'd
 sftp
 sftp
 SHA
 SHA
+sha
 SMTP
 SMTP
+snapshotting
 SRV
 SRV
 sshd
 sshd
 SSL
 SSL
+stderr
 stdin
 stdin
 stdout
 stdout
+stylesheet
+stylesheets
 Subclasses
 Subclasses
 subdir
 subdir
+subdirectories
 subdirectory
 subdirectory
 sublicense
 sublicense
 subnet
 subnet
 substring
 substring
 subtest
 subtest
 sudo
 sudo
+svg
 symlink
 symlink
 symlinked
 symlinked
 symlinking
 symlinking
@@ -206,17 +324,33 @@ symlinks
 sysctl
 sysctl
 syslog
 syslog
 systemd
 systemd
+TCMS
+tcms
 TCP
 TCP
 teardown
 teardown
 templated
 templated
 templating
 templating
+textarea
+themeclone
 timestamp
 timestamp
 TLD
 TLD
 TLDs
 TLDs
 TLS
 TLS
+tmux
 TODO
 TODO
+tokenizer
 toolchain
 toolchain
+topbar
+totp
+tpm
+tpsgi
+trigram
+trog
+troglodyne
+truthy
+tx
 TXT
 TXT
+txt
 udev
 udev
 UDP
 UDP
 UI
 UI
@@ -224,24 +358,40 @@ UID
 umask
 umask
 uncached
 uncached
 unconfigured
 unconfigured
+undecorated
 undef
 undef
 undefine
 undefine
 undefines
 undefines
 undefining
 undefining
+unindexed
+unix
 unlink
 unlink
 unlinked
 unlinked
 unlinking
 unlinking
 unlinks
 unlinks
+unparameterized
+unstretched
 URI
 URI
+uri
 URIs
 URIs
+useradd
 UUID
 UUID
+uuid
 validator
 validator
 vhost
 vhost
 vhosts
 vhosts
+virdomainscreenshot
+virt
 VM
 VM
 VMs
 VMs
 VPN
 VPN
 VPNs
 VPNs
 WAL
 WAL
+webserver
 whitespace
 whitespace
+wilma
+xhr
+xml
+xsl
+xslate
 YAML
 YAML

+ 2 - 2
lib/TCMS.pm

@@ -206,13 +206,13 @@ sub build_routes {
 
 
 Whether this request should be turned around and sent to /totp.
 Whether this request should be turned around and sent to /totp.
 
 
-Two factors or nothing.  A login is what gets you to the enrolment page -- the QR
+Two factors or nothing.  A login is what gets you to the enrollment page -- the QR
 is the shared secret, so it cannot be handed to whoever asks for it -- which
 is the shared secret, so it cannot be handed to whoever asks for it -- which
 leaves a window where somebody is logged in and has no second factor yet.  This
 leaves a window where somebody is logged in and has no second factor yet.  This
 is what makes that window one page wide: every route a login can reach, and no
 is what makes that window one page wide: every route a login can reach, and no
 route a logged out visitor can, is refused until they have enrolled.
 route a logged out visitor can, is refused until they have enrolled.
 
 
-The enrolment page and the QR on it say so themselves, with totp_exempt, because
+The enrollment page and the QR on it say so themselves, with totp_exempt, because
 the exemption belongs next to the route rather than in a list over here that the
 the exemption belongs next to the route rather than in a list over here that the
 routing table knows nothing about.
 routing table knows nothing about.
 
 

+ 5 - 5
lib/Trog/Auth.pm

@@ -193,8 +193,8 @@ sub totp ( $user, $domain ) {
     # it is given -- so one containing a slash or .. would otherwise write
     # it is given -- so one containing a slash or .. would otherwise write
     # outside totp/ entirely.  Same whitelist Trog::DataSource::Virt uses for
     # outside totp/ entirely.  Same whitelist Trog::DataSource::Virt uses for
     # guest names.
     # guest names.
-    my ($safe_user)   = ( $user   // '' ) =~ m/^([A-Za-z0-9._-]+)$/;
-    my ($safe_domain) = ( $domain // '' ) =~ m/^([A-Za-z0-9._-]+)$/;
+    my ($safe_user)   = ( $user   // '' ) =~ m/^([\w.-]+)$/;
+    my ($safe_domain) = ( $domain // '' ) =~ m/^([\w.-]+)$/;
     return ( undef, undef, 1, "Refusing to build a TOTP QR for unsafe username '" . ( $user // '' ) . "'." ) unless $safe_user;
     return ( undef, undef, 1, "Refusing to build a TOTP QR for unsafe username '" . ( $user // '' ) . "'." ) unless $safe_user;
     return ( undef, undef, 1, "Refusing to build a TOTP QR for unsafe domain '" . ( $domain // '' ) . "'." ) unless $safe_domain;
     return ( undef, undef, 1, "Refusing to build a TOTP QR for unsafe domain '" . ( $domain // '' ) . "'." ) unless $safe_domain;
 
 
@@ -271,8 +271,8 @@ nowhere else until they have.
 
 
 There is no way to turn it back off.  Losing an authenticator is not a reason to
 There is no way to turn it back off.  Losing an authenticator is not a reason to
 stop having a second factor, and the answer to it is bin/totp, which re-reads a
 stop having a second factor, and the answer to it is bin/totp, which re-reads a
-user their enrolment out of band -- the secret is still in the database, so it
-is the same enrolment rather than a new one.
+user their enrollment out of band -- the secret is still in the database, so it
+is the same enrollment rather than a new one.
 
 
 =cut
 =cut
 
 
@@ -310,7 +310,7 @@ our $totp_period    = 30;
 our $totp_digits    = 6;
 our $totp_digits    = 6;
 
 
 sub spend_totp ( $user, $code ) {
 sub spend_totp ( $user, $code ) {
-    return ( 0, 'no code given' ) unless defined $code && $code =~ m/^[0-9]{$totp_digits}$/;
+    return ( 0, 'no code given' ) unless defined $code && $code =~ m/^\d{$totp_digits}$/;
 
 
     my $dbh    = dbh();
     my $dbh    = dbh();
     my $rows   = $dbh->selectall_arrayref( "SELECT totp_secret FROM user WHERE name=?", { Slice => {} }, $user );
     my $rows   = $dbh->selectall_arrayref( "SELECT totp_secret FROM user WHERE name=?", { Slice => {} }, $user );

+ 2 - 2
lib/Trog/Component.pm

@@ -32,7 +32,7 @@ function call, hands them to its warn handler, and carries on with an empty
 string where the call was -- so a broken component would otherwise vanish from
 string where the call was -- so a broken component would otherwise vanish from
 the page with nothing but a line on stderr to say so.  The failure is therefore
 the page with nothing but a line on stderr to say so.  The failure is therefore
 also recorded in $Trog::Component::error, which Trog::Renderer::Base::render()
 also recorded in $Trog::Component::error, which Trog::Renderer::Base::render()
-localises and checks, turning it into the 500 it deserves.
+localizes and checks, turning it into the 500 it deserves.
 
 
 =head1 FUNCTIONS
 =head1 FUNCTIONS
 
 
@@ -54,7 +54,7 @@ my sub load ($name) {
     state %loaded;
     state %loaded;
 
 
     die "Component name must be given\n"   unless $name;
     die "Component name must be given\n"   unless $name;
-    die "Invalid component name '$name'\n" unless $name =~ m/^[A-Za-z][A-Za-z0-9_]*$/;
+    die "Invalid component name '$name'\n" unless $name =~ m/^[[:alpha:]]\w*$/;
 
 
     my $module = "Trog::Component::$name";
     my $module = "Trog::Component::$name";
     return $module if $loaded{$module};
     return $module if $loaded{$module};

+ 3 - 3
lib/Trog/Data/SQLite.pm

@@ -174,12 +174,12 @@ my sub where ($query) {
     # version's, so older/newer have always meant the post's birthday rather
     # version's, so older/newer have always meant the post's birthday rather
     # than the date of its most recent edit.
     # than the date of its most recent edit.
     if ( $query->{older} ) {
     if ( $query->{older} ) {
-        ( my $older = $query->{older} ) =~ s/[^0-9]//g;
+        ( my $older = $query->{older} ) =~ s/\D//g;
         push( @where, 'v.first_created < ?' );
         push( @where, 'v.first_created < ?' );
         push( @bind,  $older );
         push( @bind,  $older );
     }
     }
     if ( $query->{newer} ) {
     if ( $query->{newer} ) {
-        ( my $newer = $query->{newer} ) =~ s/[^0-9]//g;
+        ( my $newer = $query->{newer} ) =~ s/\D//g;
         push( @where, 'v.first_created > ?' );
         push( @where, 'v.first_created > ?' );
         push( @bind,  $newer );
         push( @bind,  $newer );
     }
     }
@@ -428,7 +428,7 @@ sub index_fields ( $, @fields ) {
 
 
     my %want;
     my %want;
     foreach my $field (@fields) {
     foreach my $field (@fields) {
-        my ($safe) = ( $field // '' ) =~ m/^([a-z][a-z0-9_]{0,31})$/;
+        my ($safe) = ( $field // '' ) =~ m/^([[:lower:]][[:lower:]\d_]{0,31})$/;
         if ( !defined $safe ) {
         if ( !defined $safe ) {
             WARN( "Refusing to index '" . ( $field // '' ) . "': not a field name" );
             WARN( "Refusing to index '" . ( $field // '' ) . "': not a field name" );
             next;
             next;

+ 8 - 8
lib/Trog/DataModule.pm

@@ -72,7 +72,7 @@ Documentation for that query language, shown to the user alongside it.
 
 
 Every post the data model holds, as an arrayref of hashrefs.  Filtering is
 Every post the data model holds, as an arrayref of hashrefs.  Filtering is
 get()'s job unless your storage engine can do it more cheaply itself, in which
 get()'s job unless your storage engine can do it more cheaply itself, in which
-case honour $query here and override get() too.
+case honor $query here and override get() too.
 
 
 =head2 write($posts)
 =head2 write($posts)
 
 
@@ -230,11 +230,11 @@ sub filter ( $, $query, @filtered ) {
     #Filter out posts which are too old
     #Filter out posts which are too old
     #Coerce older into numeric
     #Coerce older into numeric
     if ( $query->{older} ) {
     if ( $query->{older} ) {
-        $query->{older} =~ s/[^0-9]//g;
+        $query->{older} =~ s/\D//g;
         @filtered = grep { $_->{created} < $query->{older} } @filtered;
         @filtered = grep { $_->{created} < $query->{older} } @filtered;
     }
     }
     if ( $query->{newer} ) {
     if ( $query->{newer} ) {
-        $query->{newer} =~ s/[^0-9]//g;
+        $query->{newer} =~ s/\D//g;
         @filtered = grep { $_->{created} > $query->{newer} } @filtered;
         @filtered = grep { $_->{created} > $query->{newer} } @filtered;
     }
     }
 
 
@@ -454,7 +454,7 @@ my sub sidecar_generation {
 # explicitly undef form.
 # explicitly undef form.
 my sub type_of ($form) {
 my sub type_of ($form) {
     $form = '' if !defined $form || ref $form;
     $form = '' if !defined $form || ref $form;
-    my ($type) = $form =~ m/^([A-Za-z0-9_-]+)\.tx$/;    ## no critic (RegularExpressions::ProhibitRegexToStripAffix) -- the class is the check: anything else is no type
+    my ($type) = $form =~ m/^([\w-]+)\.tx$/;    ## no critic (RegularExpressions::ProhibitRegexToStripAffix) -- the class is the check: anything else is no type
     return $type // '';
     return $type // '';
 }
 }
 
 
@@ -595,7 +595,7 @@ my sub includes_of ($type) {
     # including the ones the wizard generates.  Deduped, since a template is
     # including the ones the wizard generates.  Deduped, since a template is
     # free to splice the same block in twice.
     # free to splice the same block in twice.
     my %seen;
     my %seen;
-    return [ grep { !$seen{$_}++ } $template =~ m/^\s*:\s*include\s+"([A-Za-z0-9_-]+\.tx)"/gm ];
+    return [ grep { !$seen{$_}++ } $template =~ m/^\s*:\s*include\s+"([\w-]+\.tx)"/gm ];
 }
 }
 
 
 =head2 includes_for($form)
 =head2 includes_for($form)
@@ -819,7 +819,7 @@ spot, but it was never asked.
 
 
 Checks rather than filters, unlike validate().  process() adds fields the base
 Checks rather than filters, unlike validate().  process() adds fields the base
 schema has never heard of -- content_type, is_video, attachments -- and dropping
 schema has never heard of -- content_type, is_video, attachments -- and dropping
-what it does not recognise would throw away the work it just did.  So this looks
+what it does not recognize would throw away the work it just did.  So this looks
 only at the fields the schema actually describes, on a copy, and changes nothing.
 only at the fields the schema actually describes, on a copy, and changes nothing.
 
 
 Returns the list of validation errors, empty when the post is fit to store.
 Returns the list of validation errors, empty when the post is fit to store.
@@ -1029,7 +1029,7 @@ sub delete { die 'stub' }
 =head2 routes() = HASH
 =head2 routes() = HASH
 
 
 Returns the routes to each post.
 Returns the routes to each post.
-You should override this for performance reasons, as it's just a wrapper around get() by defualt.
+You should override this for performance reasons, as it's just a wrapper around get() by default.
 
 
 =cut
 =cut
 
 
@@ -1041,7 +1041,7 @@ sub routes ($self) {
 =head2 aliases() = HASH
 =head2 aliases() = HASH
 
 
 Returns the aliases for each post, indexed by aliases.
 Returns the aliases for each post, indexed by aliases.
-You should override this for performance reasons, as it's just a wrapper around get() by defualt.
+You should override this for performance reasons, as it's just a wrapper around get() by default.
 
 
 =cut
 =cut
 
 

+ 2 - 2
lib/Trog/DataSource.pm

@@ -112,11 +112,11 @@ sub filter ( $query, @posts ) {
     # Coerced to a number the same way Trog::DataModule::filter() does it, since
     # Coerced to a number the same way Trog::DataModule::filter() does it, since
     # this arrives straight off a query string.
     # this arrives straight off a query string.
     if ( $query->{older} ) {
     if ( $query->{older} ) {
-        ( my $older = $query->{older} ) =~ s/[^0-9]//g;
+        ( my $older = $query->{older} ) =~ s/\D//g;
         @posts = grep { ( $_->{created} // 0 ) < $older } @posts if $older;
         @posts = grep { ( $_->{created} // 0 ) < $older } @posts if $older;
     }
     }
     if ( $query->{newer} ) {
     if ( $query->{newer} ) {
-        ( my $newer = $query->{newer} ) =~ s/[^0-9]//g;
+        ( my $newer = $query->{newer} ) =~ s/\D//g;
         @posts = grep { ( $_->{created} // 0 ) > $newer } @posts if $newer;
         @posts = grep { ( $_->{created} // 0 ) > $newer } @posts if $newer;
     }
     }
 
 

+ 1 - 1
lib/Trog/DataSource/DirIndex.pm

@@ -348,7 +348,7 @@ Ask tPSGI to tell us when the directory changes, and throw the static renders
 away when it does.
 away when it does.
 
 
 These pages are cached exactly like any other: the render goes through
 These pages are cached exactly like any other: the render goes through
-Trog::Renderer, which hands anonymous, unparameterised, successful renders to
+Trog::Renderer, which hands anonymous, unparameterized, successful renders to
 tPSGI to save.  That is the whole point -- a directory listing is cheap to build
 tPSGI to save.  That is the whole point -- a directory listing is cheap to build
 but there is no reason to build it per request -- but a cached listing of a
 but there is no reason to build it per request -- but a cached listing of a
 directory that has since changed is simply wrong, and nothing else would ever
 directory that has since changed is simply wrong, and nothing else would ever

+ 1 - 1
lib/Trog/DataSource/ProvisionedVirt.pm

@@ -88,7 +88,7 @@ my sub safe_domain ($domain) {
     return undef if !$domain || length $domain >= 254;
     return undef if !$domain || length $domain >= 254;
     return undef if $domain =~ m/(?:^|\.)\.\.?(?:\.|$)/;
     return undef if $domain =~ m/(?:^|\.)\.\.?(?:\.|$)/;
 
 
-    my ($safe) = $domain =~ m/^([A-Za-z0-9][A-Za-z0-9._-]*)$/;
+    my ($safe) = $domain =~ m/^([[:alnum:]][\w.-]*)$/;
     return $safe;
     return $safe;
 }
 }
 
 

+ 3 - 3
lib/Trog/DataSource/Virt.pm

@@ -320,10 +320,10 @@ other's console.
 =cut
 =cut
 
 
 sub screenshot ( $hypervisor, $guest_name ) {
 sub screenshot ( $hypervisor, $guest_name ) {
-    my ($safe) = ( $guest_name // '' ) =~ m/^([A-Za-z0-9._-]+)$/;
+    my ($safe) = ( $guest_name // '' ) =~ m/^([\w.-]+)$/;
     return ( undef, 'bad guest name' ) unless $safe;
     return ( undef, 'bad guest name' ) unless $safe;
 
 
-    my ($host) = ( $hypervisor->{id} // '' ) =~ m/^([A-Za-z0-9._-]+)$/;
+    my ($host) = ( $hypervisor->{id} // '' ) =~ m/^([\w.-]+)$/;
     return ( undef, 'bad hypervisor id' ) unless $host;
     return ( undef, 'bad hypervisor id' ) unless $host;
 
 
     my $conn_uri = $hypervisor->{conn_uri};
     my $conn_uri = $hypervisor->{conn_uri};
@@ -380,7 +380,7 @@ logged with the user who asked for it.
 =cut
 =cut
 
 
 sub act ( $action, $conn_uri, $domain_name, $user ) {
 sub act ( $action, $conn_uri, $domain_name, $user ) {
-    my ($safe) = $domain_name =~ m/^([A-Za-z0-9._-]+)$/;
+    my ($safe) = $domain_name =~ m/^([\w.-]+)$/;
     return ( 0, 'bad guest name' ) unless $safe;
     return ( 0, 'bad guest name' ) unless $safe;
 
 
     # The template draws neither of these buttons for this guest; this is what
     # The template draws neither of these buttons for this guest; this is what

+ 10 - 10
lib/Trog/Routes/HTML.pm

@@ -625,8 +625,8 @@ sub setup ($query) {
 Enable 2 factor auth via TOTP for the currently authenticated user.
 Enable 2 factor auth via TOTP for the currently authenticated user.
 Returns a page with a QR code & TOTP uri for pasting into your authenticator app of choice.
 Returns a page with a QR code & TOTP uri for pasting into your authenticator app of choice.
 
 
-Every user has to enrol, so this is where a login without one lands and the only
-page it can reach until it has one.  Showing the same enrolment again is the
+Every user has to enroll, so this is where a login without one lands and the only
+page it can reach until it has one.  Showing the same enrollment again is the
 point rather than a fallback: a user who lost their authenticator scans it again
 point rather than a fallback: a user who lost their authenticator scans it again
 and is back where they were.
 and is back where they were.
 
 
@@ -975,7 +975,7 @@ Routes for user service of their authentication details.
 
 
 There is no route here for turning TOTP off.  Everybody has it, and losing an
 There is no route here for turning TOTP off.  Everybody has it, and losing an
 authenticator is not a reason to stop having a second factor -- bin/totp reads a
 authenticator is not a reason to stop having a second factor -- bin/totp reads a
-user their existing enrolment back out of band, which is the same enrolment
+user their existing enrollment back out of band, which is the same enrollment
 rather than a new one.
 rather than a new one.
 
 
 =cut
 =cut
@@ -1428,7 +1428,7 @@ sub enrich_post ( $post, $query, $cache = {} ) {
         my $relation = $relations->{$as};
         my $relation = $relations->{$as};
         next unless Ref::Util::is_hashref($relation);
         next unless Ref::Util::is_hashref($relation);
 
 
-        my ($target) = ( $relation->{form} // '' ) =~ m/^([A-Za-z0-9_-]+\.tx)$/;
+        my ($target) = ( $relation->{form} // '' ) =~ m/^([\w-]+\.tx)$/;
         next unless $target;
         next unless $target;
 
 
         $cache->{$target} //= [ post_helper( { form => $target }, [], $query->{user_acls} ) ];
         $cache->{$target} //= [ post_helper( { form => $target }, [], $query->{user_acls} ) ];
@@ -2037,7 +2037,7 @@ sub paginate_offset ( $query, $limit, @posts ) {
     # newer get, rather than int() warning its way through a word.  Past the end
     # newer get, rather than int() warning its way through a word.  Past the end
     # shows the last page rather than an empty one, which is what a stale
     # shows the last page rather than an empty one, which is what a stale
     # bookmark deserves.
     # bookmark deserves.
-    ( my $wanted = $query->{page} // '' ) =~ s/[^0-9]//g;
+    ( my $wanted = $query->{page} // '' ) =~ s/\D//g;
     my $page = $wanted ? int($wanted) : 1;
     my $page = $wanted ? int($wanted) : 1;
     $page = 1            if $page < 1;
     $page = 1            if $page < 1;
     $page = $pages_total if $page > $pages_total;
     $page = $pages_total if $page > $pages_total;
@@ -2708,7 +2708,7 @@ land on the generation before this save.
 
 
 Returns the error if there was one, and nothing if all was well.  A failure to
 Returns the error if there was one, and nothing if all was well.  A failure to
 index is not a failure to create the post type: the type is written by this
 index is not a failure to create the post type: the type is written by this
-point, works, and is only missing an optimisation.
+point, works, and is only missing an optimization.
 
 
 =cut
 =cut
 
 
@@ -2851,7 +2851,7 @@ sub post_wizard_save ($query) {
     # Anchored capture rather than a substitution, so anything containing a '/'
     # Anchored capture rather than a substitution, so anything containing a '/'
     # or a '..' simply fails to match instead of being silently scrubbed into
     # or a '..' simply fails to match instead of being silently scrubbed into
     # something that still escapes the directory.
     # something that still escapes the directory.
-    my ($name) = wizard_scalar( $query->{name} ) =~ m/^([A-Za-z0-9_-]+)$/;
+    my ($name) = wizard_scalar( $query->{name} ) =~ m/^([\w-]+)$/;
     return wizard_fail( $query, "Post type names may only contain letters, numbers, '-' and '_'." ) unless $name;
     return wizard_fail( $query, "Post type names may only contain letters, numbers, '-' and '_'." ) unless $name;
 
 
     # Whichever forms dir is in use: the theme's if it has one, the stock one
     # Whichever forms dir is in use: the theme's if it has one, the stock one
@@ -3273,7 +3273,7 @@ sub wizard_fields ($query) {
 
 
     my ( @fields, %seen );
     my ( @fields, %seen );
     foreach my $index ( 0 .. $#$names ) {
     foreach my $index ( 0 .. $#$names ) {
-        my ($fname) = lc( wizard_scalar( $names->[$index] ) ) =~ m/^([a-z][a-z0-9_]{0,31})$/;
+        my ($fname) = lc( wizard_scalar( $names->[$index] ) ) =~ m/^([[:lower:]][[:lower:]\d_]{0,31})$/;
 
 
         # Blank and bogus rows just get dropped.
         # Blank and bogus rows just get dropped.
         next unless $fname;
         next unless $fname;
@@ -3288,7 +3288,7 @@ sub wizard_fields ($query) {
 
 
         # A relation is only a relation if it names a target type that looks
         # A relation is only a relation if it names a target type that looks
         # like one; anything else falls back to being a plain text field.
         # like one; anything else falls back to being a plain text field.
-        my ($rform) = wizard_scalar( $rforms->[$index] ) =~ m/^([A-Za-z0-9_-]+\.tx)$/;
+        my ($rform) = wizard_scalar( $rforms->[$index] ) =~ m/^([\w-]+\.tx)$/;
         my $rmode = wizard_scalar( $rmodes->[$index] ) eq 'all' ? 'all' : 'one';
         my $rmode = wizard_scalar( $rmodes->[$index] ) eq 'all' ? 'all' : 'one';
         $type = 'text' if $type eq 'relation' && !$rform;
         $type = 'text' if $type eq 'relation' && !$rform;
 
 
@@ -3414,7 +3414,7 @@ Implements the /totp/* routes.
 
 
 Serves the QR code generated when a user enables TOTP.  These live outside
 Serves the QR code generated when a user enables TOTP.  These live outside
 www/, so they go out through serve() rather than the static file handler --
 www/, so they go out through serve() rather than the static file handler --
-handing someone else's enrolment QR out to the world would be handing them the
+handing someone else's enrollment QR out to the world would be handing them the
 shared secret.
 shared secret.
 
 
 =cut
 =cut

+ 2 - 2
lib/Trog/Routes/JSON.pm

@@ -35,7 +35,7 @@ our %routes = (
         callback   => \&process_auth_change_request,
         callback   => \&process_auth_change_request,
         captures   => ['token'],
         captures   => ['token'],
         parameters => {
         parameters => {
-            token => sub { my $tok = shift; $tok =~ m/[a-f|0-9|-]+/; },
+            token => sub { my $tok = shift; $tok =~ m/\A[\da-f-]+\z/; },
         },
         },
         noindex    => 1,
         noindex    => 1,
         robot_name => '/api/auth_change_request/*',
         robot_name => '/api/auth_change_request/*',
@@ -46,7 +46,7 @@ our %routes = (
         parameters => {
         parameters => {
 
 
             # Same shape series.json demands of child_form.
             # Same shape series.json demands of child_form.
-            form => sub { $_[0] =~ m/^[A-Za-z0-9_-]+\.tx$/ },
+            form => sub { $_[0] =~ m/^[\w-]+\.tx$/ },
         },
         },
         callback => \&posts_of_form,
         callback => \&posts_of_form,
         noindex  => 1,
         noindex  => 1,

+ 1 - 1
lib/Trog/Vault.pm

@@ -199,7 +199,7 @@ my sub derive ($salt) {
 # a row to its owner, so it is a short plain thing or it is nothing.
 # a row to its owner, so it is a short plain thing or it is nothing.
 my sub safe_name ($name) {
 my sub safe_name ($name) {
     return 0 if !$name || length $name > 64;
     return 0 if !$name || length $name > 64;
-    return $name =~ m/^[A-Za-z0-9][A-Za-z0-9._-]*$/ ? 1 : 0;
+    return $name =~ m/^[[:alnum:]][\w.-]*$/ ? 1 : 0;
 }
 }
 
 
 # The same database the users are in, since a secret belongs to one.
 # The same database the users are in, since a secret belongs to one.

+ 1 - 1
t/Trog-DataSource-DirIndex.t

@@ -120,7 +120,7 @@ subtest 'ids are stable and opaque' => sub {
 
 
     # Pagination hands out page 2 of a list keyed on these, and a filename is
     # Pagination hands out page 2 of a list keyed on these, and a filename is
     # not a thing to put in a URL.
     # not a thing to put in a URL.
-    ok( $one{'report.pdf'} =~ m/^dirindex-[0-9a-f]{32}$/, 'and it is opaque rather than the name' );
+    ok( $one{'report.pdf'} =~ m/^dirindex-[\da-f]{32}$/, 'and it is opaque rather than the name' );
     isnt( $one{'report.pdf'}, $one{'notes.txt'}, 'different entries get different ids' );
     isnt( $one{'report.pdf'}, $one{'notes.txt'}, 'different entries get different ids' );
 };
 };
 
 

+ 1 - 1
t/Trog-Routes-HTML-series-lifecycle.t

@@ -422,7 +422,7 @@ subtest 'registering the first admin bootstraps the install' => sub {
     is( $code, 200, 'login() rendered' ) or diag($err);
     is( $code, 200, 'login() rendered' ) or diag($err);
 
 
     my %h = @{ $headers || [] };
     my %h = @{ $headers || [] };
-    like( $h{'Set-Cookie'} // '', qr/^tcmslogin=[0-9a-f-]{36};/, 'a session cookie came back' );
+    like( $h{'Set-Cookie'} // '', qr/^tcmslogin=[\da-f-]{36};/, 'a session cookie came back' );
 
 
     ok( -f 'config/has_users', 'registration is now closed' );
     ok( -f 'config/has_users', 'registration is now closed' );
     is_deeply( Trog::Auth::acls4user($ADMIN_USER), ['admin'], 'the new user is an admin' );
     is_deeply( Trog::Auth::acls4user($ADMIN_USER), ['admin'], 'the new user is an admin' );

+ 23 - 0
t/Trog-Routes-JSON.t

@@ -0,0 +1,23 @@
+#!/usr/bin/env perl
+use 5.040;
+use re '/aasx';
+use warnings FATAL => 'all';
+
+use Test::More;
+
+use FindBin::libs;
+
+require_ok('Trog::Routes::JSON') or BAIL_OUT("Can't find SUT");
+
+subtest 'the change request token is a uuid, and nothing else' => sub {
+    no warnings 'once';
+    my $valid = $Trog::Routes::JSON::routes{'/api/auth_change_request/(.*)'}{parameters}{token};
+
+    ok( $valid->('5bed1745-4201-40dd-b5dd-65acc0316604'), 'a uuid is a token' );
+    ok( !$valid->(q{}),                                   'nothing is not' );
+    ok( !$valid->('x;drop table user'),                   'and neither is text with a hex digit in it' );
+    ok( !$valid->("5bed1745-4201\n"),                     'nor a token with something after it' );
+    ok( !$valid->('a|b'),                                 'nor a pipe, which the class used to hold' );
+};
+
+done_testing();

+ 1 - 1
t/Trog-Vault.t

@@ -59,7 +59,7 @@ Trog::Auth::useradd( 'eve', 'Eve Evilson', 'letmein', ['public'], 'eve@example.c
 subtest 'the key is taken out of the environment' => sub {
 subtest 'the key is taken out of the environment' => sub {
     ok( Trog::Vault::has_key(),        'the key was read' );
     ok( Trog::Vault::has_key(),        'the key was read' );
     ok( !exists $ENV{TPSGI_VAULT_KEY}, 'and is gone from the environment, so nothing we fork inherits it' );
     ok( !exists $ENV{TPSGI_VAULT_KEY}, 'and is gone from the environment, so nothing we fork inherits it' );
-    like( Trog::Vault::key_id(), qr/^[0-9a-f]{16}$/, 'and fingerprints to something a row can name' );
+    like( Trog::Vault::key_id(), qr/^[\da-f]{16}$/, 'and fingerprints to something a row can name' );
 };
 };
 
 
 subtest 'what a secret looks like on disk' => sub {
 subtest 'what a secret looks like on disk' => sub {