| 1 | # psl (Public Suffix List)
|
|---|
| 2 |
|
|---|
| 3 | [](https://github.com/lupomontero/psl/actions/workflows/node.js.yml)
|
|---|
| 4 |
|
|---|
| 5 | `psl` is a `JavaScript` domain name parser based on the
|
|---|
| 6 | [Public Suffix List](https://publicsuffix.org/).
|
|---|
| 7 |
|
|---|
| 8 | This implementation is tested against the
|
|---|
| 9 | [test data hosted by Mozilla](http://mxr.mozilla.org/mozilla-central/source/netwerk/test/unit/data/test_psl.txt?raw=1)
|
|---|
| 10 | and kindly provided by [Comodo](https://www.comodo.com/).
|
|---|
| 11 |
|
|---|
| 12 | Cross browser testing provided by
|
|---|
| 13 | [<img alt="BrowserStack" width="160" src="./browserstack-logo.svg" />](https://www.browserstack.com/)
|
|---|
| 14 |
|
|---|
| 15 | ## What is the Public Suffix List?
|
|---|
| 16 |
|
|---|
| 17 | The Public Suffix List is a cross-vendor initiative to provide an accurate list
|
|---|
| 18 | of domain name suffixes.
|
|---|
| 19 |
|
|---|
| 20 | The Public Suffix List is an initiative of the Mozilla Project, but is
|
|---|
| 21 | maintained as a community resource. It is available for use in any software,
|
|---|
| 22 | but was originally created to meet the needs of browser manufacturers.
|
|---|
| 23 |
|
|---|
| 24 | A "public suffix" is one under which Internet users can directly register names.
|
|---|
| 25 | Some examples of public suffixes are ".com", ".co.uk" and "pvt.k12.wy.us". The
|
|---|
| 26 | Public Suffix List is a list of all known public suffixes.
|
|---|
| 27 |
|
|---|
| 28 | Source: http://publicsuffix.org
|
|---|
| 29 |
|
|---|
| 30 | ## Installation
|
|---|
| 31 |
|
|---|
| 32 | This module is available both for Node.js and the browser. See below for more
|
|---|
| 33 | details.
|
|---|
| 34 |
|
|---|
| 35 | ### Node.js
|
|---|
| 36 |
|
|---|
| 37 | This module is tested on Node.js v8, v10, v12, v14, v16, v18, v20 and v22. See
|
|---|
| 38 | [`.github/workflows/node.js.yml`](.github/workflows/node.js.yml).
|
|---|
| 39 |
|
|---|
| 40 | ```sh
|
|---|
| 41 | npm install psl
|
|---|
| 42 | ```
|
|---|
| 43 |
|
|---|
| 44 | #### ESM
|
|---|
| 45 |
|
|---|
| 46 | From version `v1.13.0` you can now import `psl` as ESM.
|
|---|
| 47 |
|
|---|
| 48 | ```js
|
|---|
| 49 | import psl from 'psl';
|
|---|
| 50 | ```
|
|---|
| 51 |
|
|---|
| 52 | #### CommonJS
|
|---|
| 53 |
|
|---|
| 54 | If your project still uses CommonJS, you can continue importing the module like
|
|---|
| 55 | in previous versions.
|
|---|
| 56 |
|
|---|
| 57 | ```js
|
|---|
| 58 | const psl = require('psl');
|
|---|
| 59 | ```
|
|---|
| 60 |
|
|---|
| 61 | ### Browser
|
|---|
| 62 |
|
|---|
| 63 | #### Using a bundler
|
|---|
| 64 |
|
|---|
| 65 | If you are using a bundler to build your app, you should be able to `import`
|
|---|
| 66 | and/or `require` the module just like in Node.js.
|
|---|
| 67 |
|
|---|
| 68 | #### ESM (using a CDN)
|
|---|
| 69 |
|
|---|
| 70 | In modern browsers you can also import the ESM directly from a `CDN`. For
|
|---|
| 71 | example:
|
|---|
| 72 |
|
|---|
| 73 | ```js
|
|---|
| 74 | import psl from 'https://unpkg.com/psl@latest/dist/psl.mjs';
|
|---|
| 75 | ```
|
|---|
| 76 |
|
|---|
| 77 | #### UMD / CommonJS
|
|---|
| 78 |
|
|---|
| 79 | Finally, you can still download [`dist/psl.umd.cjs`](https://raw.githubusercontent.com/lupomontero/psl/main/dist/psl.umd.cjs)
|
|---|
| 80 | and include it in a script tag.
|
|---|
| 81 |
|
|---|
| 82 | ```html
|
|---|
| 83 | <script src="psl.umd.cjs"></script>
|
|---|
| 84 | ```
|
|---|
| 85 |
|
|---|
| 86 | This script is bundled and wrapped in a [umd](https://github.com/umdjs/umd)
|
|---|
| 87 | wrapper so you should be able to use it standalone or together with a module
|
|---|
| 88 | loader.
|
|---|
| 89 |
|
|---|
| 90 | The script is also available on most popular CDNs. For example:
|
|---|
| 91 |
|
|---|
| 92 | * https://unpkg.com/psl@latest/dist/psl.umd.cjs
|
|---|
| 93 |
|
|---|
| 94 | ## API
|
|---|
| 95 |
|
|---|
| 96 | ### `psl.parse(domain)`
|
|---|
| 97 |
|
|---|
| 98 | Parse domain based on Public Suffix List. Returns an `Object` with the following
|
|---|
| 99 | properties:
|
|---|
| 100 |
|
|---|
| 101 | * `tld`: Top level domain (this is the _public suffix_).
|
|---|
| 102 | * `sld`: Second level domain (the first private part of the domain name).
|
|---|
| 103 | * `domain`: The domain name is the `sld` + `tld`.
|
|---|
| 104 | * `subdomain`: Optional parts left of the domain.
|
|---|
| 105 |
|
|---|
| 106 | #### Examples
|
|---|
| 107 |
|
|---|
| 108 | Parse domain without subdomain:
|
|---|
| 109 |
|
|---|
| 110 | ```js
|
|---|
| 111 | import psl from 'psl';
|
|---|
| 112 |
|
|---|
| 113 | const parsed = psl.parse('google.com');
|
|---|
| 114 | console.log(parsed.tld); // 'com'
|
|---|
| 115 | console.log(parsed.sld); // 'google'
|
|---|
| 116 | console.log(parsed.domain); // 'google.com'
|
|---|
| 117 | console.log(parsed.subdomain); // null
|
|---|
| 118 | ```
|
|---|
| 119 |
|
|---|
| 120 | Parse domain with subdomain:
|
|---|
| 121 |
|
|---|
| 122 | ```js
|
|---|
| 123 | import psl from 'psl';
|
|---|
| 124 |
|
|---|
| 125 | const parsed = psl.parse('www.google.com');
|
|---|
| 126 | console.log(parsed.tld); // 'com'
|
|---|
| 127 | console.log(parsed.sld); // 'google'
|
|---|
| 128 | console.log(parsed.domain); // 'google.com'
|
|---|
| 129 | console.log(parsed.subdomain); // 'www'
|
|---|
| 130 | ```
|
|---|
| 131 |
|
|---|
| 132 | Parse domain with nested subdomains:
|
|---|
| 133 |
|
|---|
| 134 | ```js
|
|---|
| 135 | import psl from 'psl';
|
|---|
| 136 |
|
|---|
| 137 | const parsed = psl.parse('a.b.c.d.foo.com');
|
|---|
| 138 | console.log(parsed.tld); // 'com'
|
|---|
| 139 | console.log(parsed.sld); // 'foo'
|
|---|
| 140 | console.log(parsed.domain); // 'foo.com'
|
|---|
| 141 | console.log(parsed.subdomain); // 'a.b.c.d'
|
|---|
| 142 | ```
|
|---|
| 143 |
|
|---|
| 144 | ### `psl.get(domain)`
|
|---|
| 145 |
|
|---|
| 146 | Get domain name, `sld` + `tld`. Returns `null` if not valid.
|
|---|
| 147 |
|
|---|
| 148 | #### Examples
|
|---|
| 149 |
|
|---|
| 150 | ```js
|
|---|
| 151 | import psl from 'psl';
|
|---|
| 152 |
|
|---|
| 153 | // null input.
|
|---|
| 154 | psl.get(null); // null
|
|---|
| 155 |
|
|---|
| 156 | // Mixed case.
|
|---|
| 157 | psl.get('COM'); // null
|
|---|
| 158 | psl.get('example.COM'); // 'example.com'
|
|---|
| 159 | psl.get('WwW.example.COM'); // 'example.com'
|
|---|
| 160 |
|
|---|
| 161 | // Unlisted TLD.
|
|---|
| 162 | psl.get('example'); // null
|
|---|
| 163 | psl.get('example.example'); // 'example.example'
|
|---|
| 164 | psl.get('b.example.example'); // 'example.example'
|
|---|
| 165 | psl.get('a.b.example.example'); // 'example.example'
|
|---|
| 166 |
|
|---|
| 167 | // TLD with only 1 rule.
|
|---|
| 168 | psl.get('biz'); // null
|
|---|
| 169 | psl.get('domain.biz'); // 'domain.biz'
|
|---|
| 170 | psl.get('b.domain.biz'); // 'domain.biz'
|
|---|
| 171 | psl.get('a.b.domain.biz'); // 'domain.biz'
|
|---|
| 172 |
|
|---|
| 173 | // TLD with some 2-level rules.
|
|---|
| 174 | psl.get('uk.com'); // null);
|
|---|
| 175 | psl.get('example.uk.com'); // 'example.uk.com');
|
|---|
| 176 | psl.get('b.example.uk.com'); // 'example.uk.com');
|
|---|
| 177 |
|
|---|
| 178 | // More complex TLD.
|
|---|
| 179 | psl.get('c.kobe.jp'); // null
|
|---|
| 180 | psl.get('b.c.kobe.jp'); // 'b.c.kobe.jp'
|
|---|
| 181 | psl.get('a.b.c.kobe.jp'); // 'b.c.kobe.jp'
|
|---|
| 182 | psl.get('city.kobe.jp'); // 'city.kobe.jp'
|
|---|
| 183 | psl.get('www.city.kobe.jp'); // 'city.kobe.jp'
|
|---|
| 184 |
|
|---|
| 185 | // IDN labels.
|
|---|
| 186 | psl.get('食狮.com.cn'); // '食狮.com.cn'
|
|---|
| 187 | psl.get('食狮.公司.cn'); // '食狮.公司.cn'
|
|---|
| 188 | psl.get('www.食狮.公司.cn'); // '食狮.公司.cn'
|
|---|
| 189 |
|
|---|
| 190 | // Same as above, but punycoded.
|
|---|
| 191 | psl.get('xn--85x722f.com.cn'); // 'xn--85x722f.com.cn'
|
|---|
| 192 | psl.get('xn--85x722f.xn--55qx5d.cn'); // 'xn--85x722f.xn--55qx5d.cn'
|
|---|
| 193 | psl.get('www.xn--85x722f.xn--55qx5d.cn'); // 'xn--85x722f.xn--55qx5d.cn'
|
|---|
| 194 | ```
|
|---|
| 195 |
|
|---|
| 196 | ### `psl.isValid(domain)`
|
|---|
| 197 |
|
|---|
| 198 | Check whether a domain has a valid Public Suffix. Returns a `Boolean` indicating
|
|---|
| 199 | whether the domain has a valid Public Suffix.
|
|---|
| 200 |
|
|---|
| 201 | #### Example
|
|---|
| 202 |
|
|---|
| 203 | ```js
|
|---|
| 204 | import psl from 'psl';
|
|---|
| 205 |
|
|---|
| 206 | psl.isValid('google.com'); // true
|
|---|
| 207 | psl.isValid('www.google.com'); // true
|
|---|
| 208 | psl.isValid('x.yz'); // false
|
|---|
| 209 | ```
|
|---|
| 210 |
|
|---|
| 211 | ## Testing and Building
|
|---|
| 212 |
|
|---|
| 213 | There are tests both for Node.js and the browser (using [Playwright](https://playwright.dev)
|
|---|
| 214 | and [BrowserStack](https://www.browserstack.com/)).
|
|---|
| 215 |
|
|---|
| 216 | ```sh
|
|---|
| 217 | # Run tests in node.
|
|---|
| 218 | npm test
|
|---|
| 219 | # Run tests in browserstack.
|
|---|
| 220 | npm run test:browserstack
|
|---|
| 221 |
|
|---|
| 222 | # Update rules from publicsuffix.org
|
|---|
| 223 | npm run update-rules
|
|---|
| 224 |
|
|---|
| 225 | # Build ESM, CJS and UMD and create dist files
|
|---|
| 226 | npm run build
|
|---|
| 227 | ```
|
|---|
| 228 |
|
|---|
| 229 | Feel free to fork if you see possible improvements!
|
|---|
| 230 |
|
|---|
| 231 | ## Acknowledgements
|
|---|
| 232 |
|
|---|
| 233 | * Mozilla Foundation's [Public Suffix List](https://publicsuffix.org/)
|
|---|
| 234 | * Thanks to Rob Stradling of [Comodo](https://www.comodo.com/) for providing
|
|---|
| 235 | test data.
|
|---|
| 236 | * Inspired by [weppos/publicsuffix-ruby](https://github.com/weppos/publicsuffix-ruby)
|
|---|
| 237 |
|
|---|
| 238 | ## License
|
|---|
| 239 |
|
|---|
| 240 | The MIT License (MIT)
|
|---|
| 241 |
|
|---|
| 242 | Copyright (c) 2014-2024 Lupo Montero <lupomontero@gmail.com>
|
|---|
| 243 |
|
|---|
| 244 | Permission is hereby granted, free of charge, to any person obtaining a copy
|
|---|
| 245 | of this software and associated documentation files (the "Software"), to deal
|
|---|
| 246 | in the Software without restriction, including without limitation the rights
|
|---|
| 247 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|---|
| 248 | copies of the Software, and to permit persons to whom the Software is
|
|---|
| 249 | furnished to do so, subject to the following conditions:
|
|---|
| 250 |
|
|---|
| 251 | The above copyright notice and this permission notice shall be included in
|
|---|
| 252 | all copies or substantial portions of the Software.
|
|---|
| 253 |
|
|---|
| 254 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|---|
| 255 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|---|
| 256 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|---|
| 257 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|---|
| 258 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|---|
| 259 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|---|
| 260 | THE SOFTWARE.
|
|---|