{"record":{"id":"67ddcb9109b81e16","repo":"jackwener/OpenCLI","slug":"unsupported-z-library-url-host-url-hostname","errorCode":null,"errorMessage":"Unsupported Z-Library URL host: ${url.hostname}","messagePattern":"Unsupported Z-Library URL host: (.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/zlibrary/utils.js","lineNumber":23,"sourceCode":"import { ArgumentError } from '@jackwener/opencli/errors';\n\nconst ZLIBRARY_DOMAIN = 'z-library.im';\nconst ZLIBRARY_ORIGIN = `https://${ZLIBRARY_DOMAIN}`;\nconst ZLIBRARY_ALLOWED_HOSTS = new Set([\n  ZLIBRARY_DOMAIN,\n  `www.${ZLIBRARY_DOMAIN}`,\n]);\n\nexport function normalizeZlibraryBookUrl(input) {\n  const raw = String(input || '').trim();\n  let url;\n  try {\n    url = new URL(raw);\n  } catch {\n    throw new ArgumentError('Z-Library book URL must be a valid http(s) URL', `Example: ${ZLIBRARY_ORIGIN}/book/...`);\n  }\n  if (!['http:', 'https:'].includes(url.protocol) || !ZLIBRARY_ALLOWED_HOSTS.has(url.hostname)) {\n    throw new ArgumentError(\n      `Unsupported Z-Library URL host: ${url.hostname}`,\n      `Pass a book URL under ${ZLIBRARY_DOMAIN}, for example ${ZLIBRARY_ORIGIN}/book/...`,\n    );\n  }\n  return url.toString();\n}\n\n/**\n * Build a Z-Library search URL.\n * Z-Library uses /s/<url-encoded-query> for search.\n */\nexport function buildSearchUrl(query) {\n  const normalized = String(query || '').trim();\n  if (!normalized) {\n    throw new ArgumentError('zlibrary search query cannot be empty');\n  }\n  return `${ZLIBRARY_ORIGIN}/s/${encodeURIComponent(normalized)}`;\n}","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/zlibrary/utils.js#L5-L41","documentation":"normalizeZlibraryBookUrl throws ArgumentError (code ARGUMENT, exit 2) when the URL parses but its protocol is not http(s) or its hostname is not in ZLIBRARY_ALLOWED_HOSTS. The library whitelists Z-Library hosts to avoid navigating to arbitrary or malicious domains.","triggerScenarios":"Passing an ftp:/file: URL, or a book URL on a mirror domain not in the allowed host list, e.g. https://some-mirror.example/book/123.","commonSituations":"Using a regional/mirror Z-Library domain that the adapter does not whitelist, single-mirror domains changing after Z-Library rotation, or copying a link from a search engine pointing at a different host.","solutions":["Use a book URL under the supported Z-Library domain (see the error hint for the allowed origin)","Re-find the book on the whitelisted domain via `zlibrary search`","Check the adapter's ZLIBRARY_ALLOWED_HOSTS to see which hosts are accepted","Update the library if Z-Library moved to a new official domain"],"exampleFix":"// before\nawait zlibraryInfo({ url: 'https://mirror-not-allowed.example/book/123' });\n// after\nawait zlibraryInfo({ url: 'https://z-library.example/book/123' });","handlingStrategy":"validation","validationCode":"const u = new URL(bookUrl);\nconst allowed = ['z-library.example', /* see ZLIBRARY_ALLOWED_HOSTS */];\nif (!allowed.includes(u.hostname)) throw new Error(`unsupported host: ${u.hostname}`);","typeGuard":"function isAllowedZlibraryUrl(v, allowedHosts) {\n  try { const u = new URL(v); return ['http:', 'https:'].includes(u.protocol) && allowedHosts.includes(u.hostname); }\n  catch { return false; }\n}","tryCatchPattern":"try {\n  await zlibraryInfo({ url: bookUrl });\n} catch (e) {\n  if (e?.code === 'ARGUMENT' && /Unsupported.*host/.test(e.message)) {\n    // re-find the book on a whitelisted domain\n  }\n  throw e;\n}","preventionTips":["Only use book links from the adapter's supported domain","Re-search on the whitelisted origin instead of pasting mirror links","Check the error hint for the accepted domain","Update the library when Z-Library rotates domains"],"tags":["argument-validation","url","allowlist"],"backgroundTag":"invalid-url-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}