toURL() leaves `#` unencoded in text overlays and public IDs

Open Beginner friendly
#608 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
82/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
javascript, nodejs, typescript
Domain
api

Research direction

The URL is assembled in src/assets/CloudinaryFile.ts, in createCloudinaryURL(), where encodeURI is followed by replace calls for ? and =. Read that chain and compare it with the createCloudinaryLegacyURL output the issue cites, which encodes # as %23. Reproduce with the two snippets in the issue and check that toURL() yields Item%20%231 and a%23b. Done when both URLs contain %23 and the existing URL-generation tests still pass.

Written by the indexing model from the issue text.

Description

toURL() encodes URL segments with encodeURI() and replaces ? and =, leaving # unencoded so any # in a text overlay or public ID starts a URL fragment and truncates the request path.

Reproduction (npm install @cloudinary/[email protected], then run with node --input-type=module):

import { CloudinaryImage } from "@cloudinary/url-gen";
import { source } from "@cloudinary/url-gen/actions/overlay";
import { text } from "@cloudinary/url-gen/qualifiers/source";
import { TextStyle } from "@cloudinary/url-gen/qualifiers/textStyle";

console.log(
  new CloudinaryImage("sample", { cloudName: "demo" }, { analytics: false })
    .overlay(source(text("Item #1", new TextStyle("Arial", 20))))
    .toURL()
);

console.log(
  new CloudinaryImage("a#b", { cloudName: "demo" }, { analytics: false }).toURL()
);

Actual output:

https://res.cloudinary.com/demo/image/upload/l_text:Arial_20:Item%20#1/fl_layer_apply/sample
https://res.cloudinary.com/demo/image/upload/a#b

Browsers and fetch() strip #1/fl_layer_apply/sample as the fragment and request /demo/image/upload/l_text:Arial_20:Item%20, which returns HTTP 404 (x-cld-error: Resource not found - l_text:Arial_20:Item). Likewise, /demo/image/upload/a#b requests /demo/image/upload/a.

Expected output:

https://res.cloudinary.com/demo/image/upload/l_text:Arial_20:Item%20%231/fl_layer_apply/sample
https://res.cloudinary.com/demo/image/upload/a%23b

Per the text layer documentation, SDK helper methods escape special URL characters automatically. createCloudinaryLegacyURL in @cloudinary/url-gen encodes # to %23 in both cases, and https://res.cloudinary.com/demo/image/upload/l_text:Arial_20:Item%20%231/fl_layer_apply/sample returns HTTP 200.

In src/assets/CloudinaryFile.ts, createCloudinaryURL() runs encodeURI(...) followed by .replace(/\?/g, '%3F').replace(/=/g, '%3D'), without .replace(/#/g, '%23').

Versions tested: @cloudinary/[email protected] (npm latest, with @cloudinary/[email protected]) and master at 8f61b55, on Node.js v26.13.0.

Credit: https://ucsbplse.github.io/

Dominant language
HTML
Stars
56
Forks
11
PR merge metrics
No merged PRs in 30d

Getting set up

  • No Dockerfile or Docker Compose file
  • Has a pull request template
  • No contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from cloudinary/js-url-gen

All issues in cloudinary/js-url-gen

Similar issues

More Backend & API Design issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.