{"record":{"id":"edfe3b61396c5578","repo":"Crosstalk-Solutions/project-nomad","slug":"could-not-fetch-directory-listing-from-the-provide","errorCode":null,"errorMessage":"Could not fetch directory listing from the provided URL","messagePattern":"Could not fetch directory listing from the provided URL","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"admin/app/controllers/zim_controller.ts","lineNumber":224,"sourceCode":"      await this.zimService.removeCustomLibrary(payload.params.id)\n      return { message: 'Custom library removed' }\n    } catch (error) {\n      if (error.message === 'Custom library not found') {\n        return response.status(404).send({ message: error.message })\n      }\n      throw error\n    }\n  }\n\n  async browseLibrary({ request, response }: HttpContext) {\n    const payload = await request.validateUsing(browseLibraryValidator)\n    try {\n      return await this.zimService.browseLibraryUrl(payload.url)\n    } catch (error) {\n      if (error.message?.includes('loopback or link-local')) {\n        return response.status(400).send({ message: error.message })\n      }\n      return response.status(502).send({\n        message: 'Could not fetch directory listing from the provided URL',\n      })\n    }\n  }\n}\n","sourceCodeStart":206,"sourceCodeEnd":230,"githubUrl":"https://github.com/Crosstalk-Solutions/project-nomad/blob/0bd1c6f4f9888d577fe232de06ac144bb8337131/admin/app/controllers/zim_controller.ts#L206-L230","documentation":"Catch-all 502 from browseLibrary when fetching the directory listing from the provided library URL fails for a non-SSRF reason: host unreachable, connection refused, timeout, TLS error, or the response is not a parseable directory index. The proxy-style 502 signals the failure belongs to the upstream URL, not this server.","triggerScenarios":"Calling browse-library with a URL that is down/unreachable, serves a non-HTML/non-directory-index page, requires auth, or has a bad certificate; any fetch error not matching the loopback/link-local message.","commonSituations":"Library server stopped or firewall blocking the admin server's egress; URL points to an HTML page or a native OPDS feed instead of an Apache/nginx-style directory listing; self-signed cert causing TLS rejection; slow mirror timing out.","solutions":["curl the URL from the admin server host to confirm it is reachable and returns a plain directory listing (links ending in .zim)","Fix scheme/port/hostname typos and ensure the server serves an autoindex-style listing the parser understands","If TLS fails, install/point to a valid certificate or use http within a trusted network","Increase the fetch timeout in zimService.browseLibraryUrl for slow mirrors"],"exampleFix":"// before\nawait zimController.browseLibrary({ url: 'https://mirror.example.org/zims/' }) // 502\n// after\n# verify from the same host first:\n# curl -sSL https://mirror.example.org/zims/ | head\nawait zimController.browseLibrary({ url: 'https://mirror.example.org/en/zims/' }) // valid listing path","handlingStrategy":"fallback","validationCode":"// pre-flight the URL from the same host/network before calling browse\nconst ok = await fetch(url, { method: 'HEAD', signal: AbortSignal.timeout(5000) })\nif (!ok.ok) throw new Error(`Library URL unreachable (${ok.status})`)","typeGuard":null,"tryCatchPattern":"try {\n  return await zimService.browseLibraryUrl(url)\n} catch (e) {\n  if (e.message?.includes('loopback or link-local')) throw e // SSRF — do not retry\n  // fallback: try a mirror or cached listing\n  return cachedListing(url) ?? []\n}","preventionTips":["curl/HEAD-check library URLs before indexing","Monitor library mirrors for availability","Prefer stable mirrors and set sane fetch timeouts"],"tags":["zim","network","upstream","http-502","directory-listing"],"backgroundTag":"upstream-fetch-failed","analyzedSha":"0bd1c6f4f9888d577fe232de06ac144bb8337131","analyzedAt":"2026-08-27T05:34:15.424Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}