fhirsmith 0.18.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/ACCESSIBILITY.md +119 -0
  2. package/CHANGELOG.md +190 -9
  3. package/CONFIGURATION.md +305 -0
  4. package/LICENSE +29 -0
  5. package/README.md +29 -122
  6. package/cluster.js +1220 -0
  7. package/cluster.md +707 -0
  8. package/config-template.json +77 -8
  9. package/extension-tracker/extension-tracker-template.html +5 -8
  10. package/extension-tracker/extension-tracker.js +0 -2
  11. package/folder/folder.js +7 -6
  12. package/ips/analysis.js +560 -0
  13. package/ips/ips-template.html +5 -8
  14. package/ips/ips.js +331 -33
  15. package/ips/jurisdictions.js +65 -0
  16. package/ips/nationality.js +120 -0
  17. package/ips/pipeline.js +131 -17
  18. package/ips/presenter.js +1 -1
  19. package/ips/profile-editor.js +283 -4
  20. package/ips/readme.md +170 -20
  21. package/ips/render.js +769 -109
  22. package/ips/scripts.js +46 -0
  23. package/ips/store.js +123 -11
  24. package/ips/structure.js +247 -0
  25. package/ips/terminology.js +1 -1
  26. package/library/body-limit.js +22 -0
  27. package/library/cache-generation.js +15 -0
  28. package/library/cgroup-memory.js +133 -1
  29. package/library/cluster-counts.js +121 -0
  30. package/library/cluster-events.js +153 -0
  31. package/library/cluster-metrics.js +193 -0
  32. package/library/cluster-status.js +468 -0
  33. package/library/config-reader.js +96 -0
  34. package/library/daily-report.js +100 -2
  35. package/library/html-server.js +27 -1
  36. package/library/i18nsupport.js +23 -1
  37. package/library/keep-alive.js +18 -0
  38. package/library/languages.js +17 -3
  39. package/library/logger.js +31 -0
  40. package/library/memory-guard.js +428 -0
  41. package/library/native-memory.js +52 -0
  42. package/library/npm-audit.js +51 -25
  43. package/library/openapi-doc.js +7 -5
  44. package/library/package-manager.js +8 -0
  45. package/library/role.js +197 -0
  46. package/library/server-credentials.js +169 -0
  47. package/library/sqlite-utilities.js +59 -0
  48. package/library/ssrf-guard.js +100 -0
  49. package/library/static-response-stats.js +16 -0
  50. package/library/version-utilities.js +50 -0
  51. package/library/worker-count.js +192 -0
  52. package/nginx.md +30 -11
  53. package/npmprojector/indexer.js +18 -10
  54. package/package.json +11 -8
  55. package/packages/feed-check-page.js +117 -0
  56. package/packages/feed-check.js +279 -0
  57. package/packages/npm-check.js +57 -3
  58. package/packages/npm-signing.js +219 -0
  59. package/packages/openapi.yaml +207 -1
  60. package/packages/package-crawler.js +322 -41
  61. package/packages/packages-template.html +6 -8
  62. package/packages/packages.js +604 -57
  63. package/packages/readme.md +113 -1
  64. package/publisher/publisher-template.html +2 -5
  65. package/publisher/publisher.js +121 -110
  66. package/publisher/readme.md +75 -0
  67. package/registry/api.js +179 -1
  68. package/registry/crawler.js +86 -108
  69. package/registry/documentation/api.md +68 -2
  70. package/registry/documentation/configuration.md +34 -0
  71. package/registry/documentation/internals.md +4 -1
  72. package/registry/documentation/readme.md +3 -2
  73. package/registry/model.js +8 -0
  74. package/registry/openapi.yaml +84 -0
  75. package/registry/registry-template.html +5 -8
  76. package/registry/registry.js +58 -6
  77. package/root-bare-template.html +7 -4
  78. package/root-template.html +5 -8
  79. package/server.js +398 -198
  80. package/shl/shl.js +38 -37
  81. package/static/a11y/a11y-check.js +832 -0
  82. package/static/a11y/axe.min.js +12 -0
  83. package/static/assets/css/bootstrap-fhir.css +15 -15
  84. package/static/assets/images/flags/au.svg +18 -0
  85. package/static/assets/images/flags/unknown.svg +11 -0
  86. package/static/assets/images/flags/us.svg +63 -0
  87. package/static/assets/images/icon_audit.png +0 -0
  88. package/static/assets/images/icon_clinical.png +0 -0
  89. package/static/assets/images/icon_document.png +0 -0
  90. package/static/assets/images/icon_external.png +0 -0
  91. package/static/assets/images/icon_medication.png +0 -0
  92. package/static/assets/images/icon_organization.png +0 -0
  93. package/static/assets/images/icon_other.png +0 -0
  94. package/static/assets/images/icon_person.png +0 -0
  95. package/static/assets/images/icon_section.png +0 -0
  96. package/stats-db.js +45 -0
  97. package/stats.js +197 -10
  98. package/testing/testing-template.html +5 -8
  99. package/token/nginx_passport_setup.md +1 -1
  100. package/token/security_guide.md +1 -1
  101. package/token/token-template.html +2 -5
  102. package/token/token.js +40 -36
  103. package/translations/Messages.properties +19 -0
  104. package/tx/README.md +158 -4
  105. package/tx/closure/closure-store.js +126 -3
  106. package/tx/cm/cm-database.js +69 -57
  107. package/tx/cm/cm-package.js +50 -16
  108. package/tx/cs/cs-api.js +284 -19
  109. package/tx/cs/cs-api.md +41 -4
  110. package/tx/cs/cs-areacode.js +1 -1
  111. package/tx/cs/cs-country.js +17 -5
  112. package/tx/cs/cs-cpt.js +33 -16
  113. package/tx/cs/cs-cs.js +199 -24
  114. package/tx/cs/cs-currency.js +1 -1
  115. package/tx/cs/cs-icd11.js +21 -13
  116. package/tx/cs/cs-icf.js +22 -3
  117. package/tx/cs/cs-lang.js +2 -2
  118. package/tx/cs/cs-loinc.js +193 -67
  119. package/tx/cs/cs-mimetypes.js +12 -2
  120. package/tx/cs/cs-ndc.js +40 -25
  121. package/tx/cs/cs-omop.js +54 -29
  122. package/tx/cs/cs-rxnorm.js +300 -196
  123. package/tx/cs/cs-snomed.js +861 -95
  124. package/tx/cs/cs-ucum.js +14 -4
  125. package/tx/cs/cs-unii.js +18 -14
  126. package/tx/cs/cs-virtual.js +210 -0
  127. package/tx/ecosystem-resolver.js +413 -0
  128. package/tx/html/cluster-dash-metrics.liquid +26 -0
  129. package/tx/html/cluster-home-metrics.liquid +31 -0
  130. package/tx/html/cluster-metrics-script.liquid +251 -0
  131. package/tx/html/codesystem-operations.liquid +3 -3
  132. package/tx/html/ecl-panel.liquid +1 -1
  133. package/tx/html/external-metrics.liquid +17 -0
  134. package/tx/html/operations-form.liquid +17 -17
  135. package/tx/html/search-form.liquid +11 -11
  136. package/tx/html/status-line.liquid +58 -0
  137. package/tx/html/status-panel.liquid +140 -0
  138. package/tx/html/status-tasks.liquid +25 -0
  139. package/tx/html/tx-template.html +2 -5
  140. package/tx/importers/atc-to-fhir.js +17 -16
  141. package/tx/importers/export-icd11-codesystem.js +3 -2
  142. package/tx/importers/import-icd11.js +4 -3
  143. package/tx/importers/import-loinc.module.js +118 -56
  144. package/tx/importers/import-ndc.module.js +56 -44
  145. package/tx/importers/import-rxnorm.module.js +118 -79
  146. package/tx/importers/import-sct.module.js +125 -61
  147. package/tx/importers/import-unii.module.js +37 -36
  148. package/tx/importers/subset-loinc.module.js +49 -48
  149. package/tx/importers/subset-rxnorm.module.js +29 -28
  150. package/tx/importers/terminal.js +18 -0
  151. package/tx/importers/tx-import-base.js +13 -12
  152. package/tx/importers/tx-import-settings.js +5 -4
  153. package/tx/importers/tx-import.js +53 -52
  154. package/tx/library/canonical-resource.js +49 -0
  155. package/tx/library/codesystem.js +94 -5
  156. package/tx/library/designations.js +45 -2
  157. package/tx/library/extensions.js +13 -7
  158. package/tx/library/implicit-valuesets.js +66 -0
  159. package/tx/library/renderer.js +77 -21
  160. package/tx/library/sqlite-utilities.js +2 -0
  161. package/tx/library/tho-precedence.js +40 -0
  162. package/tx/library/ucum-parsers.js +11 -1
  163. package/tx/library/wait-for-cache.js +43 -0
  164. package/tx/library.js +321 -22
  165. package/tx/openapi-operations.js +6 -4
  166. package/tx/operation-context.js +460 -99
  167. package/tx/params.js +25 -0
  168. package/tx/problems.js +3 -5
  169. package/tx/provider.js +314 -67
  170. package/tx/resource-cache-sqlite.js +1050 -0
  171. package/tx/sct/documentation.md +45 -21
  172. package/tx/sct/ecl.js +81 -17
  173. package/tx/sct/editions.js +39 -0
  174. package/tx/sct/expressions.js +10 -1
  175. package/tx/sct/load-config.js +107 -0
  176. package/tx/sct/mmap.js +109 -0
  177. package/tx/sct/structures.js +175 -15
  178. package/tx/sct/text-index.js +5 -1
  179. package/tx/syndication/syndication-feed.js +2 -2
  180. package/tx/tx-html.js +44 -8
  181. package/tx/tx.fhir.org.yml +8 -8
  182. package/tx/tx.js +282 -80
  183. package/tx/usage-tracker.js +39 -11
  184. package/tx/vs/vs-api.js +19 -0
  185. package/tx/vs/vs-database.js +386 -62
  186. package/tx/vs/vs-package.js +44 -17
  187. package/tx/vs/vs-vsac.js +649 -101
  188. package/tx/workers/batch-validate.js +37 -10
  189. package/tx/workers/batch.js +6 -1
  190. package/tx/workers/cache-control.js +15 -4
  191. package/tx/workers/closure.js +156 -13
  192. package/tx/workers/compare.js +8 -5
  193. package/tx/workers/expand.js +1173 -289
  194. package/tx/workers/metadata.js +35 -0
  195. package/tx/workers/search.js +53 -1
  196. package/tx/workers/supplement-filters.js +145 -0
  197. package/tx/workers/translate.js +169 -4
  198. package/tx/workers/validate.js +223 -119
  199. package/tx/workers/worker.js +123 -31
  200. package/tx/xversion/xv-valueset.js +7 -4
  201. package/utilities/submit-test-report.js +3 -1
  202. package/utilities/tx-stress-test.js +482 -0
  203. package/vcl/readme.md +37 -0
  204. package/vcl/vcl-parser.js +15 -0
  205. package/xig/readme.md +52 -0
  206. package/xig/xig-template.html +5 -8
  207. package/xig/xig.js +238 -422
  208. package/changelog-template.md +0 -22
  209. package/registry/test-runner.js +0 -237
  210. package/release.sh +0 -80
  211. package/test-scripts/repro-re2-wasm-leak.js +0 -34
  212. package/tsconfig.eslint.json +0 -9
  213. package/tx/html/dash-metrics.liquid +0 -147
  214. package/tx/html/home-metrics.liquid +0 -225
