{"record":{"id":"1387b9fe06c95c6a","repo":"transloadit/uppy","slug":"invalid-public-link-url","errorCode":null,"errorMessage":"invalid public link url","messagePattern":"invalid public link url","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@uppy/companion/src/server/provider/webdav/index.ts","lineNumber":59,"sourceCode":"  }: {\n    providerUserSession: WebdavUserSession\n  }): boolean {\n    return providerUserSession.webdavUrl != null\n  }\n\n  async getClient({\n    providerUserSession,\n  }: {\n    providerUserSession: WebdavUserSession\n  }): Promise<WebdavClient> {\n    const webdavUrl = providerUserSession?.webdavUrl\n    const { allowLocalUrls } = this\n    if (\n      webdavUrl == null ||\n      webdavUrl.length === 0 ||\n      !validateURL(webdavUrl, allowLocalUrls)\n    ) {\n      throw new Error('invalid public link url')\n    }\n\n    // Is this an ownCloud or Nextcloud public link URL? e.g. https://example.com/s/kFy9Lek5sm928xP\n    // they have specific urls that we can identify\n    // todo not sure if this is the right way to support nextcloud and other webdavs\n    if (/\\/s\\/([^/]+)/.test(webdavUrl)) {\n      const [baseURL, publicLinkToken] = webdavUrl.split('/s/')\n      if (!baseURL) {\n        throw new Error('invalid public link url')\n      }\n\n      return this.getClientHelper({\n        url: `${baseURL.replace('/index.php', '')}/public.php/webdav/`,\n        authType: AuthType.Password,\n        username: publicLinkToken!,\n        password: 'null',\n      })\n    }","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/transloadit/uppy/blob/5d4dedd02a1ac0ae022c75c54aca76558f88e256/packages/@uppy/companion/src/server/provider/webdav/index.ts#L41-L77","documentation":"WebDAV public-link mode (username omitted, only url passed) validates the URL before creating a client. It throws 'invalid public link url' when the url is null/empty or fails validateURL — which enforces protocol and (unless allowLocalUrls) rejects localhost/private addresses for SSRF protection.","triggerScenarios":"Calling provider.client()/getClient() in public-link mode with a missing, empty, or malformed url; using a http:// URL when only https is allowed; passing a localhost/127.0.0.1/private-IP URL while allowLocalUrls is false.","commonSituations":"Frontend not sending the url field when the user picks 'public link auth'; users pasting `http://` or typo'd URLs; testing against a local Nextcloud without allowLocalUrls: true in Companion's webdav provider options.","solutions":["Ensure the request includes a well-formed https:// WebDAV public link URL","If testing against a local server, enable allowLocalUrls in the Companion webdav provider options (never in production)","Validate/normalize the URL client-side before sending it to Companion"],"exampleFix":"// before\nconst opts = { provider: 'webdav' } // url missing\n\n// after\nconst opts = {\n  provider: 'webdav',\n  url: 'https://cloud.example.com/s/kFy9Lek5sm928xP',\n}\n// companion config for local testing:\n// { providerOptions: { webdav: { allowLocalUrls: true } } }","handlingStrategy":"validation","validationCode":"import validateURL from './validateURL.js'\nif (webdavUrl == null || webdavUrl.length === 0 || !validateURL(webdavUrl, allowLocalUrls)) {\n  // reject client-side before calling Companion\n  throw new Error('invalid public link url')\n}","typeGuard":"const isValidPublicLink = (u: string, allowLocal = false): boolean => {\n  try { const parsed = new URL(u); return parsed.protocol === 'https:' || (allowLocal && parsed.hostname === 'localhost') } catch { return false }\n}","tryCatchPattern":"try { await provider.client({ url }) } catch (e) { if (e instanceof Error && e.message === 'invalid public link url') { showUrlError() } throw e }","preventionTips":["Send full absolute https URLs for public links","Enable allowLocalUrls only in local dev","Validate the URL in the picker UI before submission"],"tags":["webdav","public-link","url-validation","ssrf","companion"],"backgroundTag":"invalid-url-validation","analyzedSha":"5d4dedd02a1ac0ae022c75c54aca76558f88e256","analyzedAt":"2026-08-28T12:18:41.267Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}