Bump the go-modules-updates group in /backend with 7 updates (#1995)

* Bump the go-modules-updates group in /backend with 7 updates

Bumps the go-modules-updates group in /backend with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) | `2.21.1` | `2.23.1` |
| [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) | `5.2.3` | `5.2.4` |
| [github.com/go-pkgz/rest](https://github.com/go-pkgz/rest) | `1.20.6` | `1.21.0` |
| [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) | `5.3.0` | `5.3.1` |
| [golang.org/x/crypto](https://github.com/golang/crypto) | `0.46.0` | `0.47.0` |
| [golang.org/x/image](https://github.com/golang/image) | `0.34.0` | `0.35.0` |
| [golang.org/x/net](https://github.com/golang/net) | `0.48.0` | `0.49.0` |


Updates `github.com/alecthomas/chroma/v2` from 2.21.1 to 2.23.1
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.21.1...v2.23.1)

Updates `github.com/go-chi/chi/v5` from 5.2.3 to 5.2.4
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.2.3...v5.2.4)

Updates `github.com/go-pkgz/rest` from 1.20.6 to 1.21.0
- [Release notes](https://github.com/go-pkgz/rest/releases)
- [Commits](https://github.com/go-pkgz/rest/compare/v1.20.6...v1.21.0)

Updates `github.com/golang-jwt/jwt/v5` from 5.3.0 to 5.3.1
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Commits](https://github.com/golang-jwt/jwt/compare/v5.3.0...v5.3.1)

Updates `golang.org/x/crypto` from 0.46.0 to 0.47.0
- [Commits](https://github.com/golang/crypto/compare/v0.46.0...v0.47.0)

Updates `golang.org/x/image` from 0.34.0 to 0.35.0
- [Commits](https://github.com/golang/image/compare/v0.34.0...v0.35.0)

Updates `golang.org/x/net` from 0.48.0 to 0.49.0
- [Commits](https://github.com/golang/net/compare/v0.48.0...v0.49.0)

---
updated-dependencies:
- dependency-name: github.com/alecthomas/chroma/v2
  dependency-version: 2.23.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-modules-updates
- dependency-name: github.com/go-chi/chi/v5
  dependency-version: 5.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-modules-updates
- dependency-name: github.com/go-pkgz/rest
  dependency-version: 1.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-modules-updates
- dependency-name: github.com/golang-jwt/jwt/v5
  dependency-version: 5.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-modules-updates
- dependency-name: golang.org/x/crypto
  dependency-version: 0.47.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-modules-updates
- dependency-name: golang.org/x/image
  dependency-version: 0.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-modules-updates
- dependency-name: golang.org/x/net
  dependency-version: 0.49.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-modules-updates
...

Signed-off-by: dependabot[bot] <support@github.com>

* Run go mod tidy in examples directory

Co-authored-by: paskal <712534+paskal@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: paskal <712534+paskal@users.noreply.github.com>
This commit is contained in:
dependabot[bot]
2026-02-14 19:48:24 -06:00
committed by GitHub
co-authored by paskal dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
parent e3b0d63648
commit 638fa63e81
77 changed files with 9259 additions and 45191 deletions
+6 -6
View File
@@ -13,12 +13,12 @@ require (
require (
github.com/Depado/bfchroma/v2 v2.0.0 // indirect
github.com/PuerkitoBio/goquery v1.11.0 // indirect
github.com/alecthomas/chroma/v2 v2.21.1 // indirect
github.com/alecthomas/chroma/v2 v2.23.1 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/go-pkgz/rest v1.20.6 // indirect
github.com/go-pkgz/rest v1.21.0 // indirect
github.com/go-pkgz/routegroup v1.6.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
@@ -30,10 +30,10 @@ require (
github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
go.etcd.io/bbolt v1.4.3 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/image v0.34.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/image v0.36.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/sys v0.41.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+12 -12
View File
@@ -4,8 +4,8 @@ github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.21.1 h1:FaSDrp6N+3pphkNKU6HPCiYLgm8dbe5UXIXcoBhZSWA=
github.com/alecthomas/chroma/v2 v2.21.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
@@ -21,8 +21,8 @@ github.com/go-pkgz/jrpc v0.4.0 h1:oD7xiGrzDkndkuCjeHGugQXxbggLSV7O1QmHhoc5pYY=
github.com/go-pkgz/jrpc v0.4.0/go.mod h1:JFoY3bRjRyx4M3CbEVDFQStMB1m2gmQ7OjqFK7q3kOo=
github.com/go-pkgz/lgr v0.12.1 h1:8GVfG2rSARq3Eaj5PP158rtBR2LHVGkwioIkQBGbvKg=
github.com/go-pkgz/lgr v0.12.1/go.mod h1:A4AxjOthFVFK6jRnVYMeusno5SeDAxcLVHd0kI/lN/Y=
github.com/go-pkgz/rest v1.20.6 h1:O/IhQ3I2cS4bJYvL1TLcy63w2OcXZTTBG3R+wTIqPS4=
github.com/go-pkgz/rest v1.20.6/go.mod h1:NY+MX1is2kJckJt+nHDNovS/5j9jmF4yQuSno4qg7XU=
github.com/go-pkgz/rest v1.21.0 h1:Y/C4d/TpclJJDxqnH1RAcS6Hmox0RIReAlkwMcUWXK4=
github.com/go-pkgz/rest v1.21.0/go.mod h1:+AHzjHazq7Z3Tk/kRWOhbbAz/YZlUV40feC1Hf4NtbE=
github.com/go-pkgz/routegroup v1.6.0 h1:44XHZgF6JIIldRlv+zjg6SygULASmjifnfIQjwCT0e4=
github.com/go-pkgz/routegroup v1.6.0/go.mod h1:Pmu04fhgWhRtBMIJ8HXppnnzOPjnL/IEPBIdO2zmeqg=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -62,10 +62,10 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc=
golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -80,8 +80,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -102,8 +102,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+9 -9
View File
@@ -5,9 +5,9 @@ go 1.25
require (
github.com/Depado/bfchroma/v2 v2.0.0
github.com/PuerkitoBio/goquery v1.11.0
github.com/alecthomas/chroma/v2 v2.21.1
github.com/alecthomas/chroma/v2 v2.23.1
github.com/didip/tollbooth/v8 v8.0.1
github.com/go-chi/chi/v5 v5.2.3
github.com/go-chi/chi/v5 v5.2.5
github.com/go-chi/cors v1.2.2
github.com/go-pkgz/auth/v2 v2.1.2-0.20260211003156-fbba7f2baa6b
github.com/go-pkgz/jrpc v0.4.0
@@ -15,9 +15,9 @@ require (
github.com/go-pkgz/lgr v0.12.1
github.com/go-pkgz/notify v1.3.0
github.com/go-pkgz/repeater/v2 v2.2.0
github.com/go-pkgz/rest v1.20.6
github.com/go-pkgz/rest v1.21.0
github.com/go-pkgz/syncs v1.3.2
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/uuid v1.6.0
github.com/gorilla/feeds v1.2.0
github.com/hashicorp/go-multierror v1.1.1
@@ -30,9 +30,9 @@ require (
github.com/stretchr/testify v1.11.1
go.etcd.io/bbolt v1.4.3
go.uber.org/goleak v1.3.0
golang.org/x/crypto v0.46.0
golang.org/x/image v0.34.0
golang.org/x/net v0.48.0
golang.org/x/crypto v0.48.0
golang.org/x/image v0.36.0
golang.org/x/net v0.49.0
)
require (
@@ -67,7 +67,7 @@ require (
go.mongodb.org/mongo-driver v1.17.6 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+18 -24
View File
@@ -8,8 +8,8 @@ github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.21.1 h1:FaSDrp6N+3pphkNKU6HPCiYLgm8dbe5UXIXcoBhZSWA=
github.com/alecthomas/chroma/v2 v2.21.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
@@ -42,18 +42,12 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-oauth2/oauth2/v4 v4.5.4 h1:YjI0tmGW8oxVhn9QSBIxlr641QugWrJY5UWa6XmLcW0=
github.com/go-oauth2/oauth2/v4 v4.5.4/go.mod h1:BXiOY+QZtZy2ewbsGk2B5P8TWmtz/Rf7ES5ZttQFxfQ=
github.com/go-pkgz/auth/v2 v2.1.1 h1:CBH3Z6ovLT51Nx9TkBcu2L8Dd/xwL6CgJcgqUnC2isQ=
github.com/go-pkgz/auth/v2 v2.1.1/go.mod h1:9LwzESczjMavmXNZo1XhYpfYdKWtoCbXt/ZIi0GTvF0=
github.com/go-pkgz/auth/v2 v2.1.2-0.20260119213210-5ff800f4c064 h1:+7XirxGV7RV0VzStWbyP6c0edbfaCXB69JeZh6uVpE0=
github.com/go-pkgz/auth/v2 v2.1.2-0.20260119213210-5ff800f4c064/go.mod h1:9LwzESczjMavmXNZo1XhYpfYdKWtoCbXt/ZIi0GTvF0=
github.com/go-pkgz/auth/v2 v2.1.2-0.20260210234152-7e1ed2cedf71 h1:FxzoRgUemfWKBt0iw9J9Qck5nHB6bNlhmUJneMNL/C8=
github.com/go-pkgz/auth/v2 v2.1.2-0.20260210234152-7e1ed2cedf71/go.mod h1:9LwzESczjMavmXNZo1XhYpfYdKWtoCbXt/ZIi0GTvF0=
github.com/go-pkgz/auth/v2 v2.1.2-0.20260211003156-fbba7f2baa6b h1:N8iS/o/LgbSL4NLabOuLgfmROjtMLW2Qc3EsMmdYNGs=
github.com/go-pkgz/auth/v2 v2.1.2-0.20260211003156-fbba7f2baa6b/go.mod h1:9LwzESczjMavmXNZo1XhYpfYdKWtoCbXt/ZIi0GTvF0=
github.com/go-pkgz/email v0.6.0 h1:snZnXldjeF4PgKSjnx9Fa25mtOgFpAOEeWvnQvrxjLE=
@@ -72,16 +66,16 @@ github.com/go-pkgz/repeater v1.2.0 h1:oJFvjyKdTDd5RCzpzxlzYIZFFj6Zfl17rE1aUfu6Uj
github.com/go-pkgz/repeater v1.2.0/go.mod h1:vypP6xamA53MFmafnGUucqOmALKk36xgKu2hSG73LHM=
github.com/go-pkgz/repeater/v2 v2.2.0 h1:8nZR/NaknmLfx2YMHbr78u9OL4Xj+8+romm9dz4FpMg=
github.com/go-pkgz/repeater/v2 v2.2.0/go.mod h1:RgX5vUbLKq7PV82QUDP5pFbQS1os4Z+U9XzKymK23A8=
github.com/go-pkgz/rest v1.20.6 h1:O/IhQ3I2cS4bJYvL1TLcy63w2OcXZTTBG3R+wTIqPS4=
github.com/go-pkgz/rest v1.20.6/go.mod h1:NY+MX1is2kJckJt+nHDNovS/5j9jmF4yQuSno4qg7XU=
github.com/go-pkgz/rest v1.21.0 h1:Y/C4d/TpclJJDxqnH1RAcS6Hmox0RIReAlkwMcUWXK4=
github.com/go-pkgz/rest v1.21.0/go.mod h1:+AHzjHazq7Z3Tk/kRWOhbbAz/YZlUV40feC1Hf4NtbE=
github.com/go-pkgz/routegroup v1.6.0 h1:44XHZgF6JIIldRlv+zjg6SygULASmjifnfIQjwCT0e4=
github.com/go-pkgz/routegroup v1.6.0/go.mod h1:Pmu04fhgWhRtBMIJ8HXppnnzOPjnL/IEPBIdO2zmeqg=
github.com/go-pkgz/syncs v1.3.2 h1:gmioASlJNy3gNosPlgvWOM2QP0Hdjzn2u+/sUShgd8E=
github.com/go-pkgz/syncs v1.3.2/go.mod h1:qjgzpp7OpuhDf7BWsW/FHCu9DLjE32NPy6/vXAXT/Cw=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -207,10 +201,10 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc=
golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -225,8 +219,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -249,8 +243,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -270,8 +264,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+11
View File
@@ -0,0 +1,11 @@
Chroma is a syntax highlighting library, tool and web playground for Go. It is based on Pygments and includes importers for it, so most of the same concepts from Pygments apply to Chroma.
This project is written in Go, uses Hermit to manage tooling, and Just for helper commands. Helper scripts are in ./scripts.
Language definitions are XML files defined in ./lexers/embedded/*.xml.
Styles/themes are defined in ./styles/*.xml.
The CLI can be run with `chroma`.
The web playground can be run with `chromad --csrf-key=moo`. It blocks, so should generally be run in the background. It also does not hot reload, so has to be manually restarted. The playground has two modes - for local development it uses the server itself to render, while for production running `just chromad` will compile ./cmd/libchromawasm into a WASM module that is bundled into `chromad`.
+2 -3
View File
@@ -7,7 +7,6 @@ FROM ubuntu:24.04 AS builder
RUN apt-get update && apt-get install -y \
curl \
git \
make \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
@@ -25,8 +24,8 @@ ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64
# Build the application using make
RUN make build/chromad
# Build the application using just
RUN just chromad
# Runtime stage
FROM alpine:3.23 AS runtime
+55
View File
@@ -0,0 +1,55 @@
set positional-arguments := true
set shell := ["bash", "-c"]
version := `git describe --tags --dirty --always`
export GOOS := env("GOOS", "linux")
export GOARCH := env("GOARCH", "amd64")
_help:
@just -l
# Generate README.md from lexer definitions
readme:
#!/usr/bin/env bash
GOOS= GOARCH= ./table.py
# Generate tokentype_string.go
tokentype-string:
go generate
# Format JavaScript files
format-js:
biome format --write cmd/chromad/static/index.js cmd/chromad/static/chroma.js
# Build chromad binary
chromad: wasm-exec chroma-wasm
#!/usr/bin/env bash
rm -rf build
mk cmd/chromad/static/index.min.js : cmd/chromad/static/{index,chroma}.js -- \
esbuild --platform=browser --format=esm --bundle cmd/chromad/static/index.js --minify --external:./wasm_exec.js --outfile=cmd/chromad/static/index.min.js
mk cmd/chromad/static/index.min.css : cmd/chromad/static/index.css -- \
esbuild --bundle cmd/chromad/static/index.css --minify --outfile=cmd/chromad/static/index.min.css
cd cmd/chromad && CGOENABLED=0 go build -ldflags="-X 'main.version={{ version }}'" -o ../../build/chromad .
# Copy wasm_exec.js from TinyGo
wasm-exec:
#!/usr/bin/env bash
tinygoroot=$(tinygo env TINYGOROOT)
mk cmd/chromad/static/wasm_exec.js : "$tinygoroot/targets/wasm_exec.js" -- \
install -m644 "$tinygoroot/targets/wasm_exec.js" cmd/chromad/static/wasm_exec.js
# Build WASM binary
chroma-wasm:
#!/usr/bin/env bash
if type tinygo > /dev/null 2>&1; then
mk cmd/chromad/static/chroma.wasm : cmd/libchromawasm/main.go -- \
tinygo build -no-debug -target wasm -o cmd/chromad/static/chroma.wasm cmd/libchromawasm/main.go
else
mk cmd/chromad/static/chroma.wasm : cmd/libchromawasm/main.go -- \
GOOS=js GOARCH=wasm go build -o cmd/chromad/static/chroma.wasm cmd/libchromawasm/main.go
fi
# Upload chromad to server
upload: chromad
scp build/chromad root@swapoff.org:
ssh root@swapoff.org 'install -m755 ./chromad /srv/http/swapoff.org/bin && service chromad restart'
-42
View File
@@ -1,42 +0,0 @@
.PHONY: chromad upload all
VERSION ?= $(shell git describe --tags --dirty --always)
export GOOS ?= linux
export GOARCH ?= amd64
all: README.md tokentype_string.go
README.md: lexers/*.go lexers/embedded/*.xml
GOOS= GOARCH= ./table.py
tokentype_string.go: types.go
go generate
.PHONY: format-js
format-js:
biome format --write cmd/chromad/static/{index.js,chroma.js}
.PHONY: chromad
chromad: build/chromad
build/chromad: $(shell find cmd/chromad -name '*.go' -o -name '*.html' -o -name '*.css' -o -name '*.js') \
cmd/chromad/static/wasm_exec.js \
cmd/chromad/static/chroma.wasm
rm -rf build
esbuild --platform=node --bundle cmd/chromad/static/index.js --minify --outfile=cmd/chromad/static/index.min.js
esbuild --bundle cmd/chromad/static/index.css --minify --outfile=cmd/chromad/static/index.min.css
(export CGOENABLED=0 ; go build -C cmd/chromad -ldflags="-X 'main.version=$(VERSION)'" -o ../../build/chromad .)
cmd/chromad/static/wasm_exec.js: $(shell tinygo env TINYGOROOT)/targets/wasm_exec.js
install -m644 $< $@
cmd/chromad/static/chroma.wasm: $(shell git ls-files | grep '\.go|\.xml')
if type tinygo > /dev/null; then \
tinygo build -no-debug -target wasm -o $@ cmd/libchromawasm/main.go; \
else \
GOOS=js GOARCH=wasm go build -o $@ cmd/libchromawasm/main.go; \
fi
upload: build/chromad
scp build/chromad root@swapoff.org: && \
ssh root@swapoff.org 'install -m755 ./chromad /srv/http/swapoff.org/bin && service chromad restart'
+1 -1
View File
@@ -48,7 +48,7 @@ translators for Pygments lexers and styles.
| J | J, Janet, Java, JavaScript, JSON, JSONata, Jsonnet, Julia, Jungle
| K | Kakoune, Kotlin
| L | Lean4, Lighttpd configuration file, LLVM, lox, Lua
| M | Makefile, Mako, markdown, Mason, Materialize SQL dialect, Mathematica, Matlab, MCFunction, Meson, Metal, MiniZinc, MLIR, Modelica, Modula-2, Mojo, MonkeyC, MoonScript, MorrowindScript, Myghty, MySQL
| M | Makefile, Mako, markdown, Markless, Mason, Materialize SQL dialect, Mathematica, Matlab, MCFunction, Meson, Metal, MiniZinc, MLIR, Modelica, Modula-2, Mojo, MonkeyC, MoonScript, MorrowindScript, Myghty, MySQL
| N | NASM, Natural, NDISASM, Newspeak, Nginx configuration file, Nim, Nix, NSIS, Nu
| O | Objective-C, ObjectPascal, OCaml, Octave, Odin, OnesEnterprise, OpenEdge ABL, OpenSCAD, Org Mode
| P | PacmanConf, Perl, PHP, PHTML, Pig, PkgConfig, PL/pgSQL, plaintext, Plutus Core, Pony, PostgreSQL SQL dialect, PostScript, POVRay, PowerQuery, PowerShell, Prolog, Promela, PromQL, properties, Protocol Buffer, Protocol Buffer Text Format, PRQL, PSL, Puppet, Python, Python 2
@@ -528,6 +528,7 @@ func (f *Formatter) styleToCSS(style *chroma.Style) map[chroma.TokenType]string
}
classes[chroma.Background] += `;` + f.tabWidthStyle()
classes[chroma.PreWrapper] += classes[chroma.Background]
classes[chroma.PreWrapper] += ` -webkit-text-size-adjust: none;`
// Make PreWrapper a grid to show highlight style with full width.
if len(f.highlightRanges) > 0 && f.customCSS[chroma.PreWrapper] == `` {
classes[chroma.PreWrapper] += `display: grid;`
+3 -1
View File
@@ -12,6 +12,7 @@
<filename>*.ebuild</filename>
<filename>*.eclass</filename>
<filename>.env</filename>
<filename>.env.*</filename>
<filename>*.env</filename>
<filename>*.exheres-0</filename>
<filename>*.exlib</filename>
@@ -23,6 +24,7 @@
<filename>bash_*</filename>
<filename>zshrc</filename>
<filename>.zshrc</filename>
<filename>APKBUILD</filename>
<filename>PKGBUILD</filename>
<mime_type>application/x-sh</mime_type>
<mime_type>application/x-shellscript</mime_type>
@@ -217,4 +219,4 @@
</rule>
</state>
</rules>
</lexer>
</lexer>
+75
View File
@@ -0,0 +1,75 @@
<lexer>
<config>
<name>KDL</name>
<alias>kdl</alias>
<filename>*.kdl</filename>
</config>
<rules>
<state name="root">
<rule pattern="((?&lt;={|;)|^)\s*(?![/\\\{\}#;\[\]\=])[&lt;&gt;:\w\-_~,\&#x27;`!\?@\$%^&amp;*+|\.\(\)\x{0080}-\x{0001f645}]+\d*?[&lt;&gt;:\w\-_~,\&#x27;`!\?@\$%^&amp;*+|\.\(\)\x{0080}-\x{0001f645}]*?"><token type="NameLabel"/></rule>
<rule pattern="(#true|#false|#null|#nan|#inf|#-inf)\b"><token type="KeywordConstant"/></rule>
<rule pattern="[{}=;\\]"><token type="Operator"/></rule>
<rule pattern="(\b([0-9-\+]|-|\+)[0-9_]*?\.[0-9][0-9_]*?([eE][+-]?[0-9_]+)?\b|\b[0-9][0-9_]*?(\.[0-9][0-9_]*?)?[eE][+-]?[0-9_]+\b)"><token type="LiteralNumberFloat"/></rule>
<rule pattern="\b[0-9\-\+][0-9_]*\b"><token type="LiteralNumber"/></rule>
<rule pattern="\b0x[a-fA-F0-9][a-fA-F0-9_]*?\b"><token type="LiteralNumberHex"/></rule>
<rule pattern="\b0o[0-7][0-7_]*\b"><token type="LiteralNumberOct"/></rule>
<rule pattern="\b0b[01][01_]*?\b"><token type="LiteralNumberBin"/></rule>
<rule pattern="#+(\&quot;&quot;&quot;|&quot;).*?(&quot;&quot;&quot;|&quot;)#+"><token type="LiteralString"/></rule>
<rule pattern="#?&quot;&quot;&quot;"><token type="LiteralString"/><push state="multiline_string"/></rule>
<rule pattern="#?&quot;"><token type="LiteralString"/><push state="string"/></rule>
<rule pattern="/\*"><token type="CommentMultiline"/><push state="comment"/></rule>
<rule pattern="/\*!"><token type="LiteralStringDoc"/><push state="doccomment"/></rule>
<rule pattern="/-\s*{"><token type="CommentMultiline"/><push state="slashdash_block_comment"/></rule>
<rule pattern="\s*/-\s?[^\s=]*?\s?{"><token type="CommentMultiline"/><push state="slashdash_node_comment"/></rule>
<rule pattern="(?&lt;!^)\s*/-\s*(&quot;.*&quot;|.*?)?\s"><token type="CommentSingle"/></rule>
<rule pattern="(?&lt;=^)\s*/-[^{]+{"><token type="CommentMultiline"/><push state="slashdash_node_with_children_comment"/></rule>
<rule pattern="(\/\/(.*?)\n|(?&lt;!^)\s*/-\s*?\s)"><token type="CommentSingle"/></rule>
<rule pattern="(?![/\\\{\}#;\[\]\=])[&lt;&gt;:\w\-_~,\&#x27;`!\?@\$%^&amp;*+|.\(\)\x{0080}-\x{0001f645}]+\d*?[&lt;&gt;:\w\-_~,\&#x27;`!\?@\$%^&amp;*+|.\(\)\x{0080}-\x{0001f645}]*(=)"><token type="NameAttribute"/></rule>
<rule pattern="(?![/\\{\}#;\[\]\=])[&lt;&gt;:\w\-_~,\&#x27;`!\?@\$%^&amp;*+|.\(\)\x{0080}-\x{0001f645}]+\d*[&lt;&gt;:\w\-_~,\&#x27;`!\?@\$%^&amp;*+|.\(\)\x{0080}-\x{0001f645}]*?"><token type="LiteralString"/></rule>
<rule pattern="\s"><token type="TextWhitespace"/></rule>
</state>
<state name="string">
<rule pattern="&quot;#?"><token type="LiteralString"/><pop depth="1"/></rule>
<rule pattern="\\[&#x27;&quot;\\nrt]|\\x[0-7][0-9a-fA-F]|\\0|\\u\{[0-9a-fA-F]{1,6}\}"><token type="LiteralStringEscape"/></rule>
<rule pattern="[^\\&quot;]+"><token type="LiteralString"/></rule>
<rule pattern="\\"><token type="LiteralString"/></rule>
</state>
<state name="multiline_string">
<rule pattern="&quot;&quot;&quot;#?"><token type="LiteralString"/><pop depth="1"/></rule>
<rule pattern="\\[&#x27;&quot;\\nrt]|\\x[0-7][0-9a-fA-F]|\\0|\\u\{[0-9a-fA-F]{1,6}\}"><token type="LiteralStringEscape"/></rule>
<rule pattern="&quot;"><token type="LiteralString"/></rule>
<rule pattern="[^\\&quot;]+"><token type="LiteralString"/></rule>
<rule pattern="\\"><token type="LiteralString"/></rule>
</state>
<state name="slashdash_block_comment">
<rule pattern="[^}]+"><token type="CommentMultiline"/></rule>
<rule pattern="/-\s*{"><token type="CommentMultiline"/><push/></rule>
<rule pattern="\}"><token type="CommentMultiline"/><pop depth="1"/></rule>
<rule pattern="[\}]"><token type="CommentMultiline"/></rule>
</state>
<state name="slashdash_node_comment">
<rule pattern="[^\}]+"><token type="CommentMultiline"/></rule>
<rule pattern="^\s*?/-.*?\s?{"><token type="CommentMultiline"/><push/></rule>
<rule pattern="\}"><token type="CommentMultiline"/><pop depth="1"/></rule>
<rule pattern="[\}]"><token type="CommentMultiline"/></rule>
</state>
<state name="slashdash_node_with_children_comment">
<rule pattern="[^\}]+"><token type="CommentMultiline"/></rule>
<rule pattern="(?&lt;=^)\s*/-[^{]+{"><token type="CommentMultiline"/><push/></rule>
<rule pattern="\}"><token type="CommentMultiline"/><pop depth="1"/></rule>
<rule pattern="[\}]"><token type="CommentMultiline"/></rule>
</state>
<state name="comment">
<rule pattern="[^*/]+"><token type="CommentMultiline"/></rule>
<rule pattern="/\*"><token type="CommentMultiline"/><push/></rule>
<rule pattern="\*/"><token type="CommentMultiline"/><pop depth="1"/></rule>
<rule pattern="[*/]"><token type="CommentMultiline"/></rule>
</state>
<state name="doccomment">
<rule pattern="[^*/]+"><token type="LiteralStringDoc"/></rule>
<rule pattern="/\*"><token type="LiteralStringDoc"/><push/></rule>
<rule pattern="\*/"><token type="LiteralStringDoc"/><pop depth="1"/></rule>
<rule pattern="[*/]"><token type="LiteralStringDoc"/></rule>
</state>
</rules>
</lexer>
@@ -0,0 +1,139 @@
<lexer>
<config>
<name>microcad</name>
<alias>µcad</alias>
<filename>*.µcad</filename>
<filename>*.ucad</filename>
<filename>*.mcad</filename>
<mime_type>text/microcad</mime_type>
<ensure_nl>true</ensure_nl>
</config>
<rules>
<!-- Root state -->
<state name="root">
<rule pattern="\n">
<token type="TextWhitespace"/>
</rule>
<rule pattern="\s+">
<token type="TextWhitespace"/>
</rule>
<!-- COMMENTS -->
<rule pattern="///.*">
<token type="LiteralStringDoc"/>
</rule>
<rule pattern="//.*">
<token type="CommentSingle"/>
</rule>
<rule pattern="/\*.*">
<token type="CommentMultiline"/>
<push state="comment"/>
</rule>
<!-- KEYWORDS -->
<rule pattern="\b(pub|sketch|part|op|mod|use|fn|const|prop|init|return|if|else|mat|__builtin|or|and|not|as)\b">
<token type="Keyword"/>
</rule>
<!-- TYPES -->
<rule pattern="\b(Integer|Scalar|String|Color|Length|Area|Volume|Angle|Weight|Density|Bool|Matrix[0-9])\b">
<token type="KeywordType"/>
</rule>
<!-- FUNCTION NAMES -->
<rule pattern="\b([a-z_][a-zA-Z0-9_]*)\s*(?=\()">
<token type="NameFunction"/>
</rule>
<!-- OPERATORS -->
<rule pattern="[+\-*/%&amp;|&lt;&gt;^!@=]">
<token type="Operator"/>
</rule>
<!-- NUMBERS + UNITS -->
<rule pattern="\b(([0-9]+(\.[0-9]+)?)(%|m²|cm²|mm²|µm²|in²|ft²|yd²|m³|cm³|mm³|µm³|in³|ft³|yd³|ml|cl|l|µl|cm|mm|m|µm|in|ft|yd|deg|°|grad|turn|rad|g|kg|lb|oz)?)|true|false\b">
<token type="LiteralNumber"/>
</rule>
<!-- NAMESPACES -->
<rule pattern="([a-z_][a-z0-9_]*)(::)">
<bygroups>
<token type="NameNamespace"/>
<token type="Operator"/>
</bygroups>
</rule>
<!-- CONSTANTS (ALL CAPS) -->
<rule pattern="\b([A-Z_][A-Z0-9_]*)\b">
<token type="NameConstant"/>
</rule>
<!-- CUSTOM TYPES (Capitalized identifiers) -->
<rule pattern="\b([A-Z_][a-zA-Z0-9_]*)\b">
<token type="NameClass"/>
</rule>
<!-- VARIABLES -->
<rule pattern="\b([a-z_][a-zA-Z0-9_]*)\b">
<token type="Name"/>
</rule>
<!-- STRINGS -->
<rule pattern="&quot;[^&quot;]*&quot;">
<token type="LiteralString"/>
</rule>
<!-- PAREN GROUP -->
<rule pattern="[{}()\[\],.;:]">
<token type="Punctuation"/>
</rule>
<!-- BRACE GROUP -->
<rule pattern="\{">
<token type="Punctuation"/>
<push state="brace"/>
</rule>
<rule pattern="[\}\)]">
<token type="Punctuation"/>
</rule>
</state>
<!-- Parenthesized expression -->
<state name="paren">
<rule pattern="\)">
<token type="Punctuation"/>
<pop/>
</rule>
<rule>
<include state="root"/>
</rule>
</state>
<!-- Braced expression -->
<state name="brace">
<rule pattern="\}">
<token type="Punctuation"/>
<pop/>
</rule>
<rule>
<include state="root"/>
</rule>
</state>
<state name="comment">
<!-- End of comment -->
<rule pattern="\*/">
<token type="CommentMultiline"/>
<pop/>
</rule>
</state>
</rules>
</lexer>
+105 -105
View File
@@ -1,106 +1,106 @@
<lexer>
<config>
<name>Modelica</name>
<alias>modelica</alias>
<filename>*.mo</filename>
<mime_type>text/x-modelica</mime_type>
<dot_all>true</dot_all>
<ensure_nl>true</ensure_nl>
</config>
<rules>
<state name="root">
<!-- Comments -->
<rule pattern="//[^\n\r]*">
<token type="CommentSingle"/>
</rule>
<rule pattern="/[*].*?[*]/">
<token type="CommentMultiline"/>
</rule>
<!-- Whitespace and newlines -->
<rule pattern="\s+">
<token type="Text"/>
</rule>
<rule pattern="\n">
<token type="Text"/>
</rule>
<!-- Keywords (Modelica specific) -->
<rule pattern="\b(model|equation|end|extends|function|type|record|connector|parameter|constant|if|then|else|for|in|when|assert|outer|algorithm|flow|discrete|input|output|loop|elseif|return|public|protected|external|real|integer|boolean|string|array|complex|union|tuple|class|der|time|tstart|tstop)\b">
<token type="Keyword"/>
</rule>
<!-- Type definitions -->
<rule pattern="\b(boolean|integer|real|string|array|record|complex|union|tuple)\b">
<token type="KeywordType"/>
</rule>
<!-- Preprocessor directives -->
<rule pattern="#[ \t]*(if|else|endif|define|include|error)\b">
<token type="CommentPreproc"/>
</rule>
<!-- Literals -->
<rule pattern="\\&quot;[^&quot;\n]*\\&quot;">
<token type="LiteralString"/>
</rule>
<rule pattern="'[^'\n]'">
<token type="LiteralStringChar"/>
</rule>
<rule pattern="\d+(\.\d+)?([eE][+-]?\d+)?">
<token type="LiteralNumber"/>
</rule>
<!-- Identifiers and names -->
<rule pattern="@[A-Za-z_]\w*">
<token type="Name"/>
</rule>
<rule pattern="[A-Za-z_]\w*">
<token type="Name"/>
</rule>
<!-- Punctuation -->
<rule pattern="[{}(),;=.+\-*/&amp;|!&lt;&gt;^%]">
<token type="Punctuation"/>
</rule>
<!-- Operators -->
<rule pattern="(\+|\-|\*|\/|\^|\&amp;|\||\&lt;|\&gt;|\%|\=|\!=|\&lt;\=|\&gt;\=)">
<token type="Operator"/>
</rule>
</state>
<state name="function">
<!-- Function name -->
<rule pattern="[A-Za-z_]\w*">
<token type="NameFunction"/>
<pop depth="1"/>
</rule>
<rule>
<pop depth="1"/>
</rule>
</state>
<state name="type">
<!-- Type definition -->
<rule pattern="[A-Za-z_]\w*">
<token type="NameClass"/>
<pop depth="1"/>
</rule>
<rule>
<pop depth="1"/>
</rule>
</state>
<state name="record">
<!-- Record definition -->
<rule pattern="[A-Za-z_]\w*">
<token type="NameClass"/>
<pop depth="1"/>
</rule>
<rule>
<pop depth="1"/>
</rule>
</state>
</rules>
<lexer>
<config>
<name>Modelica</name>
<alias>modelica</alias>
<filename>*.mo</filename>
<mime_type>text/x-modelica</mime_type>
<dot_all>true</dot_all>
<ensure_nl>true</ensure_nl>
</config>
<rules>
<state name="root">
<!-- Comments -->
<rule pattern="//[^\n\r]*">
<token type="CommentSingle"/>
</rule>
<rule pattern="/[*].*?[*]/">
<token type="CommentMultiline"/>
</rule>
<!-- Whitespace and newlines -->
<rule pattern="\s+">
<token type="Text"/>
</rule>
<rule pattern="\n">
<token type="Text"/>
</rule>
<!-- Keywords (Modelica specific) -->
<rule pattern="\b(model|equation|end|extends|function|type|record|connector|parameter|constant|if|then|else|for|in|when|assert|outer|algorithm|flow|discrete|input|output|loop|elseif|return|public|protected|external|real|integer|boolean|string|array|complex|union|tuple|class|der|time|tstart|tstop)\b">
<token type="Keyword"/>
</rule>
<!-- Type definitions -->
<rule pattern="\b(boolean|integer|real|string|array|record|complex|union|tuple)\b">
<token type="KeywordType"/>
</rule>
<!-- Preprocessor directives -->
<rule pattern="#[ \t]*(if|else|endif|define|include|error)\b">
<token type="CommentPreproc"/>
</rule>
<!-- Literals -->
<rule pattern="\\&quot;[^&quot;\n]*\\&quot;">
<token type="LiteralString"/>
</rule>
<rule pattern="'[^'\n]'">
<token type="LiteralStringChar"/>
</rule>
<rule pattern="\d+(\.\d+)?([eE][+-]?\d+)?">
<token type="LiteralNumber"/>
</rule>
<!-- Identifiers and names -->
<rule pattern="@[A-Za-z_]\w*">
<token type="Name"/>
</rule>
<rule pattern="[A-Za-z_]\w*">
<token type="Name"/>
</rule>
<!-- Punctuation -->
<rule pattern="[{}(),;=.+\-*/&amp;|!&lt;&gt;^%]">
<token type="Punctuation"/>
</rule>
<!-- Operators -->
<rule pattern="(\+|\-|\*|\/|\^|\&amp;|\||\&lt;|\&gt;|\%|\=|\!=|\&lt;\=|\&gt;\=)">
<token type="Operator"/>
</rule>
</state>
<state name="function">
<!-- Function name -->
<rule pattern="[A-Za-z_]\w*">
<token type="NameFunction"/>
<pop depth="1"/>
</rule>
<rule>
<pop depth="1"/>
</rule>
</state>
<state name="type">
<!-- Type definition -->
<rule pattern="[A-Za-z_]\w*">
<token type="NameClass"/>
<pop depth="1"/>
</rule>
<rule>
<pop depth="1"/>
</rule>
</state>
<state name="record">
<!-- Record definition -->
<rule pattern="[A-Za-z_]\w*">
<token type="NameClass"/>
<pop depth="1"/>
</rule>
<rule>
<pop depth="1"/>
</rule>
</state>
</rules>
</lexer>
@@ -0,0 +1,75 @@
<lexer>
<config>
<name>MoonBit</name>
<alias>moonbit</alias>
<alias>mbt</alias>
<filename>*.mbt</filename>
<ensure_nl>true</ensure_nl>
</config>
<rules>
<state name="root">
<rule pattern="#.*$"><token type="CommentPreproc"/></rule>
<rule pattern="//.*$"><token type="CommentSingle"/></rule>
<rule pattern="b?\&#x27;.*\&#x27;"><token type="Literal"/></rule>
<rule pattern="#\|.*$"><token type="LiteralString"/></rule>
<rule pattern="(b)(\&quot;)"><bygroups><token type="LiteralStringAffix"/><token type="LiteralString"/></bygroups><push state="string.inline"/></rule>
<rule pattern="&quot;"><token type="LiteralString"/><push state="string.inline"/></rule>
<rule pattern="\$\|"><token type="LiteralString"/><push state="string.multiline"/></rule>
<rule pattern="0(b|B)[01]+"><token type="LiteralNumberBin"/></rule>
<rule pattern="0(o|O)[0-7]+"><token type="LiteralNumberOct"/></rule>
<rule pattern="0(x|X)[0-9a-fA-F][0-9a-fA-F_]*\.[0-9a-fA-F][0-9a-fA-F_]*(P|p)(\+|\-)?[0-9][0-9]*"><token type="LiteralNumberFloat"/></rule>
<rule pattern="0(x|X)[0-9a-fA-F][0-9a-fA-F_]*\.?(P|p)(\+|\-)?[0-9][0-9]*"><token type="LiteralNumberFloat"/></rule>
<rule pattern="0(x|X)[0-9a-fA-F][0-9a-fA-F_]*\.[0-9a-fA-F][0-9a-fA-F_]*"><token type="LiteralNumberFloat"/></rule>
<rule pattern="0(x|X)[0-9a-fA-F][0-9a-fA-F_]*\."><token type="LiteralNumberFloat"/></rule>
<rule pattern="0(x|X)[0-9a-fA-F][0-9a-fA-F_]*"><token type="LiteralNumberHex"/></rule>
<rule pattern="\d(_|\d)*U?L"><token type="LiteralNumberIntegerLong"/></rule>
<rule pattern="\d(_|\d)*U?"><token type="LiteralNumberInteger"/></rule>
<rule pattern="\d+(.\d+)?"><token type="LiteralNumber"/></rule>
<rule pattern="(type|type!|enum|struct|trait|typealias|traitalias)\b"><token type="KeywordDeclaration"/></rule>
<rule pattern="(async|fn|const|let|mut|impl|with|derive|fnalias)\b"><token type="KeywordDeclaration"/></rule>
<rule pattern="(self|Self)\b"><token type="Keyword"/></rule>
<rule pattern="(guard|if|while|match|else|loop|for|in|is)\b"><token type="Keyword"/></rule>
<rule pattern="(return|break|continue)\b"><token type="Keyword"/></rule>
<rule pattern="(try|catch|raise|noraise)\b"><token type="Keyword"/></rule>
<rule pattern="\bas\b"><token type="Keyword"/></rule>
<rule pattern="(extern|pub|priv|pub\(all\)|pub\(readonly\)|pub\(open\)|test)\b"><token type="Keyword"/></rule>
<rule pattern="(true|false)\b"><token type="KeywordConstant"/></rule>
<rule pattern="(Eq|Compare|Hash|Show|Default|ToJson|FromJson)\b"><token type="NameBuiltin"/></rule>
<rule pattern="(Array|FixedArray|Int|Int64|UInt|UInt64|Option|Result|Byte|Bool|Unit|String|Float|Double)\b"><token type="NameBuiltin"/></rule>
<rule pattern="(\+|\-|\*|/|%|\|&gt;|&gt;&gt;|&lt;&lt;|\&amp;\&amp;|\|\||\&amp;|\||&lt;|&gt;|==)"><token type="Operator"/></rule>
<rule pattern="(not|lsl|lsr|asr|op_add|op_sub|op_div|op_mul|op_mod|\.\.\.)"><token type="OperatorWord"/></rule>
<rule pattern="@[A-Za-z][A-Za-z0-9_/]*\."><token type="NameNamespace"/></rule>
<rule pattern="([a-z][A-Za-z0-9_]*)(\s+)(as)(\s+)([a-z][A-Za-z0-9_]*)"><bygroups><token type="NameFunction"/><token type="TextWhitespace"/><token type="Keyword"/><token type="TextWhitespace"/><token type="NameFunction"/></bygroups></rule>
<rule pattern="([A-Za-z][A-Za-z0-9_]*)(::)([A-Za-z][A-Za-z0-9_]*)(\s+)(as)(\s+)([a-z][A-Za-z0-9_]*)"><bygroups><token type="NameClass"/><token type="Punctuation"/><token type="NameFunction"/><token type="TextWhitespace"/><token type="Keyword"/><token type="TextWhitespace"/><token type="NameFunction"/></bygroups></rule>
<rule pattern="([A-Za-z][A-Za-z0-9_]*)(::)([A-Za-z][A-Za-z0-9_]*)"><bygroups><token type="NameClass"/><token type="Punctuation"/><token type="NameFunction"/></bygroups></rule>
<rule pattern="([a-z][A-Za-z0-9_]*)(?=!?\()"><token type="NameFunction"/></rule>
<rule pattern="Error"><token type="NameException"/></rule>
<rule pattern="(=&gt;)|(-&gt;)|[\(\)\{\}\[\]:,\.=!?~;]"><token type="Punctuation"/></rule>
<rule pattern="[a-z][a-zA-Z0-9_]*"><token type="NameVariable"/></rule>
<rule pattern="[A-Z_][a-zA-Z0-9_]*"><token type="NameClass"/></rule>
<rule pattern="[\s]"><token type="TextWhitespace"/></rule>
</state>
<state name="string.inline">
<rule><include state="escape"/></rule>
<rule pattern="\\{"><token type="LiteralStringEscape"/><push state="interpolation"/></rule>
<rule pattern="&quot;"><token type="LiteralString"/><pop depth="1"/></rule>
<rule pattern="."><token type="LiteralStringDouble"/></rule>
</state>
<state name="string.multiline">
<rule><include state="escape"/></rule>
<rule pattern="\\{"><token type="LiteralStringEscape"/><push state="interpolation"/></rule>
<rule pattern="\Z"><token type="LiteralString"/><pop depth="1"/></rule>
<rule pattern="."><token type="LiteralString"/></rule>
</state>
<state name="interpolation">
<rule pattern="}"><token type="LiteralStringEscape"/><pop depth="1"/></rule>
<rule><include state="root"/></rule>
</state>
<state name="escape">
<rule pattern="\\[0\\tnrb\&quot;&#x27;]"><token type="LiteralStringEscape"/></rule>
<rule pattern="\\x[0-9a-fA-f]{2}"><token type="LiteralStringEscape"/></rule>
<rule pattern="\\u[0-9a-fA-f]{4}"><token type="LiteralStringEscape"/></rule>
<rule pattern="\\u[0-9a-fA-f]*"><token type="LiteralStringEscape"/></rule>
</state>
</rules>
</lexer>
+111 -40
View File
@@ -8,7 +8,13 @@
</config>
<rules>
<state name="string">
<rule pattern="\\(x[a-fA-F0-9]{2}|u[a-fA-F0-9]{4}|U[a-fA-F0-9]{6}|[nr\\t\&#39;&#34;])">
<rule pattern="\\x[0-9a-fA-F]{2}">
<token type="LiteralStringEscape"/>
</rule>
<rule pattern="\\u\{[0-9a-fA-F]+\}">
<token type="LiteralStringEscape"/>
</rule>
<rule pattern="\\[nrt&#39;&#34;\\]">
<token type="LiteralStringEscape"/>
</rule>
<rule pattern="[^\\&#34;\n]+">
@@ -18,99 +24,164 @@
<token type="LiteralString"/>
<pop depth="1"/>
</rule>
<rule pattern="\\">
<token type="LiteralString"/>
</rule>
</state>
<state name="quoted_ident">
<rule pattern="\\x[0-9a-fA-F]{2}">
<token type="LiteralStringEscape"/>
</rule>
<rule pattern="\\u\{[0-9a-fA-F]+\}">
<token type="LiteralStringEscape"/>
</rule>
<rule pattern="\\[nrt&#39;&#34;\\]">
<token type="LiteralStringEscape"/>
</rule>
<rule pattern="[^\\&#34;\n]+">
<token type="Name"/>
</rule>
<rule pattern="&#34;">
<token type="Name"/>
<pop depth="1"/>
</rule>
<rule pattern="\\">
<token type="Name"/>
</rule>
</state>
<state name="root">
<!-- Whitespace -->
<rule pattern="\n">
<token type="TextWhitespace"/>
</rule>
<rule pattern="\s+">
<token type="TextWhitespace"/>
</rule>
<rule pattern="//.*?\n">
<!-- Comments -->
<rule pattern="//!.*$">
<token type="CommentSpecial"/>
</rule>
<rule pattern="///.*$">
<token type="CommentSpecial"/>
</rule>
<rule pattern="//.*$">
<token type="CommentSingle"/>
</rule>
<rule pattern="(unreachable|continue|errdefer|suspend|return|resume|cancel|break|catch|async|await|defer|asm|try)\b">
<!-- Statement keywords -->
<rule pattern="(break|return|continue|asm|defer|errdefer|unreachable|try|catch|suspend|resume|nosuspend)\b">
<token type="Keyword"/>
</rule>
<rule pattern="(threadlocal|linksection|allowzero|stdcallcc|volatile|comptime|noalias|nakedcc|inline|export|packed|extern|align|const|pub|var)\b">
<!-- Storage/reserved keywords -->
<rule pattern="(const|var|extern|packed|export|pub|noalias|inline|noinline|comptime|volatile|allowzero|align|addrspace|linksection|threadlocal|callconv)\b">
<token type="KeywordReserved"/>
</rule>
<rule pattern="(struct|union|error|enum)\b">
<!-- Structure keywords -->
<rule pattern="(struct|enum|union|error|opaque)\b">
<token type="Keyword"/>
</rule>
<!-- Repeat keywords -->
<rule pattern="(while|for)\b">
<token type="Keyword"/>
</rule>
<rule pattern="(comptime_float|comptime_int|c_longdouble|c_ulonglong|c_longlong|c_voidi8|noreturn|c_ushort|anyerror|promise|c_short|c_ulong|c_uint|c_long|isize|c_int|usize|void|f128|i128|type|bool|u128|u16|f64|f32|u64|i16|f16|i32|u32|i64|u8|i0|u0)\b">
<!-- Type keywords -->
<rule pattern="(bool|void|noreturn|type|anyerror|anyopaque|f16|f32|f64|f80|f128|i8|u8|i16|u16|i32|u32|i64|u64|i128|u128|isize|usize|comptime_int|comptime_float|c_char|c_short|c_ushort|c_int|c_uint|c_long|c_ulong|c_longlong|c_ulonglong|c_longdouble)\b">
<token type="KeywordType"/>
</rule>
<rule pattern="(undefined|false|true|null)\b">
<!-- Constant keywords -->
<rule pattern="(true|false|null|undefined)\b">
<token type="KeywordConstant"/>
</rule>
<rule pattern="(switch|orelse|else|and|if|or)\b">
<!-- Conditional keywords -->
<rule pattern="(if|else|switch|and|or|orelse)\b">
<token type="Keyword"/>
</rule>
<rule pattern="(usingnamespace|test|fn)\b">
<!-- Other keywords -->
<rule pattern="(fn|test|anyframe|anytype)\b">
<token type="Keyword"/>
</rule>
<rule pattern="0x[0-9a-fA-F]+\.[0-9a-fA-F]+([pP][\-+]?[0-9a-fA-F]+)?">
<!-- Hex float -->
<rule pattern="0x[0-9a-fA-F][0-9a-fA-F_]*(?:\.[0-9a-fA-F][0-9a-fA-F_]*(?:[pP][-+]?[0-9][0-9_]*)?|[pP][-+]?[0-9][0-9_]*)">
<token type="LiteralNumberFloat"/>
</rule>
<rule pattern="0x[0-9a-fA-F]+\.?[pP][\-+]?[0-9a-fA-F]+">
<!-- Decimal float -->
<rule pattern="[0-9][0-9_]*(?:\.[0-9][0-9_]*(?:[eE][-+]?[0-9][0-9_]*)?|[eE][-+]?[0-9][0-9_]*)">
<token type="LiteralNumberFloat"/>
</rule>
<rule pattern="[0-9]+\.[0-9]+([eE][-+]?[0-9]+)?">
<token type="LiteralNumberFloat"/>
</rule>
<rule pattern="[0-9]+\.?[eE][-+]?[0-9]+">
<token type="LiteralNumberFloat"/>
</rule>
<rule pattern="0b(?:_?[01])+">
<!-- Binary -->
<rule pattern="0b[01][01_]*">
<token type="LiteralNumberBin"/>
</rule>
<rule pattern="0o(?:_?[0-7])+">
<!-- Octal -->
<rule pattern="0o[0-7][0-7_]*">
<token type="LiteralNumberOct"/>
</rule>
<rule pattern="0x(?:_?[0-9a-fA-F])+">
<!-- Hex integer -->
<rule pattern="0x[0-9a-fA-F][0-9a-fA-F_]*">
<token type="LiteralNumberHex"/>
</rule>
<rule pattern="(?:_?[0-9])+">
<!-- Decimal integer -->
<rule pattern="[0-9][0-9_]*">
<token type="LiteralNumberInteger"/>
</rule>
<rule pattern="\b[A-Za-z_]\w*(?=\s*\()">
<token type="NameFunction"/>
<!-- Quoted identifier -->
<rule pattern="@&#34;">
<token type="Name"/>
<push state="quoted_ident"/>
</rule>
<rule pattern="@[a-zA-Z_]\w*">
<!-- Builtin -->
<rule pattern="@[a-zA-Z_][a-zA-Z0-9_]*">
<token type="NameBuiltin"/>
</rule>
<rule pattern="[a-zA-Z_]\w*">
<!-- Function call -->
<rule pattern="[a-zA-Z_][a-zA-Z0-9_]*(?=\s*\()">
<token type="NameFunction"/>
</rule>
<!-- Identifier -->
<rule pattern="[a-zA-Z_][a-zA-Z0-9_]*">
<token type="Name"/>
</rule>
<rule pattern="\&#39;\\\&#39;\&#39;">
<token type="LiteralStringEscape"/>
</rule>
<rule pattern="\&#39;\\(|x[a-fA-F0-9]{2}|u[a-fA-F0-9]{4}|U[a-fA-F0-9]{6}|[nr\\t\&#39;&#34;])\&#39;">
<token type="LiteralStringEscape"/>
</rule>
<rule pattern="\&#39;[^\\\&#39;]\&#39;">
<token type="LiteralString"/>
<!-- Character literal -->
<rule pattern="&#39;(?:\\x[0-9a-fA-F]{2}|\\u\{[0-9a-fA-F]+\}|\\[nrt&#39;&#34;\\]|[^\\&#39;\n])&#39;">
<token type="LiteralStringChar"/>
</rule>
<!-- Multiline string -->
<rule pattern="\\\\[^\n]*">
<token type="LiteralStringHeredoc"/>
</rule>
<rule pattern="c\\\\[^\n]*">
<token type="LiteralStringHeredoc"/>
</rule>
<rule pattern="c?&#34;">
<!-- String start -->
<rule pattern="&#34;">
<token type="LiteralString"/>
<push state="string"/>
</rule>
<rule pattern="[+%=&gt;&lt;|^!?/\-*&amp;~:]">
<!-- 4-char operators -->
<rule pattern="&lt;&lt;\|=">
<token type="Operator"/>
</rule>
<rule pattern="[{}()\[\],.;]">
<!-- 3-char operators -->
<rule pattern="(?:\+%=|-%=|\*%=|\+\|=|-\|=|\*\|=|&lt;&lt;=|&gt;&gt;=|&lt;&lt;\|)">
<token type="Operator"/>
</rule>
<!-- 2-char operators -->
<rule pattern="(?:\+\+|\*\*|\|\||&lt;&lt;|&gt;&gt;|\+%|-%|\*%|\+\||-\||\*\||==|!=|&lt;=|&gt;=|\+=|-=|\*=|/=|%=|&amp;=|\|=|\^=|\.\*|\.\?|\.\.)">
<token type="Operator"/>
</rule>
<!-- 1-char operators -->
<rule pattern="[+\-*/%&amp;|^~!&lt;&gt;=]">
<token type="Operator"/>
</rule>
<!-- Punctuation -->
<rule pattern="\.\.\.">
<token type="Punctuation"/>
</rule>
<rule pattern="=&gt;">
<token type="Punctuation"/>
</rule>
<rule pattern="-&gt;">
<token type="Punctuation"/>
</rule>
<rule pattern="[{}()\[\],.;:?]">
<token type="Punctuation"/>
</rule>
</state>
</rules>
</lexer>
</lexer>
+168
View File
@@ -0,0 +1,168 @@
package lexers
import (
. "github.com/alecthomas/chroma/v2" // nolint
)
// Markless lexer.
var Markless = Register(MustNewLexer(
&Config{
Name: "Markless",
Aliases: []string{"mess"},
Filenames: []string{"*.mess", "*.markless"},
MimeTypes: []string{"text/x-markless"},
},
marklessRules,
))
func marklessRules() Rules {
return Rules{
"root": {
Include("block"),
},
// Block directives
"block": {
Include("header"),
Include("ordered-list"),
Include("unordered-list"),
Include("code-block"),
Include("blockquote"),
Include("blockquote-header"),
Include("align"),
Include("comment"),
Include("instruction"),
Include("embed"),
Include("footnote"),
Include("horizontal-rule"),
Include("paragraph"),
},
"header": {
{`(# )(.*)$`, ByGroups(Keyword, GenericHeading), Push("inline")},
{`(##+)(.*)$`, ByGroups(Keyword, GenericSubheading), Push("inline")},
},
"ordered-list": {
{`([0-9]+\.)`, Keyword, nil},
},
"unordered-list": {
{`(- )`, Keyword, nil},
},
"code-block": {
{`(::+)( *)(\w*)([^\n]*)(\n)([\w\W]*?)(^\1$)`, UsingByGroup(3, 6, Keyword, TextWhitespace, NameFunction, String, TextWhitespace, Text, Keyword), nil},
},
"blockquote": {
{`(\| )(.*)$`, ByGroups(Keyword, GenericInserted), nil},
},
"blockquote-header": {
{`(~ )([^|\n]+)(\| )(.*?\n)`, ByGroups(Keyword, NameEntity, Keyword, GenericInserted), Push("inline-blockquote")},
{`(~ )(.*)$`, ByGroups(Keyword, NameEntity), nil},
},
"inline-blockquote": {
{`^( +)(\| )(.*$)`, ByGroups(TextWhitespace, Keyword, GenericInserted), nil},
Default(Pop(1)),
},
"align": {
{`(\|\|)|(\|<)|(\|>)|(><)`, Keyword, nil},
},
"comment": {
{`(;[; ]).*?$`, CommentSingle, nil},
},
"instruction": {
{`(! )([^ ]+)(.+?)$`, ByGroups(Keyword, NameFunction, NameVariable), nil},
},
"embed": {
{`(\[ )([^ ]+)( )([^,]+)`, ByGroups(Keyword, NameFunction, TextWhitespace, String), Push("embed-options")},
},
"embed-options": {
{`\\.`, Text, nil},
{`,`, Punctuation, nil},
{`\]?$`, Keyword, Pop(1)},
// Generic key or key/value pair
{`( *)([^, \]]+)([^,\]]+)?`, ByGroups(TextWhitespace, NameFunction, String), nil},
{`.`, Text, nil},
},
"footnote": {
{`(\[)([0-9]+)(\])`, ByGroups(Keyword, NameVariable, Keyword), Push("inline")},
},
"horizontal-rule": {
{`(==+)$`, LiteralOther, nil},
},
"paragraph": {
{` *`, TextWhitespace, Push("inline")},
},
// Inline directives
"inline": {
Include("escapes"),
Include("dashes"),
Include("newline"),
Include("italic"),
Include("underline"),
Include("bold"),
Include("strikethrough"),
Include("code"),
Include("compound"),
Include("footnote-reference"),
Include("subtext"),
Include("subtext"),
Include("url"),
{`.`, Text, nil},
{`\n`, TextWhitespace, Pop(1)},
},
"escapes": {
{`\\.`, Text, nil},
},
"dashes": {
{`-{2,3}`, TextPunctuation, nil},
},
"newline": {
{`-/-`, TextWhitespace, nil},
},
"italic": {
{`(//)(.*?)(\1)`, ByGroups(Keyword, GenericEmph, Keyword), nil},
},
"underline": {
{`(__)(.*?)(\1)`, ByGroups(Keyword, GenericUnderline, Keyword), nil},
},
"bold": {
{`(\*\*)(.*?)(\1)`, ByGroups(Keyword, GenericStrong, Keyword), nil},
},
"strikethrough": {
{`(<-)(.*?)(->)`, ByGroups(Keyword, GenericDeleted, Keyword), nil},
},
"code": {
{"(``+)(.*?)(\\1)", ByGroups(Keyword, LiteralStringBacktick, Keyword), nil},
},
"compound": {
{`(''+)(.*?)(''\()`, ByGroups(Keyword, UsingSelf("inline"), Keyword), Push("compound-options")},
},
"compound-options": {
{`\\.`, Text, nil},
{`,`, Punctuation, nil},
{`\)`, Keyword, Pop(1)},
// Hex Color
{` *#[0-9A-Fa-f]{3,6} *`, LiteralNumberHex, nil},
// Named Color
{` *(indian-red|light-coral|salmon|dark-salmon|light-salmon|crimson|red|firebrick|dark-red|pink|light-pink|hot-pink|deep-pink|medium-violet-red|pale-violet-red|coral|tomato|orange-red|dark-orange|orange|gold|yellow|light-yellow|lemon-chiffon|light-goldenrod-yellow|papayawhip|moccasin|peachpuff|pale-goldenrod|khaki|dark-khaki|lavender|thistle|plum|violet|orchid|fuchsia|magenta|medium-orchid|medium-purple|rebecca-purple|blue-violet|dark-violet|dark-orchid|dark-magenta|purple|indigo|slate-blue|dark-slate-blue|medium-slate-blue|green-yellow|chartreuse|lawn-green|lime|lime-green|pale-green|light-green|medium-spring-green|spring-green|medium-sea-green|sea-green|forest-green|green|dark-green|yellow-green|olive-drab|olive|dark-olive-green|medium-aquamarine|dark-sea-green|light-sea-green|dark-cyan|teal|aqua|cyan|light-cyan|pale-turquoise|aquamarine|turquoise|medium-turquoise|dark-turquoise|cadet-blue|steel-blue|light-steel-blue|powder-blue|light-blue|sky-blue|light-sky-blue|deep-sky-blue|dodger-blue|cornflower-blue|royal-blue|blue|medium-blue|dark-blue|navy|midnight-blue|cornsilk|blanched-almond|bisque|navajo-white|wheat|burlywood|tan|rosy-brown|sandy-brown|goldenrod|dark-goldenrod|peru|chocolate|saddle-brown|sienna|brown|maroon|white|snow|honeydew|mintcream|azure|alice-blue|ghost-white|white-smoke|seashell|beige|oldlace|floral-white|ivory|antique-white|linen|lavenderblush|mistyrose|gainsboro|light-gray|silver|dark-gray|gray|dim-gray|light-slate-gray|slate-gray|dark-slate-gray) *`, LiteralOther, nil},
// Named size
{` *(microscopic|tiny|small|normal|big|large|huge|gigantic) *`, NameTag, nil},
// Options
{` *(bold|italic|underline|strikethrough|subtext|supertext|spoiler) *`, NameBuiltin, nil},
// URL. Note the missing ) and , in the match.
{` *\w[-\w+.]*://[\w$\-_.+!*'(&/:;=?@z%#\\]+ *`, String, nil},
// Generic key or key/value pair
{`( *)([^, )]+)( [^,)]+)?`, ByGroups(TextWhitespace, NameFunction, String), nil},
{`.`, Text, nil},
},
"footnote-reference": {
{`(\[)([0-9]+)(\])`, ByGroups(Keyword, NameVariable, Keyword), nil},
},
"subtext": {
{`(v\()(.*?)(\))`, ByGroups(Keyword, UsingSelf("inline"), Keyword), nil},
},
"supertext": {
{`(\^\()(.*?)(\))`, ByGroups(Keyword, UsingSelf("inline"), Keyword), nil},
},
"url": {
{`\w[-\w+.]*://[\w\$\-_.+!*'()&,/:;=?@z%#\\]+`, String, nil},
},
}
}
@@ -101,7 +101,7 @@
<entry type="GenericTraceback" style="#c55858"/>
<!-- Punctuation -->
<entry type="Punctuation" style="#bdbdbd"/>
<entry type="Punctuation" style="#c17ac8"/>
<entry type="Text" style="#bdbdbd"/>
<entry type="TextWhitespace" style="#bdbdbd"/>
</style>
+1 -1
View File
@@ -101,7 +101,7 @@
<entry type="GenericTraceback" style="#ff6767"/>
<!-- Punctuation -->
<entry type="Punctuation" style="#edecee"/>
<entry type="Punctuation" style="#f694ff"/>
<entry type="Text" style="#edecee"/>
<entry type="TextWhitespace" style="#edecee"/>
</style>
+1 -1
View File
@@ -1,6 +1,6 @@
// Package chi is a small, idiomatic and composable router for building HTTP services.
//
// chi requires Go 1.14 or newer.
// chi supports the four most recent major versions of Go.
//
// Example:
//
+2 -7
View File
@@ -2,6 +2,7 @@ package middleware
import (
"net/http"
"slices"
"strings"
)
@@ -29,13 +30,7 @@ func contentEncoding(ce string, charsets ...string) bool {
_, ce = split(strings.ToLower(ce), ";")
_, ce = split(ce, "charset=")
ce, _ = split(ce, ";")
for _, c := range charsets {
if ce == c {
return true
}
}
return false
return slices.Contains(charsets, ce)
}
// Split a string in two parts, cleaning any whitespace.
+3 -3
View File
@@ -25,7 +25,7 @@ const RequestIDKey ctxKeyRequestID = 0
var RequestIDHeader = "X-Request-Id"
var prefix string
var reqid uint64
var reqid atomic.Uint64
// A quick note on the statistics here: we're trying to calculate the chance that
// two randomly generated base62 prefixes will collide. We use the formula from
@@ -69,7 +69,7 @@ func RequestID(next http.Handler) http.Handler {
ctx := r.Context()
requestID := r.Header.Get(RequestIDHeader)
if requestID == "" {
myid := atomic.AddUint64(&reqid, 1)
myid := reqid.Add(1)
requestID = fmt.Sprintf("%s-%06d", prefix, myid)
}
ctx = context.WithValue(ctx, RequestIDKey, requestID)
@@ -92,5 +92,5 @@ func GetReqID(ctx context.Context) string {
// NextRequestID generates the next request ID in the sequence.
func NextRequestID() uint64 {
return atomic.AddUint64(&reqid, 1)
return reqid.Add(1)
}
+1
View File
@@ -79,6 +79,7 @@ func (hr HeaderRouter) Handler(next http.Handler) http.Handler {
if len(hr) == 0 {
// skip if no routes set
next.ServeHTTP(w, r)
return
}
// find first matching header route, and continue
+9 -2
View File
@@ -47,15 +47,22 @@ func RedirectSlashes(next http.Handler) http.Handler {
} else {
path = r.URL.Path
}
if len(path) > 1 && path[len(path)-1] == '/' {
// Trim all leading and trailing slashes (e.g., "//evil.com", "/some/path//")
path = "/" + strings.Trim(path, "/")
// Normalize backslashes to forward slashes to prevent "/\evil.com" style redirects
// that some clients may interpret as protocol-relative.
path = strings.ReplaceAll(path, `\`, `/`)
// Collapse leading/trailing slashes and force a single leading slash.
path := "/" + strings.Trim(path, "/")
if r.URL.RawQuery != "" {
path = fmt.Sprintf("%s?%s", path, r.URL.RawQuery)
}
http.Redirect(w, r, path, 301)
return
}
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
+4 -2
View File
@@ -467,8 +467,10 @@ func (mx *Mux) routeHTTP(w http.ResponseWriter, r *http.Request) {
// Find the route
if _, _, h := mx.tree.FindRoute(rctx, method, routePath); h != nil {
if supportsPathValue {
setPathValue(rctx, r)
// Set http.Request path values from our request context
for i, key := range rctx.URLParams.Keys {
value := rctx.URLParams.Values[i]
r.SetPathValue(key, value)
}
if supportsPattern {
setPattern(rctx, r)
-21
View File
@@ -1,21 +0,0 @@
//go:build go1.22 && !tinygo
// +build go1.22,!tinygo
package chi
import "net/http"
// supportsPathValue is true if the Go version is 1.22 and above.
//
// If this is true, `net/http.Request` has methods `SetPathValue` and `PathValue`.
const supportsPathValue = true
// setPathValue sets the path values in the Request value
// based on the provided request context.
func setPathValue(rctx *Context, r *http.Request) {
for i, key := range rctx.URLParams.Keys {
value := rctx.URLParams.Values[i]
r.SetPathValue(key, value)
}
}
-19
View File
@@ -1,19 +0,0 @@
//go:build !go1.22 || tinygo
// +build !go1.22 tinygo
package chi
import "net/http"
// supportsPathValue is true if the Go version is 1.22 and above.
//
// If this is true, `net/http.Request` has methods `SetPathValue` and `PathValue`.
const supportsPathValue = false
// setPathValue sets the path values in the Request value
// based on the provided request context.
//
// setPathValue is only supported in Go 1.22 and above so
// this is just a blank function so that it compiles.
func setPathValue(rctx *Context, r *http.Request) {
}
+7 -14
View File
@@ -71,6 +71,7 @@ func RegisterMethod(method string) {
}
mt := methodTyp(2 << n)
methodMap[method] = mt
reverseMethodMap[mt] = method
mALL |= mt
}
@@ -328,7 +329,7 @@ func (n *node) replaceChild(label, tail byte, child *node) {
func (n *node) getEdge(ntyp nodeTyp, label, tail byte, prefix string) *node {
nds := n.children[ntyp]
for i := 0; i < len(nds); i++ {
for i := range nds {
if nds[i].label == label && nds[i].tail == tail {
if ntyp == ntRegexp && nds[i].prefix != prefix {
continue
@@ -429,9 +430,7 @@ func (n *node) findRoute(rctx *Context, method methodTyp, path string) *node {
}
// serially loop through each node grouped by the tail delimiter
for idx := 0; idx < len(nds); idx++ {
xn = nds[idx]
for _, xn = range nds {
// label for param nodes is the delimiter byte
p := strings.IndexByte(xsearch, xn.tail)
@@ -770,20 +769,14 @@ func patParamKeys(pattern string) []string {
}
}
// longestPrefix finds the length of the shared prefix
// of two strings
func longestPrefix(k1, k2 string) int {
max := len(k1)
if l := len(k2); l < max {
max = l
}
var i int
for i = 0; i < max; i++ {
// longestPrefix finds the length of the shared prefix of two strings
func longestPrefix(k1, k2 string) (i int) {
for i = 0; i < min(len(k1), len(k2)); i++ {
if k1[i] != k2[i] {
break
}
}
return i
return
}
type nodes []*node
+2
View File
@@ -19,6 +19,8 @@ linters:
- unconvert
- unparam
- unused
- modernize
- testifylint
settings:
goconst:
min-len: 2
+141
View File
@@ -143,6 +143,22 @@ The `Rewrite` middleware is designed to rewrite the URL path based on a given ru
For example, `Rewrite("^/sites/(.*)/settings/$", "/sites/settings/$1")` will change request's URL from `/sites/id1/settings/` to `/sites/settings/id1`
### CleanPath middleware
Cleans double slashes from URL path. For example, requests to `/users//1` or `//users////1` will be cleaned to `/users/1` before routing. Trailing slashes are preserved: `/api//v1/` becomes `/api/v1/`. Note: dot segments (`.` and `..`) are intentionally not cleaned to preserve routing semantics.
```go
router.Use(rest.CleanPath)
```
### StripSlashes middleware
Removes trailing slashes from URL path. For example, `/users/` becomes `/users`. The root path `/` is preserved.
```go
router.Use(rest.StripSlashes)
```
### NoCache middleware
Sets a number of HTTP headers to prevent a router (handler's) response from being cached by an upstream proxy and/or client.
@@ -166,6 +182,131 @@ RealIP is a middleware that sets a http.Request's RemoteAddr to the results of p
Only public IPs are accepted from headers; private/loopback/link-local IPs are skipped. This makes the middleware compatible with CDN setups like Cloudflare where the leftmost IP in `X-Forwarded-For` is the actual client.
### CORS middleware
Handles Cross-Origin Resource Sharing, allowing controlled access from different origins.
```go
// allow all origins (default)
router.Use(rest.CORS())
// specific origins with credentials
router.Use(rest.CORS(
rest.CorsAllowedOrigins("https://app.example.com", "https://admin.example.com"),
rest.CorsAllowCredentials(true),
rest.CorsMaxAge(86400),
))
// full configuration
router.Use(rest.CORS(
rest.CorsAllowedOrigins("https://app.example.com"),
rest.CorsAllowedMethods("GET", "POST", "PUT", "DELETE"),
rest.CorsAllowedHeaders("Authorization", "Content-Type", "X-Custom-Header"),
rest.CorsExposedHeaders("X-Request-Id", "X-Total-Count"),
rest.CorsAllowCredentials(true),
rest.CorsMaxAge(3600),
))
```
Features:
- Automatic preflight (OPTIONS) handling
- Origin validation with case-insensitive matching
- Credentials support (reflects origin instead of `*`)
- Configurable cache duration for preflight results
Available options:
- `CorsAllowedOrigins(origins...)` - allowed origins (default: `*`)
- `CorsAllowedMethods(methods...)` - allowed HTTP methods (default: GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD)
- `CorsAllowedHeaders(headers...)` - allowed request headers (default: Accept, Content-Type, Authorization, X-Requested-With)
- `CorsExposedHeaders(headers...)` - headers exposed to client
- `CorsAllowCredentials(bool)` - enable credentials (cookies, auth headers)
- `CorsMaxAge(seconds)` - preflight cache duration
### Secure middleware
Adds security headers to responses. By default sets: `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy`, `X-XSS-Protection`, and `Strict-Transport-Security` (for HTTPS only).
```go
// with sensible defaults
router.Use(rest.Secure())
// with full security headers for web apps (adds CSP and Permissions-Policy)
router.Use(rest.Secure(rest.SecAllHeaders()))
// with custom options
router.Use(rest.Secure(
rest.SecFrameOptions("SAMEORIGIN"),
rest.SecReferrerPolicy("no-referrer"),
rest.SecHSTS(86400, true, true),
rest.SecContentSecurityPolicy("default-src 'self'"),
rest.SecPermissionsPolicy("geolocation=(), camera=()"),
))
```
Default headers:
- `X-Frame-Options: DENY` - prevents clickjacking
- `X-Content-Type-Options: nosniff` - prevents MIME-type sniffing
- `Referrer-Policy: strict-origin-when-cross-origin` - controls referrer information
- `X-XSS-Protection: 1; mode=block` - enables XSS filtering (legacy browsers)
- `Strict-Transport-Security: max-age=31536000; includeSubDomains` - enforces HTTPS (only sent over HTTPS)
Available options:
- `SecFrameOptions(value)` - set X-Frame-Options (DENY, SAMEORIGIN)
- `SecContentTypeNosniff(enable)` - enable/disable nosniff
- `SecReferrerPolicy(policy)` - set Referrer-Policy
- `SecContentSecurityPolicy(policy)` - set Content-Security-Policy
- `SecPermissionsPolicy(policy)` - set Permissions-Policy
- `SecHSTS(maxAge, includeSubdomains, preload)` - configure HSTS
- `SecXSSProtection(value)` - set X-XSS-Protection
- `SecAllHeaders()` - convenience option that sets CSP and Permissions-Policy with restrictive defaults
### CSRF middleware
Provides Cross-Site Request Forgery protection using modern browser Fetch metadata headers (`Sec-Fetch-Site`, `Origin`). For Go 1.25+, this wraps the stdlib's `http.CrossOriginProtection`. For earlier versions, a compatible custom implementation is used.
```go
// basic protection
protection := rest.NewCrossOriginProtection()
router.Use(protection.Handler)
// with trusted origins for cross-origin requests
protection := rest.NewCrossOriginProtection()
if err := protection.AddTrustedOrigin("https://mobile.example.com"); err != nil {
log.Fatal(err)
}
if err := protection.AddTrustedOrigin("https://admin.example.com"); err != nil {
log.Fatal(err)
}
router.Use(protection.Handler)
// with bypass patterns for webhooks or OAuth
protection := rest.NewCrossOriginProtection()
protection.AddBypassPattern("/api/webhook")
protection.AddBypassPattern("/oauth/")
router.Use(protection.Handler)
// with custom deny handler
protection := rest.NewCrossOriginProtection()
protection.SetDenyHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "CSRF validation failed", http.StatusForbidden)
}))
router.Use(protection.Handler)
```
How it works:
- Safe methods (GET, HEAD, OPTIONS) are always allowed
- Checks `Sec-Fetch-Site` header for "same-origin" or "none"
- Falls back to comparing `Origin` header with request `Host`
- Requests without these headers are assumed same-origin (non-browser clients)
Available methods:
- `NewCrossOriginProtection()` - creates new CSRF protection middleware
- `AddTrustedOrigin(origin)` - adds origin allowed for cross-origin requests (format: "scheme://host[:port]")
- `AddBypassPattern(pattern)` - adds URL pattern that bypasses protection (for webhooks, OAuth, etc.)
- `SetDenyHandler(handler)` - sets custom handler for rejected requests (default: 403 Forbidden)
- `Check(request)` - manually validates a request, returns error if blocked
- `Handler(handler)` - wraps an http.Handler with CSRF protection
### Maybe middleware
Maybe middleware allows changing the flow of the middleware stack execution depending on the return
+1 -4
View File
@@ -156,10 +156,7 @@ func (b *Benchmarks) Stats(interval time.Duration) BenchmarkStats {
}
// ensure we calculate rate based on actual interval
actualInterval := fnInterval.Sub(stInterval)
if actualInterval < time.Second {
actualInterval = time.Second
}
actualInterval := max(fnInterval.Sub(stInterval), time.Second)
return BenchmarkStats{
Requests: requests,
+180
View File
@@ -0,0 +1,180 @@
package rest
import (
"net/http"
"strconv"
"strings"
)
// CORSConfig defines CORS middleware configuration.
// Use CorsOpt functions to customize.
type CORSConfig struct {
// AllowedOrigins is a list of origins that may access the resource.
// use "*" to allow all origins (not recommended with credentials).
// default: ["*"]
AllowedOrigins []string
// AllowedMethods is a list of methods the client is allowed to use.
// default: GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD
AllowedMethods []string
// AllowedHeaders is a list of headers the client is allowed to send.
// default: Accept, Content-Type, Authorization, X-Requested-With
AllowedHeaders []string
// ExposedHeaders is a list of headers that are safe to expose to the client.
// default: empty
ExposedHeaders []string
// AllowCredentials indicates whether the request can include credentials.
// when true, AllowedOrigins cannot be "*" (browser security restriction).
// default: false
AllowCredentials bool
// MaxAge indicates how long (in seconds) the results of a preflight can be cached.
// default: 0 (no caching)
MaxAge int
}
// CorsOpt is a functional option for CORSConfig
type CorsOpt func(*CORSConfig)
// defaultCORSConfig returns config with sensible defaults
func defaultCORSConfig() CORSConfig {
return CORSConfig{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"},
AllowedHeaders: []string{"Accept", "Content-Type", "Authorization", "X-Requested-With"},
ExposedHeaders: []string{},
AllowCredentials: false,
MaxAge: 0,
}
}
// CorsAllowedOrigins sets the list of allowed origins.
// Use "*" to allow all origins (not recommended with credentials).
func CorsAllowedOrigins(origins ...string) CorsOpt {
return func(c *CORSConfig) {
c.AllowedOrigins = origins
}
}
// CorsAllowedMethods sets the list of allowed HTTP methods.
func CorsAllowedMethods(methods ...string) CorsOpt {
return func(c *CORSConfig) {
c.AllowedMethods = methods
}
}
// CorsAllowedHeaders sets the list of allowed request headers.
func CorsAllowedHeaders(headers ...string) CorsOpt {
return func(c *CORSConfig) {
c.AllowedHeaders = headers
}
}
// CorsExposedHeaders sets the list of headers exposed to the client.
func CorsExposedHeaders(headers ...string) CorsOpt {
return func(c *CORSConfig) {
c.ExposedHeaders = headers
}
}
// CorsAllowCredentials enables or disables credentials.
// When true, AllowedOrigins cannot be "*".
func CorsAllowCredentials(allow bool) CorsOpt {
return func(c *CORSConfig) {
c.AllowCredentials = allow
}
}
// CorsMaxAge sets how long (in seconds) preflight results can be cached.
func CorsMaxAge(seconds int) CorsOpt {
return func(c *CORSConfig) {
c.MaxAge = seconds
}
}
// CORS is middleware that handles Cross-Origin Resource Sharing.
// It handles preflight OPTIONS requests and sets appropriate headers.
// By default allows all origins with common methods and headers.
func CORS(opts ...CorsOpt) func(http.Handler) http.Handler {
cfg := defaultCORSConfig()
for _, opt := range opts {
opt(&cfg)
}
// pre-compute joined strings for performance
methodsStr := strings.Join(cfg.AllowedMethods, ", ")
headersStr := strings.Join(cfg.AllowedHeaders, ", ")
exposedStr := strings.Join(cfg.ExposedHeaders, ", ")
// check if wildcard is used
allowAll := len(cfg.AllowedOrigins) == 1 && cfg.AllowedOrigins[0] == "*"
// build origin lookup for O(1) check (only when not allowing all)
var originSet map[string]bool
if !allowAll {
originSet = make(map[string]bool, len(cfg.AllowedOrigins))
for _, o := range cfg.AllowedOrigins {
originSet[strings.ToLower(o)] = true
}
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
// no origin header means same-origin or non-browser request
if origin == "" {
next.ServeHTTP(w, r)
return
}
// check if origin is allowed
var allowed bool
if allowAll {
allowed = true
} else {
allowed = originSet[strings.ToLower(origin)]
}
if !allowed {
// origin not allowed, proceed without CORS headers
next.ServeHTTP(w, r)
return
}
// set Vary header for caching
w.Header().Add("Vary", "Origin")
// set allowed origin
if allowAll && !cfg.AllowCredentials {
w.Header().Set("Access-Control-Allow-Origin", "*")
} else {
// reflect the specific origin (required for credentials)
w.Header().Set("Access-Control-Allow-Origin", origin)
}
// set credentials header if enabled
if cfg.AllowCredentials {
w.Header().Set("Access-Control-Allow-Credentials", "true")
}
// handle preflight request
if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" {
// preflight request
w.Header().Set("Access-Control-Allow-Methods", methodsStr)
w.Header().Set("Access-Control-Allow-Headers", headersStr)
if cfg.MaxAge > 0 {
w.Header().Set("Access-Control-Max-Age", strconv.Itoa(cfg.MaxAge))
}
w.WriteHeader(http.StatusNoContent)
return
}
// actual request - set exposed headers
if exposedStr != "" {
w.Header().Set("Access-Control-Expose-Headers", exposedStr)
}
next.ServeHTTP(w, r)
})
}
}
+207
View File
@@ -0,0 +1,207 @@
//go:build !go1.25
package rest
import (
"fmt"
"net/http"
"net/url"
"strings"
"sync"
)
// CrossOriginProtection provides CSRF protection using modern browser Fetch metadata.
// It validates requests using Sec-Fetch-Site and Origin headers, rejecting cross-origin
// state-changing requests. Safe methods (GET, HEAD, OPTIONS) are always allowed.
//
// For Go 1.25+, this wraps the stdlib http.CrossOriginProtection.
// For earlier versions, it provides an equivalent custom implementation.
type CrossOriginProtection struct {
mu sync.RWMutex
trustedOrigins map[string]bool
bypassPatterns []string
denyHandler http.Handler
}
// NewCrossOriginProtection creates a new CSRF protection middleware.
func NewCrossOriginProtection() *CrossOriginProtection {
return &CrossOriginProtection{
trustedOrigins: make(map[string]bool),
}
}
// AddTrustedOrigin adds an origin that should be allowed to make cross-origin requests.
// The origin must be in the format "scheme://host" or "scheme://host:port".
// Returns an error if the origin format is invalid.
func (c *CrossOriginProtection) AddTrustedOrigin(origin string) error {
u, err := url.Parse(origin)
if err != nil {
return fmt.Errorf("invalid origin: %w", err)
}
if u.Scheme == "" || u.Host == "" {
return fmt.Errorf("origin must have scheme and host: %s", origin)
}
if u.Path != "" && u.Path != "/" {
return fmt.Errorf("origin must not have path: %s", origin)
}
if u.RawQuery != "" || u.Fragment != "" {
return fmt.Errorf("origin must not have query or fragment: %s", origin)
}
normalized := strings.ToLower(u.Scheme) + "://" + strings.ToLower(u.Host)
c.mu.Lock()
c.trustedOrigins[normalized] = true
c.mu.Unlock()
return nil
}
// AddBypassPattern adds a URL pattern that should bypass CSRF protection.
// Patterns follow the same syntax as http.ServeMux (e.g., "/api/webhook", "/oauth/").
// Use sparingly and only for endpoints that have alternative authentication.
func (c *CrossOriginProtection) AddBypassPattern(pattern string) {
c.mu.Lock()
c.bypassPatterns = append(c.bypassPatterns, pattern)
c.mu.Unlock()
}
// SetDenyHandler sets a custom handler for rejected requests.
// If not set, rejected requests receive a 403 Forbidden response.
func (c *CrossOriginProtection) SetDenyHandler(h http.Handler) {
c.mu.Lock()
c.denyHandler = h
c.mu.Unlock()
}
// Check validates a request against CSRF protection rules.
// Returns nil if the request is allowed, or an error describing why it was rejected.
func (c *CrossOriginProtection) Check(r *http.Request) error {
// safe methods are always allowed
if isSafeMethod(r.Method) {
return nil
}
// check bypass patterns
if c.matchesBypassPattern(r.URL.Path) {
return nil
}
// check Sec-Fetch-Site header (modern browsers)
secFetchSite := r.Header.Get("Sec-Fetch-Site")
if secFetchSite != "" {
switch secFetchSite {
case "same-origin", "none":
return nil
case "cross-site", "same-site":
// check if origin is trusted
origin := r.Header.Get("Origin")
if origin != "" && c.isOriginTrusted(origin) {
return nil
}
return fmt.Errorf("cross-origin request blocked: Sec-Fetch-Site=%s", secFetchSite)
}
}
// fallback: check Origin header against Host
origin := r.Header.Get("Origin")
if origin != "" {
// check if origin is trusted
if c.isOriginTrusted(origin) {
return nil
}
// compare origin host with request host
originURL, err := url.Parse(origin)
if err != nil {
return fmt.Errorf("invalid Origin header: %w", err)
}
requestHost := r.Host
if requestHost == "" {
requestHost = r.URL.Host
}
// normalize hosts for comparison
originHost := strings.ToLower(originURL.Host)
requestHost = strings.ToLower(requestHost)
if originHost != requestHost {
return fmt.Errorf("cross-origin request blocked: origin %s does not match host %s", originHost, requestHost)
}
return nil
}
// no Sec-Fetch-Site or Origin headers - assume same-origin or non-browser request
return nil
}
// Handler wraps an http.Handler with CSRF protection.
// Rejected requests receive a 403 Forbidden response (or custom deny handler).
func (c *CrossOriginProtection) Handler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := c.Check(r); err != nil {
c.mu.RLock()
deny := c.denyHandler
c.mu.RUnlock()
if deny != nil {
deny.ServeHTTP(w, r)
return
}
http.Error(w, "Forbidden - CSRF check failed", http.StatusForbidden)
return
}
h.ServeHTTP(w, r)
})
}
// isSafeMethod returns true for HTTP methods that don't modify state.
func isSafeMethod(method string) bool {
switch method {
case http.MethodGet, http.MethodHead, http.MethodOptions:
return true
}
return false
}
// isOriginTrusted checks if the origin is in the trusted list.
func (c *CrossOriginProtection) isOriginTrusted(origin string) bool {
u, err := url.Parse(origin)
if err != nil {
return false
}
normalized := strings.ToLower(u.Scheme) + "://" + strings.ToLower(u.Host)
c.mu.RLock()
trusted := c.trustedOrigins[normalized]
c.mu.RUnlock()
return trusted
}
// matchesBypassPattern checks if the path matches any bypass pattern.
func (c *CrossOriginProtection) matchesBypassPattern(path string) bool {
c.mu.RLock()
patterns := make([]string, len(c.bypassPatterns))
copy(patterns, c.bypassPatterns)
c.mu.RUnlock()
for _, pattern := range patterns {
if matchPattern(pattern, path) {
return true
}
}
return false
}
// matchPattern implements simple pattern matching similar to http.ServeMux.
func matchPattern(pattern, path string) bool {
// exact match
if pattern == path {
return true
}
// prefix match for patterns ending with /
if strings.HasSuffix(pattern, "/") && strings.HasPrefix(path, pattern) {
return true
}
return false
}
+111
View File
@@ -0,0 +1,111 @@
//go:build go1.25
package rest
import (
"fmt"
"net/http"
"net/url"
"strings"
)
// CrossOriginProtection provides CSRF protection using modern browser Fetch metadata.
// It validates requests using Sec-Fetch-Site and Origin headers, rejecting cross-origin
// state-changing requests. Safe methods (GET, HEAD, OPTIONS) are always allowed.
//
// For Go 1.25+, this wraps the stdlib http.CrossOriginProtection.
// For earlier versions, it provides an equivalent custom implementation.
type CrossOriginProtection struct {
stdlib *http.CrossOriginProtection
}
// NewCrossOriginProtection creates a new CSRF protection middleware.
func NewCrossOriginProtection() *CrossOriginProtection {
return &CrossOriginProtection{
stdlib: http.NewCrossOriginProtection(),
}
}
// AddTrustedOrigin adds an origin that should be allowed to make cross-origin requests.
// The origin must be in the format "scheme://host" or "scheme://host:port".
// Returns an error if the origin format is invalid.
func (c *CrossOriginProtection) AddTrustedOrigin(origin string) error {
u, err := url.Parse(origin)
if err != nil {
return fmt.Errorf("invalid origin: %w", err)
}
if u.Scheme == "" || u.Host == "" {
return fmt.Errorf("origin must have scheme and host: %s", origin)
}
if u.Path != "" && u.Path != "/" {
return fmt.Errorf("origin must not have path: %s", origin)
}
if u.RawQuery != "" || u.Fragment != "" {
return fmt.Errorf("origin must not have query or fragment: %s", origin)
}
// normalize to lowercase for consistent case-insensitive matching
normalized := strings.ToLower(u.Scheme) + "://" + strings.ToLower(u.Host)
return c.stdlib.AddTrustedOrigin(normalized)
}
// AddBypassPattern adds a URL pattern that should bypass CSRF protection.
// Patterns follow the same syntax as http.ServeMux (e.g., "/api/webhook", "/oauth/").
// Use sparingly and only for endpoints that have alternative authentication.
func (c *CrossOriginProtection) AddBypassPattern(pattern string) {
c.stdlib.AddInsecureBypassPattern(pattern)
}
// SetDenyHandler sets a custom handler for rejected requests.
// If not set, rejected requests receive a 403 Forbidden response.
func (c *CrossOriginProtection) SetDenyHandler(h http.Handler) {
c.stdlib.SetDenyHandler(h)
}
// Check validates a request against CSRF protection rules.
// Returns nil if the request is allowed, or an error describing why it was rejected.
func (c *CrossOriginProtection) Check(r *http.Request) error {
// the stdlib Check method panics or returns void, so we use our own check logic
// by creating a test handler and seeing if it gets called
// safe methods are always allowed
switch r.Method {
case http.MethodGet, http.MethodHead, http.MethodOptions:
return nil
}
// use a test to determine if request would be allowed
allowed := false
testHandler := http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {
allowed = true
})
// create a response recorder to capture the result
rec := &discardResponseWriter{}
c.stdlib.Handler(testHandler).ServeHTTP(rec, r)
if !allowed {
return fmt.Errorf("cross-origin request blocked by CSRF protection")
}
return nil
}
// Handler wraps an http.Handler with CSRF protection.
// Rejected requests receive a 403 Forbidden response (or custom deny handler).
func (c *CrossOriginProtection) Handler(h http.Handler) http.Handler {
return c.stdlib.Handler(h)
}
// discardResponseWriter is a minimal ResponseWriter for testing.
type discardResponseWriter struct {
header http.Header
}
func (d *discardResponseWriter) Header() http.Header {
if d.header == nil {
d.header = make(http.Header)
}
return d.header
}
func (d *discardResponseWriter) Write(b []byte) (int, error) { return len(b), nil }
func (d *discardResponseWriter) WriteHeader(_ int) {}
+1 -1
View File
@@ -20,7 +20,7 @@ var gzDefaultContentTypes = []string{
}
var gzPool = sync.Pool{
New: func() interface{} { return gzip.NewWriter(io.Discard) },
New: func() any { return gzip.NewWriter(io.Discard) },
}
type gzipResponseWriter struct {
+2 -2
View File
@@ -32,7 +32,7 @@ type Middleware struct {
// Backend is logging backend
type Backend interface {
Logf(format string, args ...interface{})
Logf(format string, args ...any)
}
type logParts struct {
@@ -51,7 +51,7 @@ type logParts struct {
type stdBackend struct{}
func (s stdBackend) Logf(format string, args ...interface{}) {
func (s stdBackend) Logf(format string, args ...any) {
log.Printf(format, args...)
}
+7 -4
View File
@@ -36,14 +36,17 @@ func AppInfo(app, author, version string) func(http.Handler) http.Handler {
return f
}
// Ping middleware response with pong to /ping. Stops chain if ping request detected
// Ping middleware response with pong to /ping. Stops chain if ping request detected.
// Handles both GET and HEAD methods - HEAD returns headers only without body,
// which is useful for lightweight health checks by monitoring tools.
func Ping(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" && strings.HasSuffix(strings.ToLower(r.URL.Path), "/ping") {
if (r.Method == "GET" || r.Method == "HEAD") && strings.HasSuffix(strings.ToLower(r.URL.Path), "/ping") {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("pong"))
if r.Method == "GET" {
_, _ = w.Write([]byte("pong"))
}
return
}
next.ServeHTTP(w, r)
+1 -2
View File
@@ -58,8 +58,7 @@ func Get(r *http.Request) (string, error) {
// check X-Forwarded-For, find leftmost public IP
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
addresses := strings.Split(xff, ",")
for _, addr := range addresses {
for addr := range strings.SplitSeq(xff, ",") {
ip := strings.TrimSpace(addr)
if parsedIP := net.ParseIP(ip); isPublicIP(parsedIP) {
return ip, nil
+4 -5
View File
@@ -13,7 +13,7 @@ import (
type JSON map[string]any
// RenderJSON sends data as json
func RenderJSON(w http.ResponseWriter, data interface{}) {
func RenderJSON(w http.ResponseWriter, data any) {
buf := &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(true)
@@ -35,9 +35,8 @@ func RenderJSONFromBytes(w http.ResponseWriter, r *http.Request, data []byte) er
}
// RenderJSONWithHTML allows html tags and forces charset=utf-8
func RenderJSONWithHTML(w http.ResponseWriter, r *http.Request, v interface{}) error {
encodeJSONWithHTML := func(v interface{}) ([]byte, error) {
func RenderJSONWithHTML(w http.ResponseWriter, r *http.Request, v any) error {
encodeJSONWithHTML := func(v any) ([]byte, error) {
buf := &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
@@ -55,7 +54,7 @@ func RenderJSONWithHTML(w http.ResponseWriter, r *http.Request, v interface{}) e
}
// renderJSONWithStatus sends data as json and enforces status code
func renderJSONWithStatus(w http.ResponseWriter, data interface{}, code int) {
func renderJSONWithStatus(w http.ResponseWriter, data any, code int) {
buf := &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(true)
+73
View File
@@ -9,6 +9,79 @@ import (
"strings"
)
// CleanPath middleware cleans double slashes from URL path.
// For example, if a request is made to /users//1 or //users////1,
// it will be cleaned to /users/1 before routing.
// Trailing slashes are preserved: /users//1/ becomes /users/1/.
// Dot segments (. and ..) are intentionally NOT cleaned to preserve routing semantics.
func CleanPath(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rctx := r.Context()
// skip if already cleaned
if _, ok := rctx.Value(contextKey("cleanpath")).(bool); ok {
next.ServeHTTP(w, r)
return
}
p := r.URL.Path
cleaned := cleanDoubleSlashes(p)
if cleaned != p {
r.URL.Path = cleaned
if r.URL.RawPath != "" {
// clean double slashes in RawPath separately to preserve percent-encoding
r.URL.RawPath = cleanDoubleSlashes(r.URL.RawPath)
}
rctx = context.WithValue(rctx, contextKey("cleanpath"), true)
r = r.WithContext(rctx)
}
next.ServeHTTP(w, r)
})
}
// cleanDoubleSlashes removes consecutive slashes from path while preserving
// trailing slashes and dot segments (. and ..).
func cleanDoubleSlashes(p string) string {
if p == "" || p == "/" {
return p
}
var b strings.Builder
b.Grow(len(p))
prevSlash := false
for i := 0; i < len(p); i++ {
c := p[i]
if c == '/' {
if !prevSlash {
b.WriteByte(c)
}
prevSlash = true
} else {
b.WriteByte(c)
prevSlash = false
}
}
return b.String()
}
// StripSlashes middleware removes trailing slashes from URL path.
// For example, /users/1/ becomes /users/1.
// The root path "/" is preserved.
func StripSlashes(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
p := r.URL.Path
if len(p) > 1 && p[len(p)-1] == '/' {
r.URL.Path = p[:len(p)-1]
if r.URL.RawPath != "" {
r.URL.RawPath = strings.TrimSuffix(r.URL.RawPath, "/")
}
}
next.ServeHTTP(w, r)
})
}
// Rewrite middleware with from->to rule. Supports regex (like nginx) and prevents multiple rewrites
// example: Rewrite(`^/sites/(.*)/settings/$`, `/sites/settings/$1`
func Rewrite(from, to string) func(http.Handler) http.Handler {
+207
View File
@@ -0,0 +1,207 @@
package rest
import (
"net/http"
"strconv"
"strings"
)
// SecureConfig defines security headers configuration.
// Use SecOpt functions to customize.
type SecureConfig struct {
// xFrameOptions sets X-Frame-Options header. Default: DENY
XFrameOptions string
// xContentTypeOptions sets X-Content-Type-Options. Default: nosniff
XContentTypeOptions string
// ReferrerPolicy sets Referrer-Policy header. Default: strict-origin-when-cross-origin
ReferrerPolicy string
// ContentSecurityPolicy sets Content-Security-Policy header. Default: empty (not set)
ContentSecurityPolicy string
// PermissionsPolicy sets Permissions-Policy header. Default: empty (not set)
PermissionsPolicy string
// sTSSeconds sets max-age for Strict-Transport-Security. 0 disables.
// only sent when request uses HTTPS. Default: 31536000 (1 year)
STSSeconds int
// sTSIncludeSubdomains adds includeSubDomains to HSTS. Default: true
STSIncludeSubdomains bool
// sTSPreload adds preload flag to HSTS. Default: false
STSPreload bool
// xSSProtection sets X-XSS-Protection header. Default: 1; mode=block
// note: this header is deprecated in modern browsers but still useful for older ones
XSSProtection string
}
// SecOpt is a functional option for SecureConfig
type SecOpt func(*SecureConfig)
// defaultSecureConfig returns config with sensible defaults
func defaultSecureConfig() SecureConfig {
return SecureConfig{
XFrameOptions: "DENY",
XContentTypeOptions: "nosniff",
ReferrerPolicy: "strict-origin-when-cross-origin",
STSSeconds: 31536000, // 1 year
STSIncludeSubdomains: true,
STSPreload: false,
XSSProtection: "1; mode=block",
}
}
// SecFrameOptions sets X-Frame-Options header.
// Common values: "DENY", "SAMEORIGIN"
func SecFrameOptions(value string) SecOpt {
return func(c *SecureConfig) {
c.XFrameOptions = value
}
}
// SecContentTypeNosniff enables or disables X-Content-Type-Options: nosniff
func SecContentTypeNosniff(enable bool) SecOpt {
return func(c *SecureConfig) {
if enable {
c.XContentTypeOptions = "nosniff"
} else {
c.XContentTypeOptions = ""
}
}
}
// SecReferrerPolicy sets Referrer-Policy header.
// Common values: "no-referrer", "same-origin", "strict-origin", "strict-origin-when-cross-origin"
func SecReferrerPolicy(policy string) SecOpt {
return func(c *SecureConfig) {
c.ReferrerPolicy = policy
}
}
// SecContentSecurityPolicy sets Content-Security-Policy header.
// Example: "default-src 'self'; script-src 'self'"
func SecContentSecurityPolicy(policy string) SecOpt {
return func(c *SecureConfig) {
c.ContentSecurityPolicy = policy
}
}
// SecPermissionsPolicy sets Permissions-Policy header.
// Example: "geolocation=(), microphone=()"
func SecPermissionsPolicy(policy string) SecOpt {
return func(c *SecureConfig) {
c.PermissionsPolicy = policy
}
}
// SecHSTS configures Strict-Transport-Security header.
// maxAge is in seconds (0 disables HSTS), includeSubdomains and preload are optional flags.
// Note: HSTS header is only sent when the request is over HTTPS.
func SecHSTS(maxAge int, includeSubdomains, preload bool) SecOpt {
return func(c *SecureConfig) {
c.STSSeconds = maxAge
c.STSIncludeSubdomains = includeSubdomains
c.STSPreload = preload
}
}
// SecXSSProtection sets X-XSS-Protection header.
// Set to empty string to disable. Common values: "0", "1", "1; mode=block"
func SecXSSProtection(value string) SecOpt {
return func(c *SecureConfig) {
c.XSSProtection = value
}
}
// SecAllHeaders is a convenience option to set common headers for secure web applications.
// Sets CSP with self-only policy and restrictive permissions.
func SecAllHeaders() SecOpt {
return func(c *SecureConfig) {
c.ContentSecurityPolicy = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; form-action 'self'; frame-ancestors 'none'"
c.PermissionsPolicy = "geolocation=(), microphone=(), camera=()"
}
}
// Secure is middleware that adds security headers to responses.
// By default it sets: X-Frame-Options, X-Content-Type-Options, Referrer-Policy,
// X-XSS-Protection, and Strict-Transport-Security (for HTTPS only).
// Use SecOpt functions to customize the configuration.
func Secure(opts ...SecOpt) func(http.Handler) http.Handler {
cfg := defaultSecureConfig()
for _, opt := range opts {
opt(&cfg)
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// set security headers
if cfg.XFrameOptions != "" {
w.Header().Set("X-Frame-Options", cfg.XFrameOptions)
}
if cfg.XContentTypeOptions != "" {
w.Header().Set("X-Content-Type-Options", cfg.XContentTypeOptions)
}
if cfg.ReferrerPolicy != "" {
w.Header().Set("Referrer-Policy", cfg.ReferrerPolicy)
}
if cfg.XSSProtection != "" {
w.Header().Set("X-XSS-Protection", cfg.XSSProtection)
}
if cfg.ContentSecurityPolicy != "" {
w.Header().Set("Content-Security-Policy", cfg.ContentSecurityPolicy)
}
if cfg.PermissionsPolicy != "" {
w.Header().Set("Permissions-Policy", cfg.PermissionsPolicy)
}
// HSTS only for HTTPS connections
if cfg.STSSeconds > 0 && isHTTPS(r) {
sts := "max-age=" + strconv.Itoa(cfg.STSSeconds)
if cfg.STSIncludeSubdomains {
sts += "; includeSubDomains"
}
if cfg.STSPreload {
sts += "; preload"
}
w.Header().Set("Strict-Transport-Security", sts)
}
next.ServeHTTP(w, r)
})
}
}
// isHTTPS checks if the request is over HTTPS by examining TLS state and common proxy headers
func isHTTPS(r *http.Request) bool {
// direct TLS connection
if r.TLS != nil {
return true
}
// check common proxy headers (case-insensitive)
if strings.EqualFold(r.Header.Get("X-Forwarded-Proto"), "https") {
return true
}
// check RFC 7239 Forwarded header
if forwarded := r.Header.Get("Forwarded"); forwarded != "" {
if forwardedProtoIsHTTPS(forwarded) {
return true
}
}
return false
}
// forwardedProtoIsHTTPS parses RFC 7239 Forwarded header to check for proto=https.
// The header format is: Forwarded: for=1.2.3.4;proto=https;by=proxy, for=5.6.7.8
// Parameters are separated by semicolons, multiple forwarded elements by commas.
func forwardedProtoIsHTTPS(header string) bool {
// split by comma to get individual forwarded elements
for element := range strings.SplitSeq(header, ",") {
// split by semicolon to get parameters within element
for param := range strings.SplitSeq(element, ";") {
param = strings.TrimSpace(param)
// check for proto=https (case-insensitive per RFC 7239)
if len(param) > 6 && strings.EqualFold(param[:6], "proto=") {
if strings.EqualFold(strings.TrimSpace(param[6:]), "https") {
return true
}
}
}
}
return false
}
+6 -5
View File
@@ -140,11 +140,12 @@ A common use case would be integrating with different 3rd party signature
providers, like key management services from various cloud providers or Hardware
Security Modules (HSMs) or to implement additional standards.
| Extension | Purpose | Repo |
| --------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| GCP | Integrates with multiple Google Cloud Platform signing tools (AppEngine, IAM API, Cloud KMS) | https://github.com/someone1/gcp-jwt-go |
| AWS | Integrates with AWS Key Management Service, KMS | https://github.com/matelang/jwt-go-aws-kms |
| JWKS | Provides support for JWKS ([RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517)) as a `jwt.Keyfunc` | https://github.com/MicahParks/keyfunc |
| Extension | Purpose | Repo |
| --------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
| GCP | Integrates with multiple Google Cloud Platform signing tools (AppEngine, IAM API, Cloud KMS) | https://github.com/someone1/gcp-jwt-go |
| AWS | Integrates with AWS Key Management Service, KMS | https://github.com/matelang/jwt-go-aws-kms |
| JWKS | Provides support for JWKS ([RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517)) as a `jwt.Keyfunc` | https://github.com/MicahParks/keyfunc |
| TPM | Integrates with Trusted Platform Module (TPM) | https://github.com/salrashid123/golang-jwt-tpm |
*Disclaimer*: Unless otherwise specified, these integrations are maintained by
third parties and should not be considered as a primary offer by any of the
+1 -1
View File
@@ -97,7 +97,7 @@ Backwards compatible API change that was missed in 2.0.0.
There were two major reasons for breaking backwards compatibility with this update. The first was a refactor required to expand the width of the RSA and HMAC-SHA signing implementations. There will likely be no required code changes to support this change.
The second update, while unfortunately requiring a small change in integration, is required to open up this library to other signing methods. Not all keys used for all signing methods have a single standard on-disk representation. Requiring `[]byte` as the type for all keys proved too limiting. Additionally, this implementation allows for pre-parsed tokens to be reused, which might matter in an application that parses a high volume of tokens with a small set of keys. Backwards compatibilty has been maintained for passing `[]byte` to the RSA signing methods, but they will also accept `*rsa.PublicKey` and `*rsa.PrivateKey`.
The second update, while unfortunately requiring a small change in integration, is required to open up this library to other signing methods. Not all keys used for all signing methods have a single standard on-disk representation. Requiring `[]byte` as the type for all keys proved too limiting. Additionally, this implementation allows for pre-parsed tokens to be reused, which might matter in an application that parses a high volume of tokens with a small set of keys. Backwards compatibility has been maintained for passing `[]byte` to the RSA signing methods, but they will also accept `*rsa.PublicKey` and `*rsa.PrivateKey`.
It is likely the only integration change required here will be to change `func(t *jwt.Token) ([]byte, error)` to `func(t *jwt.Token) (interface{}, error)` when calling `Parse`.
+13 -11
View File
@@ -76,13 +76,6 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
}
}
// Decode signature
token.Signature, err = p.DecodeSegment(parts[2])
if err != nil {
return token, newError("could not base64 decode signature", ErrTokenMalformed, err)
}
text := strings.Join(parts[0:2], ".")
// Lookup key(s)
if keyFunc == nil {
// keyFunc was not provided. short circuiting validation
@@ -94,11 +87,14 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
return token, newError("error while executing keyfunc", ErrTokenUnverifiable, err)
}
// Join together header and claims in order to verify them with the signature
text := strings.Join(parts[0:2], ".")
switch have := got.(type) {
case VerificationKeySet:
if len(have.Keys) == 0 {
return token, newError("keyfunc returned empty verification key set", ErrTokenUnverifiable)
}
// Iterate through keys and verify signature, skipping the rest when a match is found.
// Return the last error if no match is found.
for _, key := range have.Keys {
@@ -131,7 +127,7 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
return token, nil
}
// ParseUnverified parses the token but doesn't validate the signature.
// ParseUnverified parses the token but does not validate the signature.
//
// WARNING: Don't use this method unless you know what you're doing.
//
@@ -146,7 +142,7 @@ func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Toke
token = &Token{Raw: tokenString}
// parse Header
// Parse Header
var headerBytes []byte
if headerBytes, err = p.DecodeSegment(parts[0]); err != nil {
return token, parts, newError("could not base64 decode header", ErrTokenMalformed, err)
@@ -155,7 +151,7 @@ func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Toke
return token, parts, newError("could not JSON decode header", ErrTokenMalformed, err)
}
// parse Claims
// Parse Claims
token.Claims = claims
claimBytes, err := p.DecodeSegment(parts[1])
@@ -196,6 +192,12 @@ func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Toke
return token, parts, newError("signing method (alg) is unspecified", ErrTokenUnverifiable)
}
// Parse token signature
token.Signature, err = p.DecodeSegment(parts[2])
if err != nil {
return token, parts, newError("could not base64 decode signature", ErrTokenMalformed, err)
}
return token, parts, nil
}
@@ -216,7 +218,7 @@ func splitToken(token string) ([]string, bool) {
parts[1] = claims
// One more cut to ensure the signature is the last part of the token and there are no more
// delimiters. This avoids an issue where malicious input could contain additional delimiters
// causing unecessary overhead parsing tokens.
// causing unnecessary overhead parsing tokens.
signature, _, unexpected := strings.Cut(remain, tokenDelimiter)
if unexpected {
return nil, false
+9 -3
View File
@@ -3,9 +3,7 @@ package jwt
import "time"
// ParserOption is used to implement functional-style options that modify the
// behavior of the parser. To add new options, just create a function (ideally
// beginning with With or Without) that returns an anonymous function that takes
// a *Parser type as input and manipulates its configuration accordingly.
// behavior of the parser.
type ParserOption func(*Parser)
// WithValidMethods is an option to supply algorithm methods that the parser
@@ -66,6 +64,14 @@ func WithExpirationRequired() ParserOption {
}
}
// WithNotBeforeRequired returns the ParserOption to make nbf claim required.
// By default nbf claim is optional.
func WithNotBeforeRequired() ParserOption {
return func(p *Parser) {
p.validator.requireNbf = true
}
}
// WithAudience configures the validator to require any of the specified
// audiences in the `aud` claim. Validation will fail if the audience is not
// listed in the token or the `aud` claim is missing.
+4 -2
View File
@@ -32,8 +32,8 @@ type Token struct {
Method SigningMethod // Method is the signing method used or to be used
Header map[string]any // Header is the first segment of the token in decoded form
Claims Claims // Claims is the second segment of the token in decoded form
Signature []byte // Signature is the third segment of the token in decoded form. Populated when you Parse a token
Valid bool // Valid specifies if the token is valid. Populated when you Parse/Verify a token
Signature []byte // Signature is the third segment of the token in decoded form. Populated when you [Parse] or sign a token
Valid bool // Valid specifies if the token is valid. Populated when you [Parse] a token
}
// New creates a new [Token] with the specified signing method and an empty map
@@ -71,6 +71,8 @@ func (t *Token) SignedString(key any) (string, error) {
return "", err
}
t.Signature = sig
return sstr + "." + t.EncodeSegment(sig), nil
}
+6 -2
View File
@@ -44,6 +44,9 @@ type Validator struct {
// requireExp specifies whether the exp claim is required
requireExp bool
// requireNbf specifies whether the nbf claim is required
requireNbf bool
// verifyIat specifies whether the iat (Issued At) claim will be verified.
// According to https://www.rfc-editor.org/rfc/rfc7519#section-4.1.6 this
// only specifies the age of the token, but no validation check is
@@ -111,8 +114,9 @@ func (v *Validator) Validate(claims Claims) error {
}
// We always need to check not-before, but usage of the claim itself is
// OPTIONAL.
if err = v.verifyNotBefore(claims, now, false); err != nil {
// OPTIONAL by default. requireNbf overrides this behavior and makes
// the nbf claim mandatory.
if err = v.verifyNotBefore(claims, now, v.requireNbf); err != nil {
errs = append(errs, err)
}
+3
View File
@@ -196,6 +196,9 @@ func Key(password, salt []byte, N, r, p, keyLen int) ([]byte, error) {
if N <= 1 || N&(N-1) != 0 {
return nil, errors.New("scrypt: N must be > 1 and a power of 2")
}
if r <= 0 || p <= 0 {
return nil, errors.New("scrypt: parameters must be > 0")
}
if uint64(r)*uint64(p) >= 1<<30 || r > maxInt/128/p || r > maxInt/256 || N > maxInt/128/r {
return nil, errors.New("scrypt: parameters are too large")
}
+1 -1
View File
@@ -47,7 +47,7 @@ func archInit() {
switch runtime.GOOS {
case "freebsd":
readARM64Registers()
case "linux", "netbsd", "openbsd":
case "linux", "netbsd", "openbsd", "windows":
doinit()
default:
// Many platforms don't seem to allow reading these registers.
+1 -1
View File
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !linux && !netbsd && !openbsd && arm64
//go:build !linux && !netbsd && !openbsd && !windows && arm64
package cpu
+42
View File
@@ -0,0 +1,42 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cpu
import (
"golang.org/x/sys/windows"
)
func doinit() {
// set HasASIMD and HasFP to true as per
// https://learn.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=msvc-170#base-requirements
//
// The ARM64 version of Windows always presupposes that it's running on an ARMv8 or later architecture.
// Both floating-point and NEON support are presumed to be present in hardware.
//
ARM64.HasASIMD = true
ARM64.HasFP = true
if windows.IsProcessorFeaturePresent(windows.PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE) {
ARM64.HasAES = true
ARM64.HasPMULL = true
ARM64.HasSHA1 = true
ARM64.HasSHA2 = true
}
ARM64.HasSHA3 = windows.IsProcessorFeaturePresent(windows.PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE)
ARM64.HasCRC32 = windows.IsProcessorFeaturePresent(windows.PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE)
ARM64.HasSHA512 = windows.IsProcessorFeaturePresent(windows.PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE)
ARM64.HasATOMICS = windows.IsProcessorFeaturePresent(windows.PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE)
if windows.IsProcessorFeaturePresent(windows.PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE) {
ARM64.HasASIMDDP = true
ARM64.HasASIMDRDM = true
}
if windows.IsProcessorFeaturePresent(windows.PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE) {
ARM64.HasLRCPC = true
ARM64.HasSM3 = true
}
ARM64.HasSVE = windows.IsProcessorFeaturePresent(windows.PF_ARM_SVE_INSTRUCTIONS_AVAILABLE)
ARM64.HasSVE2 = windows.IsProcessorFeaturePresent(windows.PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE)
ARM64.HasJSCVT = windows.IsProcessorFeaturePresent(windows.PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE)
}
+122 -48
View File
@@ -64,6 +64,80 @@ func initOptions() {
func archInit() {
// From internal/cpu
const (
// eax bits
cpuid_AVXVNNI = 1 << 4
// ecx bits
cpuid_SSE3 = 1 << 0
cpuid_PCLMULQDQ = 1 << 1
cpuid_AVX512VBMI = 1 << 1
cpuid_AVX512VBMI2 = 1 << 6
cpuid_SSSE3 = 1 << 9
cpuid_AVX512GFNI = 1 << 8
cpuid_AVX512VAES = 1 << 9
cpuid_AVX512VNNI = 1 << 11
cpuid_AVX512BITALG = 1 << 12
cpuid_FMA = 1 << 12
cpuid_AVX512VPOPCNTDQ = 1 << 14
cpuid_SSE41 = 1 << 19
cpuid_SSE42 = 1 << 20
cpuid_POPCNT = 1 << 23
cpuid_AES = 1 << 25
cpuid_OSXSAVE = 1 << 27
cpuid_AVX = 1 << 28
// "Extended Feature Flag" bits returned in EBX for CPUID EAX=0x7 ECX=0x0
cpuid_BMI1 = 1 << 3
cpuid_AVX2 = 1 << 5
cpuid_BMI2 = 1 << 8
cpuid_ERMS = 1 << 9
cpuid_AVX512F = 1 << 16
cpuid_AVX512DQ = 1 << 17
cpuid_ADX = 1 << 19
cpuid_AVX512CD = 1 << 28
cpuid_SHA = 1 << 29
cpuid_AVX512BW = 1 << 30
cpuid_AVX512VL = 1 << 31
// "Extended Feature Flag" bits returned in ECX for CPUID EAX=0x7 ECX=0x0
cpuid_AVX512_VBMI = 1 << 1
cpuid_AVX512_VBMI2 = 1 << 6
cpuid_GFNI = 1 << 8
cpuid_AVX512VPCLMULQDQ = 1 << 10
cpuid_AVX512_BITALG = 1 << 12
// edx bits
cpuid_FSRM = 1 << 4
// edx bits for CPUID 0x80000001
cpuid_RDTSCP = 1 << 27
)
// Additional constants not in internal/cpu
const (
// eax=1: edx
cpuid_SSE2 = 1 << 26
// eax=1: ecx
cpuid_CX16 = 1 << 13
cpuid_RDRAND = 1 << 30
// eax=7,ecx=0: ebx
cpuid_RDSEED = 1 << 18
cpuid_AVX512IFMA = 1 << 21
cpuid_AVX512PF = 1 << 26
cpuid_AVX512ER = 1 << 27
// eax=7,ecx=0: edx
cpuid_AVX5124VNNIW = 1 << 2
cpuid_AVX5124FMAPS = 1 << 3
cpuid_AMXBF16 = 1 << 22
cpuid_AMXTile = 1 << 24
cpuid_AMXInt8 = 1 << 25
// eax=7,ecx=1: eax
cpuid_AVX512BF16 = 1 << 5
cpuid_AVXIFMA = 1 << 23
// eax=7,ecx=1: edx
cpuid_AVXVNNIInt8 = 1 << 4
)
Initialized = true
maxID, _, _, _ := cpuid(0, 0)
@@ -73,90 +147,90 @@ func archInit() {
}
_, _, ecx1, edx1 := cpuid(1, 0)
X86.HasSSE2 = isSet(26, edx1)
X86.HasSSE2 = isSet(edx1, cpuid_SSE2)
X86.HasSSE3 = isSet(0, ecx1)
X86.HasPCLMULQDQ = isSet(1, ecx1)
X86.HasSSSE3 = isSet(9, ecx1)
X86.HasFMA = isSet(12, ecx1)
X86.HasCX16 = isSet(13, ecx1)
X86.HasSSE41 = isSet(19, ecx1)
X86.HasSSE42 = isSet(20, ecx1)
X86.HasPOPCNT = isSet(23, ecx1)
X86.HasAES = isSet(25, ecx1)
X86.HasOSXSAVE = isSet(27, ecx1)
X86.HasRDRAND = isSet(30, ecx1)
X86.HasSSE3 = isSet(ecx1, cpuid_SSE3)
X86.HasPCLMULQDQ = isSet(ecx1, cpuid_PCLMULQDQ)
X86.HasSSSE3 = isSet(ecx1, cpuid_SSSE3)
X86.HasFMA = isSet(ecx1, cpuid_FMA)
X86.HasCX16 = isSet(ecx1, cpuid_CX16)
X86.HasSSE41 = isSet(ecx1, cpuid_SSE41)
X86.HasSSE42 = isSet(ecx1, cpuid_SSE42)
X86.HasPOPCNT = isSet(ecx1, cpuid_POPCNT)
X86.HasAES = isSet(ecx1, cpuid_AES)
X86.HasOSXSAVE = isSet(ecx1, cpuid_OSXSAVE)
X86.HasRDRAND = isSet(ecx1, cpuid_RDRAND)
var osSupportsAVX, osSupportsAVX512 bool
// For XGETBV, OSXSAVE bit is required and sufficient.
if X86.HasOSXSAVE {
eax, _ := xgetbv()
// Check if XMM and YMM registers have OS support.
osSupportsAVX = isSet(1, eax) && isSet(2, eax)
osSupportsAVX = isSet(eax, 1<<1) && isSet(eax, 1<<2)
if runtime.GOOS == "darwin" {
// Darwin requires special AVX512 checks, see cpu_darwin_x86.go
osSupportsAVX512 = osSupportsAVX && darwinSupportsAVX512()
} else {
// Check if OPMASK and ZMM registers have OS support.
osSupportsAVX512 = osSupportsAVX && isSet(5, eax) && isSet(6, eax) && isSet(7, eax)
osSupportsAVX512 = osSupportsAVX && isSet(eax, 1<<5) && isSet(eax, 1<<6) && isSet(eax, 1<<7)
}
}
X86.HasAVX = isSet(28, ecx1) && osSupportsAVX
X86.HasAVX = isSet(ecx1, cpuid_AVX) && osSupportsAVX
if maxID < 7 {
return
}
eax7, ebx7, ecx7, edx7 := cpuid(7, 0)
X86.HasBMI1 = isSet(3, ebx7)
X86.HasAVX2 = isSet(5, ebx7) && osSupportsAVX
X86.HasBMI2 = isSet(8, ebx7)
X86.HasERMS = isSet(9, ebx7)
X86.HasRDSEED = isSet(18, ebx7)
X86.HasADX = isSet(19, ebx7)
X86.HasBMI1 = isSet(ebx7, cpuid_BMI1)
X86.HasAVX2 = isSet(ebx7, cpuid_AVX2) && osSupportsAVX
X86.HasBMI2 = isSet(ebx7, cpuid_BMI2)
X86.HasERMS = isSet(ebx7, cpuid_ERMS)
X86.HasRDSEED = isSet(ebx7, cpuid_RDSEED)
X86.HasADX = isSet(ebx7, cpuid_ADX)
X86.HasAVX512 = isSet(16, ebx7) && osSupportsAVX512 // Because avx-512 foundation is the core required extension
X86.HasAVX512 = isSet(ebx7, cpuid_AVX512F) && osSupportsAVX512 // Because avx-512 foundation is the core required extension
if X86.HasAVX512 {
X86.HasAVX512F = true
X86.HasAVX512CD = isSet(28, ebx7)
X86.HasAVX512ER = isSet(27, ebx7)
X86.HasAVX512PF = isSet(26, ebx7)
X86.HasAVX512VL = isSet(31, ebx7)
X86.HasAVX512BW = isSet(30, ebx7)
X86.HasAVX512DQ = isSet(17, ebx7)
X86.HasAVX512IFMA = isSet(21, ebx7)
X86.HasAVX512VBMI = isSet(1, ecx7)
X86.HasAVX5124VNNIW = isSet(2, edx7)
X86.HasAVX5124FMAPS = isSet(3, edx7)
X86.HasAVX512VPOPCNTDQ = isSet(14, ecx7)
X86.HasAVX512VPCLMULQDQ = isSet(10, ecx7)
X86.HasAVX512VNNI = isSet(11, ecx7)
X86.HasAVX512GFNI = isSet(8, ecx7)
X86.HasAVX512VAES = isSet(9, ecx7)
X86.HasAVX512VBMI2 = isSet(6, ecx7)
X86.HasAVX512BITALG = isSet(12, ecx7)
X86.HasAVX512CD = isSet(ebx7, cpuid_AVX512CD)
X86.HasAVX512ER = isSet(ebx7, cpuid_AVX512ER)
X86.HasAVX512PF = isSet(ebx7, cpuid_AVX512PF)
X86.HasAVX512VL = isSet(ebx7, cpuid_AVX512VL)
X86.HasAVX512BW = isSet(ebx7, cpuid_AVX512BW)
X86.HasAVX512DQ = isSet(ebx7, cpuid_AVX512DQ)
X86.HasAVX512IFMA = isSet(ebx7, cpuid_AVX512IFMA)
X86.HasAVX512VBMI = isSet(ecx7, cpuid_AVX512_VBMI)
X86.HasAVX5124VNNIW = isSet(edx7, cpuid_AVX5124VNNIW)
X86.HasAVX5124FMAPS = isSet(edx7, cpuid_AVX5124FMAPS)
X86.HasAVX512VPOPCNTDQ = isSet(ecx7, cpuid_AVX512VPOPCNTDQ)
X86.HasAVX512VPCLMULQDQ = isSet(ecx7, cpuid_AVX512VPCLMULQDQ)
X86.HasAVX512VNNI = isSet(ecx7, cpuid_AVX512VNNI)
X86.HasAVX512GFNI = isSet(ecx7, cpuid_AVX512GFNI)
X86.HasAVX512VAES = isSet(ecx7, cpuid_AVX512VAES)
X86.HasAVX512VBMI2 = isSet(ecx7, cpuid_AVX512VBMI2)
X86.HasAVX512BITALG = isSet(ecx7, cpuid_AVX512BITALG)
}
X86.HasAMXTile = isSet(24, edx7)
X86.HasAMXInt8 = isSet(25, edx7)
X86.HasAMXBF16 = isSet(22, edx7)
X86.HasAMXTile = isSet(edx7, cpuid_AMXTile)
X86.HasAMXInt8 = isSet(edx7, cpuid_AMXInt8)
X86.HasAMXBF16 = isSet(edx7, cpuid_AMXBF16)
// These features depend on the second level of extended features.
if eax7 >= 1 {
eax71, _, _, edx71 := cpuid(7, 1)
if X86.HasAVX512 {
X86.HasAVX512BF16 = isSet(5, eax71)
X86.HasAVX512BF16 = isSet(eax71, cpuid_AVX512BF16)
}
if X86.HasAVX {
X86.HasAVXIFMA = isSet(23, eax71)
X86.HasAVXVNNI = isSet(4, eax71)
X86.HasAVXVNNIInt8 = isSet(4, edx71)
X86.HasAVXIFMA = isSet(eax71, cpuid_AVXIFMA)
X86.HasAVXVNNI = isSet(eax71, cpuid_AVXVNNI)
X86.HasAVXVNNIInt8 = isSet(edx71, cpuid_AVXVNNIInt8)
}
}
}
func isSet(bitpos uint, value uint32) bool {
return value&(1<<bitpos) != 0
func isSet(hwc uint32, value uint32) bool {
return hwc&value != 0
}
+8 -3
View File
@@ -6,9 +6,7 @@
package unix
import (
"unsafe"
)
import "unsafe"
// ioctl itself should not be exposed directly, but additional get/set
// functions for specific types are permissible.
@@ -28,6 +26,13 @@ func IoctlSetPointerInt(fd int, req int, value int) error {
return ioctlPtr(fd, req, unsafe.Pointer(&v))
}
// IoctlSetString performs an ioctl operation which sets a string value
// on fd, using the specified request number.
func IoctlSetString(fd int, req int, value string) error {
bs := append([]byte(value), 0)
return ioctlPtr(fd, req, unsafe.Pointer(&bs[0]))
}
// IoctlSetWinsize performs an ioctl on fd with a *Winsize argument.
//
// To change fd's window size, the req argument should be TIOCSWINSZ.
+8 -3
View File
@@ -6,9 +6,7 @@
package unix
import (
"unsafe"
)
import "unsafe"
// ioctl itself should not be exposed directly, but additional get/set
// functions for specific types are permissible.
@@ -28,6 +26,13 @@ func IoctlSetPointerInt(fd int, req uint, value int) error {
return ioctlPtr(fd, req, unsafe.Pointer(&v))
}
// IoctlSetString performs an ioctl operation which sets a string value
// on fd, using the specified request number.
func IoctlSetString(fd int, req uint, value string) error {
bs := append([]byte(value), 0)
return ioctlPtr(fd, req, unsafe.Pointer(&bs[0]))
}
// IoctlSetWinsize performs an ioctl on fd with a *Winsize argument.
//
// To change fd's window size, the req argument should be TIOCSWINSZ.
-8
View File
@@ -1052,14 +1052,6 @@ func IoctlSetIntRetInt(fd int, req int, arg int) (int, error) {
return ioctlRet(fd, req, uintptr(arg))
}
func IoctlSetString(fd int, req int, val string) error {
bs := make([]byte, len(val)+1)
copy(bs[:len(bs)-1], val)
err := ioctlPtr(fd, req, unsafe.Pointer(&bs[0]))
runtime.KeepAlive(&bs[0])
return err
}
// Lifreq Helpers
func (l *Lifreq) SetName(name string) error {
+7 -3
View File
@@ -367,7 +367,9 @@ func Recvmsg(fd int, p, oob []byte, flags int) (n, oobn int, recvflags int, from
iov[0].SetLen(len(p))
}
var rsa RawSockaddrAny
n, oobn, recvflags, err = recvmsgRaw(fd, iov[:], oob, flags, &rsa)
if n, oobn, recvflags, err = recvmsgRaw(fd, iov[:], oob, flags, &rsa); err != nil {
return
}
// source address is only specified if the socket is unconnected
if rsa.Addr.Family != AF_UNSPEC {
from, err = anyToSockaddr(fd, &rsa)
@@ -389,8 +391,10 @@ func RecvmsgBuffers(fd int, buffers [][]byte, oob []byte, flags int) (n, oobn in
}
}
var rsa RawSockaddrAny
n, oobn, recvflags, err = recvmsgRaw(fd, iov, oob, flags, &rsa)
if err == nil && rsa.Addr.Family != AF_UNSPEC {
if n, oobn, recvflags, err = recvmsgRaw(fd, iov, oob, flags, &rsa); err != nil {
return
}
if rsa.Addr.Family != AF_UNSPEC {
from, err = anyToSockaddr(fd, &rsa)
}
return
+1
View File
@@ -900,6 +900,7 @@ const socket_error = uintptr(^uint32(0))
//sys NotifyRouteChange2(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyRouteChange2
//sys NotifyUnicastIpAddressChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyUnicastIpAddressChange
//sys CancelMibChangeNotify2(notificationHandle Handle) (errcode error) = iphlpapi.CancelMibChangeNotify2
//sys IsProcessorFeaturePresent(ProcessorFeature uint32) (ret bool) = kernel32.IsProcessorFeaturePresent
// For testing: clients can set this flag to force
// creation of IPv6 sockets to return EAFNOSUPPORT.
+85
View File
@@ -3938,3 +3938,88 @@ const (
MOUSE_EVENT = 0x0002
WINDOW_BUFFER_SIZE_EVENT = 0x0004
)
// The processor features to be tested for IsProcessorFeaturePresent, see
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-isprocessorfeaturepresent
const (
PF_ARM_64BIT_LOADSTORE_ATOMIC = 25
PF_ARM_DIVIDE_INSTRUCTION_AVAILABLE = 24
PF_ARM_EXTERNAL_CACHE_AVAILABLE = 26
PF_ARM_FMAC_INSTRUCTIONS_AVAILABLE = 27
PF_ARM_VFP_32_REGISTERS_AVAILABLE = 18
PF_3DNOW_INSTRUCTIONS_AVAILABLE = 7
PF_CHANNELS_ENABLED = 16
PF_COMPARE_EXCHANGE_DOUBLE = 2
PF_COMPARE_EXCHANGE128 = 14
PF_COMPARE64_EXCHANGE128 = 15
PF_FASTFAIL_AVAILABLE = 23
PF_FLOATING_POINT_EMULATED = 1
PF_FLOATING_POINT_PRECISION_ERRATA = 0
PF_MMX_INSTRUCTIONS_AVAILABLE = 3
PF_NX_ENABLED = 12
PF_PAE_ENABLED = 9
PF_RDTSC_INSTRUCTION_AVAILABLE = 8
PF_RDWRFSGSBASE_AVAILABLE = 22
PF_SECOND_LEVEL_ADDRESS_TRANSLATION = 20
PF_SSE3_INSTRUCTIONS_AVAILABLE = 13
PF_SSSE3_INSTRUCTIONS_AVAILABLE = 36
PF_SSE4_1_INSTRUCTIONS_AVAILABLE = 37
PF_SSE4_2_INSTRUCTIONS_AVAILABLE = 38
PF_AVX_INSTRUCTIONS_AVAILABLE = 39
PF_AVX2_INSTRUCTIONS_AVAILABLE = 40
PF_AVX512F_INSTRUCTIONS_AVAILABLE = 41
PF_VIRT_FIRMWARE_ENABLED = 21
PF_XMMI_INSTRUCTIONS_AVAILABLE = 6
PF_XMMI64_INSTRUCTIONS_AVAILABLE = 10
PF_XSAVE_ENABLED = 17
PF_ARM_V8_INSTRUCTIONS_AVAILABLE = 29
PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE = 30
PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE = 31
PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE = 34
PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE = 43
PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE = 44
PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE = 45
PF_ARM_SVE_INSTRUCTIONS_AVAILABLE = 46
PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE = 47
PF_ARM_SVE2_1_INSTRUCTIONS_AVAILABLE = 48
PF_ARM_SVE_AES_INSTRUCTIONS_AVAILABLE = 49
PF_ARM_SVE_PMULL128_INSTRUCTIONS_AVAILABLE = 50
PF_ARM_SVE_BITPERM_INSTRUCTIONS_AVAILABLE = 51
PF_ARM_SVE_BF16_INSTRUCTIONS_AVAILABLE = 52
PF_ARM_SVE_EBF16_INSTRUCTIONS_AVAILABLE = 53
PF_ARM_SVE_B16B16_INSTRUCTIONS_AVAILABLE = 54
PF_ARM_SVE_SHA3_INSTRUCTIONS_AVAILABLE = 55
PF_ARM_SVE_SM4_INSTRUCTIONS_AVAILABLE = 56
PF_ARM_SVE_I8MM_INSTRUCTIONS_AVAILABLE = 57
PF_ARM_SVE_F32MM_INSTRUCTIONS_AVAILABLE = 58
PF_ARM_SVE_F64MM_INSTRUCTIONS_AVAILABLE = 59
PF_BMI2_INSTRUCTIONS_AVAILABLE = 60
PF_MOVDIR64B_INSTRUCTION_AVAILABLE = 61
PF_ARM_LSE2_AVAILABLE = 62
PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE = 64
PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE = 65
PF_ARM_V82_I8MM_INSTRUCTIONS_AVAILABLE = 66
PF_ARM_V82_FP16_INSTRUCTIONS_AVAILABLE = 67
PF_ARM_V86_BF16_INSTRUCTIONS_AVAILABLE = 68
PF_ARM_V86_EBF16_INSTRUCTIONS_AVAILABLE = 69
PF_ARM_SME_INSTRUCTIONS_AVAILABLE = 70
PF_ARM_SME2_INSTRUCTIONS_AVAILABLE = 71
PF_ARM_SME2_1_INSTRUCTIONS_AVAILABLE = 72
PF_ARM_SME2_2_INSTRUCTIONS_AVAILABLE = 73
PF_ARM_SME_AES_INSTRUCTIONS_AVAILABLE = 74
PF_ARM_SME_SBITPERM_INSTRUCTIONS_AVAILABLE = 75
PF_ARM_SME_SF8MM4_INSTRUCTIONS_AVAILABLE = 76
PF_ARM_SME_SF8MM8_INSTRUCTIONS_AVAILABLE = 77
PF_ARM_SME_SF8DP2_INSTRUCTIONS_AVAILABLE = 78
PF_ARM_SME_SF8DP4_INSTRUCTIONS_AVAILABLE = 79
PF_ARM_SME_SF8FMA_INSTRUCTIONS_AVAILABLE = 80
PF_ARM_SME_F8F32_INSTRUCTIONS_AVAILABLE = 81
PF_ARM_SME_F8F16_INSTRUCTIONS_AVAILABLE = 82
PF_ARM_SME_F16F16_INSTRUCTIONS_AVAILABLE = 83
PF_ARM_SME_B16B16_INSTRUCTIONS_AVAILABLE = 84
PF_ARM_SME_F64F64_INSTRUCTIONS_AVAILABLE = 85
PF_ARM_SME_I16I64_INSTRUCTIONS_AVAILABLE = 86
PF_ARM_SME_LUTv2_INSTRUCTIONS_AVAILABLE = 87
PF_ARM_SME_FA64_INSTRUCTIONS_AVAILABLE = 88
PF_UMONITOR_INSTRUCTION_AVAILABLE = 89
)
+7
View File
@@ -320,6 +320,7 @@ var (
procGetVolumePathNamesForVolumeNameW = modkernel32.NewProc("GetVolumePathNamesForVolumeNameW")
procGetWindowsDirectoryW = modkernel32.NewProc("GetWindowsDirectoryW")
procInitializeProcThreadAttributeList = modkernel32.NewProc("InitializeProcThreadAttributeList")
procIsProcessorFeaturePresent = modkernel32.NewProc("IsProcessorFeaturePresent")
procIsWow64Process = modkernel32.NewProc("IsWow64Process")
procIsWow64Process2 = modkernel32.NewProc("IsWow64Process2")
procLoadLibraryExW = modkernel32.NewProc("LoadLibraryExW")
@@ -2786,6 +2787,12 @@ func initializeProcThreadAttributeList(attrlist *ProcThreadAttributeList, attrco
return
}
func IsProcessorFeaturePresent(ProcessorFeature uint32) (ret bool) {
r0, _, _ := syscall.SyscallN(procIsProcessorFeaturePresent.Addr(), uintptr(ProcessorFeature))
ret = r0 != 0
return
}
func IsWow64Process(handle Handle, isWow64 *bool) (err error) {
var _p0 uint32
if *isWow64 {
+4
View File
@@ -334,3 +334,7 @@ func (t *Transformer) advanceString(s string) (n int, ok bool) {
}
return n, true
}
func (t *Transformer) isFinal() bool {
return t.state == ruleLTRFinal || t.state == ruleRTLFinal || t.state == ruleInitial
}
-11
View File
@@ -1,11 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.10
package bidirule
func (t *Transformer) isFinal() bool {
return t.state == ruleLTRFinal || t.state == ruleRTLFinal || t.state == ruleInitial
}
-14
View File
@@ -1,14 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.10
package bidirule
func (t *Transformer) isFinal() bool {
if !t.isRTL() {
return true
}
return t.state == ruleLTRFinal || t.state == ruleRTLFinal || t.state == ruleInitial
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
//go:build go1.21
//go:build !go1.27
package bidi
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+18 -8
View File
@@ -13,15 +13,18 @@ import "encoding/binary"
// a rune to a uint16. The values take two forms. For v >= 0x8000:
// bits
// 15: 1 (inverse of NFD_QC bit of qcInfo)
// 13..7: qcInfo (see below). isYesD is always true (no decomposition).
// 12..7: qcInfo (see below). isYesD is always true (no decomposition).
// 6..0: ccc (compressed CCC value).
// For v < 0x8000, the respective rune has a decomposition and v is an index
// into a byte array of UTF-8 decomposition sequences and additional info and
// has the form:
// <header> <decomp_byte>* [<tccc> [<lccc>]]
// The header contains the number of bytes in the decomposition (excluding this
// length byte). The two most significant bits of this length byte correspond
// to bit 5 and 4 of qcInfo (see below). The byte sequence itself starts at v+1.
// length byte), with 33 mapped to 31 to fit in 5 bits.
// (If any 31- or 32-byte decompositions come along, we could switch to using
// use a general lookup table as long as there are at most 32 distinct lengths.)
// The three most significant bits of this length byte correspond
// to bit 5, 4, and 3 of qcInfo (see below). The byte sequence itself starts at v+1.
// The byte sequence is followed by a trailing and leading CCC if the values
// for these are not zero. The value of v determines which ccc are appended
// to the sequences. For v < firstCCC, there are none, for v >= firstCCC,
@@ -32,8 +35,8 @@ import "encoding/binary"
const (
qcInfoMask = 0x3F // to clear all but the relevant bits in a qcInfo
headerLenMask = 0x3F // extract the length value from the header byte
headerFlagsMask = 0xC0 // extract the qcInfo bits from the header byte
headerLenMask = 0x1F // extract the length value from the header byte (31 => 33)
headerFlagsMask = 0xE0 // extract the qcInfo bits from the header byte
)
// Properties provides access to normalization properties of a rune.
@@ -109,14 +112,14 @@ func (p Properties) BoundaryAfter() bool {
return p.isInert()
}
// We pack quick check data in 4 bits:
// We pack quick check data in 6 bits:
//
// 5: Combines forward (0 == false, 1 == true)
// 4..3: NFC_QC Yes(00), No (10), or Maybe (11)
// 2: NFD_QC Yes (0) or No (1). No also means there is a decomposition.
// 1..0: Number of trailing non-starters.
//
// When all 4 bits are zero, the character is inert, meaning it is never
// When all 6 bits are zero, the character is inert, meaning it is never
// influenced by normalization.
type qcInfo uint8
@@ -152,6 +155,9 @@ func (p Properties) Decomposition() []byte {
}
i := p.index
n := decomps[i] & headerLenMask
if n == 31 {
n = 33
}
i++
return decomps[i : i+uint16(n)]
}
@@ -260,7 +266,11 @@ func compInfo(v uint16, sz int) Properties {
f := (qcInfo(h&headerFlagsMask) >> 2) | 0x4
p := Properties{size: uint8(sz), flags: f, index: v}
if v >= firstCCC {
v += uint16(h&headerLenMask) + 1
n := uint16(h & headerLenMask)
if n == 31 {
n = 33
}
v += n + 1
c := decomps[v]
p.tccc = c >> 2
p.flags |= qcInfo(c & 0x3)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -7,7 +7,7 @@ github.com/Depado/bfchroma/v2
# github.com/PuerkitoBio/goquery v1.11.0
## explicit; go 1.24.0
github.com/PuerkitoBio/goquery
# github.com/alecthomas/chroma/v2 v2.21.1
# github.com/alecthomas/chroma/v2 v2.23.1
## explicit; go 1.22
github.com/alecthomas/chroma/v2
github.com/alecthomas/chroma/v2/formatters/html
@@ -44,8 +44,8 @@ github.com/didip/tollbooth/v8/limiter
## explicit; go 1.13
github.com/dlclark/regexp2
github.com/dlclark/regexp2/syntax
# github.com/go-chi/chi/v5 v5.2.3
## explicit; go 1.20
# github.com/go-chi/chi/v5 v5.2.5
## explicit; go 1.22
github.com/go-chi/chi/v5
github.com/go-chi/chi/v5/middleware
# github.com/go-chi/cors v1.2.2
@@ -91,7 +91,7 @@ github.com/go-pkgz/repeater/strategy
# github.com/go-pkgz/repeater/v2 v2.2.0
## explicit; go 1.23
github.com/go-pkgz/repeater/v2
# github.com/go-pkgz/rest v1.20.6
# github.com/go-pkgz/rest v1.21.0
## explicit; go 1.24.0
github.com/go-pkgz/rest
github.com/go-pkgz/rest/logger
@@ -102,7 +102,7 @@ github.com/go-pkgz/routegroup
# github.com/go-pkgz/syncs v1.3.2
## explicit; go 1.20
github.com/go-pkgz/syncs
# github.com/golang-jwt/jwt/v5 v5.3.0
# github.com/golang-jwt/jwt/v5 v5.3.1
## explicit; go 1.21
github.com/golang-jwt/jwt/v5
# github.com/golang/snappy v1.0.0
@@ -272,7 +272,7 @@ go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage
## explicit; go 1.20
go.uber.org/goleak
go.uber.org/goleak/internal/stack
# golang.org/x/crypto v0.46.0
# golang.org/x/crypto v0.48.0
## explicit; go 1.24.0
golang.org/x/crypto/acme
golang.org/x/crypto/acme/autocert
@@ -283,11 +283,11 @@ golang.org/x/crypto/blowfish
golang.org/x/crypto/ocsp
golang.org/x/crypto/pbkdf2
golang.org/x/crypto/scrypt
# golang.org/x/image v0.34.0
# golang.org/x/image v0.36.0
## explicit; go 1.24.0
golang.org/x/image/draw
golang.org/x/image/math/f64
# golang.org/x/net v0.48.0
# golang.org/x/net v0.49.0
## explicit; go 1.24.0
golang.org/x/net/html
golang.org/x/net/html/atom
@@ -313,13 +313,13 @@ golang.org/x/oauth2/yandex
## explicit; go 1.24.0
golang.org/x/sync/errgroup
golang.org/x/sync/singleflight
# golang.org/x/sys v0.39.0
# golang.org/x/sys v0.41.0
## explicit; go 1.24.0
golang.org/x/sys/cpu
golang.org/x/sys/unix
golang.org/x/sys/windows
golang.org/x/sys/windows/registry
# golang.org/x/text v0.32.0
# golang.org/x/text v0.34.0
## explicit; go 1.24.0
golang.org/x/text/secure/bidirule
golang.org/x/text/transform