Skip to content

Support configured charsets for static content#106

Open
vibhor-aggr wants to merge 2 commits into
koajs:masterfrom
vibhor-aggr:fix/configurable-static-charset
Open

Support configured charsets for static content#106
vibhor-aggr wants to merge 2 commits into
koajs:masterfrom
vibhor-aggr:fix/configurable-static-charset

Conversation

@vibhor-aggr

@vibhor-aggr vibhor-aggr commented Jun 10, 2026

Copy link
Copy Markdown

Closes #83.

This adds an optional charset setting for static file responses so applications that know their file encoding can return an explicit Content-Type charset instead of relying on Koa's default behavior.

What changed:

  • add options.charset support as either a string or a (file, type) function
  • preserve the existing MIME lookup fallback behavior
  • document the new option in the README
  • add regression coverage for string and function charset configuration

I kept this configurable rather than attempting automatic charset detection because the middleware does not have reliable encoding metadata for arbitrary static files.

Verification:

  • npm test
  • git diff --check

Summary by Sourcery

Add optional charset configuration for static file responses and document and test the new behavior.

New Features:

  • Allow configuring a static file response charset via an options.charset string or function.

Documentation:

  • Document the new options.charset setting and its behavior in the README.

Tests:

  • Add regression tests covering charset configuration as a fixed string and as a function.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@vibhor-aggr, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 12 minutes and 58 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2196ab99-cdb8-4969-b19e-36e8418e79a6

📥 Commits

Reviewing files that changed from the base of the PR and between 182f260 and 6566f89.

📒 Files selected for processing (3)
  • README.md
  • index.js
  • test/index.js

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai

sourcery-ai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds optional charset configuration for static file responses, allowing a fixed string or per-file function to append an explicit charset to the Content-Type while preserving existing MIME lookup behavior, with tests and documentation updated accordingly.

File-Level Changes

Change Details Files
Make static file Content-Type optionally include a configured charset based on new middleware option.
  • Introduce local type variable from MIME lookup with application/octet-stream fallback.
  • Derive charset from options.charset which can be a function receiving (file, type) or a string value.
  • Set obj.type and obj.mime to either the bare MIME type or the MIME type plus ; charset=<charset> when provided.
index.js
Add regression tests for string and function-based charset configuration on static responses.
  • Add test case asserting a static file is served with a configured string charset in Content-Type.
  • Add test case asserting a static file is served with a function-derived charset based on file name and MIME type.
test/index.js
Document the new charset configuration option for static cache middleware.
  • Describe options.charset as a string or function and explain its behavior and parameters in the options list.
README.md

Assessment against linked issues

Issue Objective Addressed Explanation
#83 Ensure static file responses can return a correct (non-default) charset in the Content-Type header instead of always relying on Koa's default UTF-8 behavior.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 6 issues, and left some high level feedback:

  • By storing the charset-suffixed value in both obj.type and obj.mime, any downstream logic that expects a bare MIME type may now receive a full Content-Type; consider keeping the raw type (e.g. in obj.mime) and only appending the charset at header write time or in a separate field.
  • The charset option currently passes name rather than the full pathname into the function; if callers may need the full path or extension, consider clarifying this in the README or passing a more descriptive value such as pathname or an object containing both.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- By storing the charset-suffixed value in both `obj.type` and `obj.mime`, any downstream logic that expects a bare MIME type may now receive a full `Content-Type`; consider keeping the raw type (e.g. in `obj.mime`) and only appending the charset at header write time or in a separate field.
- The `charset` option currently passes `name` rather than the full `pathname` into the function; if callers may need the full path or extension, consider clarifying this in the README or passing a more descriptive value such as `pathname` or an object containing both.

## Individual Comments

### Comment 1
<location path="index.js" line_range="193-194" />
<code_context>
+  var charset = typeof options.charset === 'function'
+    ? options.charset(name, type)
+    : options.charset
+  obj.type = obj.mime = charset
+    ? type + '; charset=' + charset
+    : type
   obj.mtime = stats.mtime
</code_context>
<issue_to_address>
**issue (bug_risk):** Appended `charset` may change existing expectations for `obj.type` / `obj.mime` consumers.

`obj.type`/`obj.mime` used to be a bare MIME type (e.g. `text/html`), but will now sometimes be a full `Content-Type` (e.g. `text/html; charset=utf-8`). Any callers that assume a bare MIME may break or behave incorrectly. If both forms are needed, consider keeping the raw MIME (e.g. `obj.mime`) and adding a separate `obj.contentType` that includes the charset.
</issue_to_address>