@@ -34,7 +34,10 @@ In summary:
34
34
  | `GET /packages/{id}/{version}` | Download a package tarball; or, when the request's `Accept` prefers JSON (ranks it above `*/*`), npm's document for that version. A `FHIR-Package-Download` header always gets the tarball (the HL7 Java tools send it) |
35
35
  | `GET /packages/updates` | Versions published since a date |
36
36
  | `GET /packages/broken` | Package versions whose dependencies this registry doesn't hold |
37
+ | `GET /packages/-/npm/v1/keys` | The key this registry signs package versions with (see [Signatures](#signatures)) |
38
+ | `GET /packages/-/fhir/v1/signatures/{name}@{version}` | The publisher's signature of a version, as the crawler fetched it (see [Publishers' signatures](#publishers-signatures)) |
37
39
  | `GET /packages/npm-check` | Check any registry against the npm registry protocol (a form, in a browser) |
40
+ | `GET /packages/feed-check` | Check a package feed the way the crawler reads it, storing nothing (a form, in a browser); the report is at `/packages/feed-check/{run}` |
38
41
 
39
42
  Most endpoints return an HTML page instead of JSON when the request's `Accept`
40
43
  header contains `text/html`. The JSON is the contract; the HTML is for people.
@@ -92,9 +95,16 @@ In the server config, under `modules.packages`:
92
95
  | `crawlToken` | Shared secret for `POST /packages/crawl`. Omit it to disable that endpoint; the scheduled crawler is unaffected. |
93
96
  | `crawler.enabled` | Run the crawler at startup and on the schedule. |
94
97
  | `crawler.schedule` | Cron expression for crawls. |
98
+ | `crawler.signatures` | Publishers' package signatures: `ignore`, `check` (the default) or `require`. See [Publishers' signatures](#publishers-signatures). |
95
99
  | `masterUrl` | The list of feeds to crawl. Defaults to `https://fhir.github.io/ig-registry/package-feeds.json`. |
96
100
  | `localFeedDirs` | Optional. Directories feeds may be read from as local files (for testing). |
97
101
  | `allowPrivateAddresses` | Optional, for testing only. Lets the crawler and `/npm-check` fetch from private and loopback addresses, which they otherwise refuse (SSRF protection). |
102
+ | `npmSigning.enabled` | Sign package versions (see [Signatures](#signatures)). On unless set to `false`. |
103
+ | `npmSigning.keyFile` | The signing key, an EC P-256 private key in PEM. A relative path is resolved against the database's folder. Default `npm-signing-key.pem`; made (readable only by the server's user) if it isn't there. |
104
+ | `feedCheck.enabled` | `/packages/feed-check`. On unless set to `false`. |
105
+ | `feedCheck.maxItems` | Items checked per run; the rest of the feed isn't read. Default 50. |
106
+ | `feedCheck.maxTotalMB` | Downloaded per run; items after that are listed but their packages aren't fetched. Default 500. |
107
+ | `feedCheck.keepMinutes` | How long a run's report is kept. Default 60. |
98
108
  | `npmCheck.enabled` | `/packages/npm-check`. On unless set to `false`. |
99
109
  | `npmCheck.maxConcurrentRuns` | Runs at once, across all users; more get a 503. Default 2. |
100
110
  | `npmCheck.runsPerWindow` | Runs per client address per 10 minutes; more get a 429. Default 10. |
@@ -122,6 +132,56 @@ are used three ways:
122
132
  `/packages/npm-check` also runs them against registry.npmjs.org once a day, and says on
123
133
  the page if any fail there.
124
134
 
135
+ ## Signatures
136
+
137
+ The server signs every package version it holds, the way the npm registry does, so
138
+ `npm audit signatures` can check packages installed from it. A signature says that this
139
+ registry served this tarball: it is the registry's statement, not the publisher's, so
140
+ it's made for every version, wherever it was crawled from. Each version in a package
141
+ document has `dist.integrity` (the sha512 of the tarball) and `dist.signatures`, the
142
+ ECDSA P-256 signature of `{id}@{version}:{integrity}`; `/packages/-/npm/v1/keys` lists
143
+ the public key.
144
+
145
+ * The key is in `npmSigning.keyFile`, next to the database unless set otherwise. If
146
+ there's no key there, one is made at startup. It is no better protected than the
147
+ database and the configuration: anyone who can change those can change what is
148
+ served anyway.
149
+ * A version is signed when it's stored. Versions stored before this (or signed with a
150
+ different key) are signed in the background at startup, a row at a time. Until every
151
+ version is signed, `/-/npm/v1/keys` is a 404, as if the server didn't sign: npm reports
152
+ an unsigned version from a registry with keys as an error. `/packages/status` shows the
153
+ progress (`npmSigning`). If some are left, it tries again after the next crawl.
154
+ * To change the key, replace the file and restart: everything is signed again with the
155
+ new key, and the keys endpoint is a 404 until that's done.
156
+ * With `npmSigning.enabled` false, versions still get `dist.integrity`, but there are no
157
+ signatures and no keys.
158
+
159
+ The signatures and integrity are in the `PackageDist` table, not in `PackageVersions`:
160
+ reading a column stored after the tarball means reading the tarball.
161
+
162
+ ## Checking a feed
163
+
164
+ `/packages/feed-check` takes a feed's URL and reads it the way the crawler does, without storing
165
+ anything: no packages, signatures or visited pages go into the database, and nothing is written
166
+ to the mirror. For each item it reports the package (downloaded, unpacked with the crawler's
167
+ limits and checked as the crawler checks it before storing: id, semver version, canonical, no
168
+ install scripts, no JavaScript unless it's a template, not notForPublication), whether the
169
+ registry's `package-restrictions` allow it from this feed, its signature (`confirmed`,
170
+ `rejected`, `absent`, or `not checked` when the feed isn't in `package-feeds.json` or lists no
171
+ keys), and whether this registry already holds the version, and if so whether it is the same
172
+ bytes. A feed-level problem (it can't be fetched, isn't RSS, or the server rate limits) is
173
+ reported instead. Next pages are followed up to `feedCheck.maxItems` items.
174
+
175
+ The signing keys can be given with the check, as JSON: the feed's `signing` block as it would be
176
+ in `package-feeds.json` (or the feed's whole entry). Signatures are then checked against those
177
+ keys instead of the registry's, so a publisher can test a feed and its keys before they are
178
+ registered.
179
+
180
+ A check runs in the background: the form redirects to `/packages/feed-check/{run}`, which reloads
181
+ every few seconds until the check is done. Every request goes through the crawler's SSRF guard,
182
+ and only http(s) URLs are fetched, so a feed can't make the server read local files. Only one
183
+ check runs at a time, across all users; a request while one is running gets a 503.
184
+
125
185
  ## Crawling
126
186
 
127
187
  Each crawl reads the master feed list, then each RSS feed in it (Simplifier last).
@@ -148,10 +208,62 @@ stores it.
148
208
 
149
209
  `/packages/log` shows what the last crawl did, feed by feed and item by item.
150
210
 
211
+ ### Publishers' signatures
212
+
213
+ A publisher can sign its packages: the IG Publisher does, when its `fhir-settings.json` has a
214
+ `packageSigningKey` (see `org.hl7.fhir.utilities.npm.NpmPackageSigner` in core). Each signed
215
+ package has a partner file, `{package}.tgz.sigstore.json`, and the feed item links it in a
216
+ `fhir:signature` element. The file is a Sigstore bundle: an ECDSA P-256 signature of a statement
217
+ naming the package (`pkg:npm/{id}@{version}`) and the sha512 of the `.tgz`.
218
+
219
+ Which keys may sign a feed's packages is in the master list, `package-feeds.json`, on the feed:
220
+
221
+ ```json
222
+ "signing": {
223
+ "required": false,
224
+ "keys": [{ "keyid": "SHA256:...", "key": "{base64 public key}", "expires": null }]
225
+ }
226
+ ```
227
+
228
+ `crawler.signatures` says what the crawler does with them, for feeds that list keys:
229
+
230
+ * `ignore`: nothing.
231
+ * `check` (the default): each package's signature is fetched and checked. One that fails, or
232
+ can't be fetched, is logged (in the server log and `/packages/log`), and the package is stored
233
+ anyway, unless the feed has `"required": true`, when it is refused.
234
+ * `require`: as `check`, but an unsigned package, or one whose signature fails, is refused,
235
+ and feeds that list no keys (Simplifier, for one) aren't read at all - the crawl log says so.
236
+
237
+ With `ignore` or `check`, feeds that list no keys are read as before, and their packages aren't
238
+ checked. A key that had expired when a
239
+ package was published doesn't count. A package that was stored before it was signed (or before
240
+ the server checked) has its signature checked against the bytes it holds, once, the next time
241
+ the crawler sees it in a feed; it is never refused, since it is already held, but a failure is
242
+ logged and recorded.
243
+
244
+ The signature file is kept in the `PackageSignatures` table as it was when it was checked, valid
245
+ or not, with the outcome, the key that signed it and when it was checked. The HTML lists of
246
+ packages (search, a package's versions, updates) show each version's signature as `confirmed`,
247
+ `rejected` or `absent` (unsigned, or not checked); hover for the key, or the reason.
248
+
249
+ In the package document, each version with a signature has `dist.fhir-signature`: `url` (the
250
+ signature file, served at `/packages/-/fhir/v1/signatures/{name}@{version}` exactly as it was
251
+ fetched), `status` (`confirmed` or `rejected`) and, when confirmed, the `keyid`. A client can
252
+ check the signature itself, against the keys in `package-feeds.json`.
253
+
254
+ It is deliberately not `dist.attestations`. npm (`npm audit signatures`) verifies attestations
255
+ as Sigstore attestations: it wants the signing key listed among the registry's own keys
256
+ (`/-/npm/v1/keys`), and a transparency log (Rekor) entry in the bundle. Publishers' bundles have
257
+ no log entry, and listing a publisher's key with the registry's would let that publisher forge
258
+ the registry's signatures, since npm accepts a registry signature from any listed key. So every
259
+ signed package would fail `npm audit signatures`.
260
+
151
261
  ## Storage
152
262
 
153
263
  Everything is in the SQLite database. `PackageVersions` holds each version's
154
- metadata and the tarball itself. `Packages` holds one row per package id, with its
264
+ metadata and the tarball itself. `PackageDist` holds each version's `dist.integrity`
265
+ and npm signature. `PackageSignatures` holds the publisher's signature of each version, as
266
+ the crawler checked it. `Packages` holds one row per package id, with its
155
267
  current version and download count. `PackageDependencies` (stored as `id@version`),
156
268
  `PackageFHIRVersions` and `PackageURLs` (the canonical URLs of the resources in each
157
269
  version) support search.
@@ -85,8 +85,6 @@
85
85
  <div id="stripe"> </div>
86
86
  <div class="container"> <!-- container -->
87
87
  <div style="background-color: #ad1f2f; padding: 6px; color: white;"> <!-- container -->
88
- <a href="http://www.hl7.org/fhir" style="color: gold" title="Fast Healthcare Interoperability Resources - Home Page"><img border="0" src="/icon-fhir-16.png" style="vertical-align: text-bottom"/> <b>FHIR</b></a> &copy; HL7.org &nbsp;|&nbsp;
89
- <a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] &nbsp;|&nbsp;
90
88
  <a href="/" style="color: gold"> Server Home</a> &nbsp;|&nbsp;
91
89
  <a href="/publisher" style="color: gold">Publisher Home</a> &nbsp;|&nbsp;
92
90
  <a href="/publisher/tasks" style="color: gold">Tasks</a> &nbsp;|&nbsp;
@@ -106,7 +104,7 @@
106
104
  <div class="inner-wrapper">
107
105
  <div class="col-9">
108
106
 
109
- <h2><img border="0" src="../FHIRsmith32.png" style="vertical-align: text-bottom"/> [%title%] </h2>
107
+ <h2><a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" title="FHIRsmith [%ver%]"><img border="0" src="/FHIRsmith32.png" alt="FHIRsmith Logo" style="vertical-align: text-bottom"/></a> [%title%] </h2>
110
108
 
111
109
  [%content%]
112
110
 
@@ -121,8 +119,7 @@
121
119
  <div class="container"> <!-- container -->
122
120
  <div class="inner-wrapper">
123
121
  <p>
124
- <a href="http://www.hl7.org/fhir" style="color: gold" title="Fast Healthcare Interoperability Resources - Home Page"><img border="0" src="/icon-fhir-16.png" style="vertical-align: text-bottom"/> <b>FHIR</b></a> &copy; HL7.org 2011+. &nbsp;|&nbsp;
125
- <a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] &copy; HealthIntersections.com.au 2023+ &nbsp;|&nbsp;
122
+ <a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" alt="FHIRsmith Logo" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] &copy; HealthIntersections.com.au 2023+ &nbsp;|&nbsp;
126
123
  ([%ms%] ms)
127
124
  [%sponsorMessage%]
128
125
  </p>