### Comment 2
<location path="test/index.js" line_range="153-162" />
<code_context>
     })
   })

+  it('should serve files with configured charset', function (done) {
+    var app = new Koa()
+    app.use(staticCache(path.join(__dirname, '..'), {
+      charset: 'iso-8859-1',
+      filter(file) {
+        return file === 'README.md'
+      }
+    }))
+    var server = app.listen()
+
+    request(server)
+    .get('/README.md')
+    .expect('Content-Type', 'text/markdown; charset=iso-8859-1')
+    .expect(200, done)
+  })
</code_context>
<issue_to_address>
**suggestion (testing):** Add a test to cover unknown MIME types with a configured charset to ensure the fallback MIME lookup behavior stays intact.

Specifically, please add a test where the served file has an unknown extension (e.g. `foo.bin`) and `options.charset` is set, and assert that the `Content-Type` is `'application/octet-stream; charset=iso-8859-1'` (or equivalent). This will protect the fallback MIME type and charset concatenation behavior from regressions.

Suggested implementation:

```javascript
    .expect('Content-Type', 'text/markdown; charset=iso-8859-1')
    .expect(200, done)
  })

  it('should serve files with configured charset for unknown mime types', function (done) {
    var app = new Koa()
    app.use(staticCache(path.join(__dirname, '..'), {
      charset: 'iso-8859-1',
      filter(file) {
        return file === 'foo.bin'
      }
    }))
    var server = app.listen()

    request(server)
    .get('/foo.bin')
    .expect('Content-Type', 'application/octet-stream; charset=iso-8859-1')
    .expect(200, done)
  })

```

For this test to pass, ensure there is a `foo.bin` file in the directory being served (based on your existing tests, likely the project root: `path.join(__dirname, '..')`). The file can be empty or contain arbitrary data; it only needs to exist so that the middleware can serve it and trigger the fallback MIME type logic.
</issue_to_address>

### Comment 3
<location path="test/index.js" line_range="183-187" />
<code_context>
+    .expect(200, done)
+  })
+
+  it('should serve files with function configured charset', function (done) {
+    var app = new Koa()
+    app.use(staticCache(path.join(__dirname, '..'), {
+      charset(file, type) {
+        return file === 'README.md' && type === 'text/markdown'
+          ? 'windows-1252'
+          : undefined
+      },
+      filter(file) {
+        return file === 'README.md'
+      }
+    }))
+    var server = app.listen()
+
+    request(server)
+    .get('/README.md')
+    .expect('Content-Type', 'text/markdown; charset=windows-1252')
+    .expect(200, done)
+  })
</code_context>
<issue_to_address>
**suggestion (testing):** Add coverage for the case where the `charset` function returns `undefined` to verify that no charset is appended.

The current test only covers when the charset function returns a string. Please also add a case where `charset(file, type)` returns `undefined` (or another falsy value) and assert that the `Content-Type` header omits `; charset=...`. This documents the fallback behavior and guards against regressions like accidentally sending `; charset=undefined`.

```suggestion
    request(server)
    .get('/README.md')
    .expect('Content-Type', 'text/markdown; charset=windows-1252')
    .expect(200, done)
  })

  it('should not append charset when charset function returns undefined', function (done) {
    var app = new Koa()
    app.use(staticCache(path.join(__dirname, '..'), {
      charset(file, type) {
        // explicit undefined to exercise the "no charset" path
        return undefined
      },
      filter(file) {
        return file === 'README.md'
      }
    }))
    var server = app.listen()

    request(server)
    .get('/README.md')
    .expect('Content-Type', 'text/markdown')
    .expect(200, done)
  })
```
</issue_to_address>

### Comment 4
<location path="README.md" line_range="51" />
<code_context>
 - `options.cacheControl` (str) - optional cache control header. Overrides `options.maxAge`.
 - `options.buffer` (bool) - store the files in memory instead of streaming from the filesystem on each request.
 - `options.gzip` (bool) - when request's accept-encoding include gzip, files will compressed by gzip.
+- `options.charset` (str | function) - optional charset appended to `Content-Type`. If a function is supplied, it receives `(file, type)` and can return a charset per file.
 - `options.usePrecompiledGzip` (bool) - try use gzip files, loaded from disk, like nginx gzip_static
</code_context>
<issue_to_address>
**issue (typo):** Fix grammar in the gzip option description.

Rephrase to: "when the request's Accept-Encoding includes gzip, files will be compressed using gzip" to fix subject-verb agreement and the missing "be" before "compressed".

```suggestion
- `options.gzip` (bool) - when the request's Accept-Encoding includes gzip, files will be compressed using gzip.
```
</issue_to_address>

### Comment 5
<location path="README.md" line_range="53" />
<code_context>
 - `options.buffer` (bool) - store the files in memory instead of streaming from the filesystem on each request.
 - `options.gzip` (bool) - when request's accept-encoding include gzip, files will compressed by gzip.
+- `options.charset` (str | function) - optional charset appended to `Content-Type`. If a function is supplied, it receives `(file, type)` and can return a charset per file.
 - `options.usePrecompiledGzip` (bool) - try use gzip files, loaded from disk, like nginx gzip_static
 - `options.alias` (obj) - object map of aliases. See below.
 - `options.prefix` (str) - the url prefix you wish to add, default to `''`.
</code_context>
<issue_to_address>
**issue (typo):** Add missing "to" in the usePrecompiledGzip description.

Change "try use gzip files" to "try to use gzip files" for correct grammar.

```suggestion
- `options.usePrecompiledGzip` (bool) - try to use gzip files, loaded from disk, like nginx gzip_static
```
</issue_to_address>

### Comment 6
<location path="README.md" line_range="55" />
<code_context>
+- `options.charset` (str | function) - optional charset appended to `Content-Type`. If a function is supplied, it receives `(file, type)` and can return a charset per file.
 - `options.usePrecompiledGzip` (bool) - try use gzip files, loaded from disk, like nginx gzip_static
 - `options.alias` (obj) - object map of aliases. See below.
 - `options.prefix` (str) - the url prefix you wish to add, default to `''`.
</code_context>
<issue_to_address>
**suggestion (typo):** Improve capitalization and verb agreement in the prefix description.

Consider capitalizing "URL" and fixing the verb agreement, for example: "the URL prefix you wish to add, defaults to `''`."
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread index.js Outdated
Comment thread test/index.js
Comment on lines +153 to +162
it('should serve files with configured charset', function (done) {
var app = new Koa()
app.use(staticCache(path.join(__dirname, '..'), {
charset: 'iso-8859-1',
filter(file) {
return file === 'README.md'
}
}))
var server = app.listen()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Add a test to cover unknown MIME types with a configured charset to ensure the fallback MIME lookup behavior stays intact.

Specifically, please add a test where the served file has an unknown extension (e.g. foo.bin) and options.charset is set, and assert that the Content-Type is 'application/octet-stream; charset=iso-8859-1' (or equivalent). This will protect the fallback MIME type and charset concatenation behavior from regressions.

Suggested implementation:

    .expect('Content-Type', 'text/markdown; charset=iso-8859-1')
    .expect(200, done)
  })

  it('should serve files with configured charset for unknown mime types', function (done) {
    var app = new Koa()
    app.use(staticCache(path.join(__dirname, '..'), {
      charset: 'iso-8859-1',
      filter(file) {
        return file === 'foo.bin'
      }
    }))
    var server = app.listen()

    request(server)
    .get('/foo.bin')
    .expect('Content-Type', 'application/octet-stream; charset=iso-8859-1')
    .expect(200, done)
  })

For this test to pass, ensure there is a foo.bin file in the directory being served (based on your existing tests, likely the project root: path.join(__dirname, '..')). The file can be empty or contain arbitrary data; it only needs to exist so that the middleware can serve it and trigger the fallback MIME type logic.

Comment thread test/index.js
Comment on lines +183 to +187
request(server)
.get('/README.md')
.expect('Content-Type', 'text/markdown; charset=windows-1252')
.expect(200, done)
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Add coverage for the case where the charset function returns undefined to verify that no charset is appended.

The current test only covers when the charset function returns a string. Please also add a case where charset(file, type) returns undefined (or another falsy value) and assert that the Content-Type header omits ; charset=.... This documents the fallback behavior and guards against regressions like accidentally sending ; charset=undefined.

Suggested change
request(server)
.get('/README.md')
.expect('Content-Type', 'text/markdown; charset=windows-1252')
.expect(200, done)
})
request(server)
.get('/README.md')
.expect('Content-Type', 'text/markdown; charset=windows-1252')
.expect(200, done)
})
it('should not append charset when charset function returns undefined', function (done) {
var app = new Koa()
app.use(staticCache(path.join(__dirname, '..'), {
charset(file, type) {
// explicit undefined to exercise the "no charset" path
return undefined
},
filter(file) {
return file === 'README.md'
}
}))
var server = app.listen()
request(server)
.get('/README.md')
.expect('Content-Type', 'text/markdown')
.expect(200, done)
})

Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Return the correct charset in content-type

1 participant