mirror of
https://github.com/tarampampam/error-pages.git
synced 2024-08-30 18:22:40 +00:00
wip: 🔕 temporary commit
This commit is contained in:
parent
ceeb7f9384
commit
c1eaee0287
@ -37,9 +37,9 @@ linters-settings:
|
|||||||
ignore-words: [cancelled]
|
ignore-words: [cancelled]
|
||||||
lll:
|
lll:
|
||||||
line-length: 120
|
line-length: 120
|
||||||
# forbidigo:
|
forbidigo:
|
||||||
# forbid:
|
forbid:
|
||||||
# - '^(fmt\.Print(|f|ln)|print(|ln))(# it looks like a forgotten debugging printing call)?$'
|
- '^(fmt\.Print(|f|ln)|print(|ln))(# it looks like a forgotten debugging printing call)?$'
|
||||||
prealloc:
|
prealloc:
|
||||||
simple: true
|
simple: true
|
||||||
range-loops: true
|
range-loops: true
|
||||||
|
57
internal/http/handlers/error_page/code.go
Normal file
57
internal/http/handlers/error_page/code.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package error_page
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// extractCodeFromURL extracts the error code from the given URL.
|
||||||
|
func extractCodeFromURL(url string) (uint16, bool) {
|
||||||
|
var parts = strings.SplitN(strings.TrimLeft(url, "/"), "/", 1)
|
||||||
|
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
fileName = strings.ToLower(parts[0])
|
||||||
|
ext = filepath.Ext(fileName) // ".html", ".htm", ".%something%" or an empty string
|
||||||
|
)
|
||||||
|
|
||||||
|
if ext != "" && ext != ".html" && ext != ".htm" {
|
||||||
|
return 0, false
|
||||||
|
} else if ext != "" {
|
||||||
|
fileName = strings.TrimSuffix(fileName, ext)
|
||||||
|
}
|
||||||
|
|
||||||
|
if code, err := strconv.ParseUint(fileName, 10, 16); err == nil && code > 0 && code < 999 {
|
||||||
|
return uint16(code), true
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLContainsCode checks if the given URL contains an error code.
|
||||||
|
func URLContainsCode(url string) (ok bool) { _, ok = extractCodeFromURL(url); return } //nolint:nlreturn
|
||||||
|
|
||||||
|
// extractCodeFromHeaders extracts the error code from the given headers.
|
||||||
|
func extractCodeFromHeaders(headers http.Header) (uint16, bool) {
|
||||||
|
// https://kubernetes.github.io/ingress-nginx/user-guide/custom-errors/
|
||||||
|
// HTTP status code returned by the request
|
||||||
|
if value := headers.Get("X-Code"); len(value) > 0 && len(value) <= 3 {
|
||||||
|
if code, err := strconv.ParseUint(value, 10, 16); err == nil && code > 0 && code < 999 {
|
||||||
|
return uint16(code), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeadersContainCode checks if the given headers contain an error code.
|
||||||
|
func HeadersContainCode(headers http.Header) (ok bool) {
|
||||||
|
_, ok = extractCodeFromHeaders(headers)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
58
internal/http/handlers/error_page/code_test.go
Normal file
58
internal/http/handlers/error_page/code_test.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package error_page_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"gh.tarampamp.am/error-pages/internal/http/handlers/error_page"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestURLContainsCode(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for giveUrl, wantOk := range map[string]bool{
|
||||||
|
"/404": true,
|
||||||
|
"/404.htm": true,
|
||||||
|
"/404.HTM": true,
|
||||||
|
"/404.html": true,
|
||||||
|
"/404.HtmL": true,
|
||||||
|
"/404.css": false,
|
||||||
|
"/foo/404": false,
|
||||||
|
"/foo/404.html": false,
|
||||||
|
"/error": false,
|
||||||
|
"/": false,
|
||||||
|
"/////": false,
|
||||||
|
"///404//": false,
|
||||||
|
"": false,
|
||||||
|
} {
|
||||||
|
t.Run(giveUrl, func(t *testing.T) {
|
||||||
|
assert.Equal(t, wantOk, error_page.URLContainsCode(giveUrl))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHeadersContainCode(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for name, _tt := range map[string]struct {
|
||||||
|
giveHeaders http.Header
|
||||||
|
wantOk bool
|
||||||
|
}{
|
||||||
|
"with code": {giveHeaders: http.Header{"X-Code": {"404"}}, wantOk: true},
|
||||||
|
|
||||||
|
"empty": {giveHeaders: nil},
|
||||||
|
"no code": {giveHeaders: http.Header{"X-Code": {""}}},
|
||||||
|
"wrong": {giveHeaders: http.Header{"X-Code": {"foo"}}},
|
||||||
|
"too big": {giveHeaders: http.Header{"X-Code": {"1000"}}},
|
||||||
|
"too small": {giveHeaders: http.Header{"X-Code": {"0"}}},
|
||||||
|
"negative": {giveHeaders: http.Header{"X-Code": {"-1"}}},
|
||||||
|
} {
|
||||||
|
tt := _tt
|
||||||
|
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.wantOk, error_page.HeadersContainCode(tt.giveHeaders))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
134
internal/http/handlers/error_page/format.go
Normal file
134
internal/http/handlers/error_page/format.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package error_page
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type preferredFormat = byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
unknownFormat preferredFormat = iota // should be first, no format detected
|
||||||
|
jsonFormat // json
|
||||||
|
xmlFormat // xml
|
||||||
|
htmlFormat // html
|
||||||
|
plainTextFormat // plain text
|
||||||
|
)
|
||||||
|
|
||||||
|
// detectPreferredFormatForClient detects the preferred format for the client based on the headers.
|
||||||
|
// It supports the following headers: Content-Type, Accept, X-Format.
|
||||||
|
// If the headers are not set or the format is not recognized, it returns unknownFormat.
|
||||||
|
func detectPreferredFormatForClient(headers http.Header) preferredFormat { //nolint:funlen,gocognit
|
||||||
|
var contentType, accept string
|
||||||
|
|
||||||
|
if contentTypeHeader := strings.TrimSpace(headers.Get("Content-Type")); contentTypeHeader != "" { //nolint:nestif
|
||||||
|
// https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Type
|
||||||
|
// text/html; charset=utf-8
|
||||||
|
// multipart/form-data; boundary=something
|
||||||
|
// application/json
|
||||||
|
if parts := strings.SplitN(contentTypeHeader, ";", 2); len(parts) > 1 { //nolint:mnd
|
||||||
|
// take only the first part of the content type:
|
||||||
|
// text/html; charset=utf-8
|
||||||
|
// ^^^^^^^^^ - will be taken
|
||||||
|
contentType = strings.TrimSpace(parts[0])
|
||||||
|
} else {
|
||||||
|
// take the whole value
|
||||||
|
contentType = contentTypeHeader
|
||||||
|
}
|
||||||
|
} else if xFormatHeader := strings.TrimSpace(headers.Get("X-Format")); xFormatHeader != "" {
|
||||||
|
// https://kubernetes.github.io/ingress-nginx/user-guide/custom-errors/
|
||||||
|
// Value of the `Accept` header sent by the client
|
||||||
|
accept = xFormatHeader
|
||||||
|
} else if acceptHeader := strings.TrimSpace(headers.Get("Accept")); acceptHeader != "" {
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
|
||||||
|
// text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8
|
||||||
|
// text/html
|
||||||
|
// image/*
|
||||||
|
// */*
|
||||||
|
// text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
|
||||||
|
accept = acceptHeader
|
||||||
|
} else {
|
||||||
|
return unknownFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case contentType != "":
|
||||||
|
return mimeTypeToPreferredFormat(contentType)
|
||||||
|
|
||||||
|
case accept != "":
|
||||||
|
type piece struct {
|
||||||
|
mimeType string
|
||||||
|
weight int // to avoid float32 comparison (weight 1.0 = 1_0, 0.9 = 0_9, 0.8 = 0_8, etc.)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pieces = make([]piece, 0, strings.Count(accept, ",")+1)
|
||||||
|
|
||||||
|
// split application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 into parts:
|
||||||
|
// ^^^^^^^^^ - segment #3
|
||||||
|
// ^^^^^^^^^^^^^^^^^^^^^ - segment #2
|
||||||
|
// ^^^^^^^^^^^^^^^^^^^^^ - segment #1
|
||||||
|
for _, segment := range strings.FieldsFunc(accept, func(r rune) bool { return r == ',' }) {
|
||||||
|
// split segment into parts:
|
||||||
|
//
|
||||||
|
// application/xhtml+xml
|
||||||
|
// ^^^^^^^^^^^^^^^^^^^^^ - part #1
|
||||||
|
//
|
||||||
|
// application/xml;q=0.9
|
||||||
|
// ^^^^^ - part #2
|
||||||
|
// ^^^^^^^^^^^^^^^ - part #1
|
||||||
|
//
|
||||||
|
// */*;q=0.8
|
||||||
|
// ^^^^^ - part #2
|
||||||
|
// ^^^ - part #1
|
||||||
|
if parts := strings.SplitN(strings.TrimSpace(segment), ";", 2); len(parts) > 0 { //nolint:mnd,nestif
|
||||||
|
if parts[0] == "*/*" {
|
||||||
|
continue // skip the wildcard
|
||||||
|
}
|
||||||
|
|
||||||
|
var p = piece{mimeType: parts[0], weight: 1_0} //nolint:mnd // by default the weight is 10 (1.0 in float)
|
||||||
|
|
||||||
|
if len(parts) > 1 { // we need to extract the weight
|
||||||
|
// trim the `q=` prefix and try to parse the weight value
|
||||||
|
if weight, err := strconv.ParseFloat(strings.TrimPrefix(strings.ToLower(parts[1]), "q="), 32); err == nil {
|
||||||
|
if weight = math.Round(weight*100) / 100; weight <= 1 && weight >= 0 { //nolint:mnd
|
||||||
|
p.weight = int(weight * 10) //nolint:mnd
|
||||||
|
} else {
|
||||||
|
p.weight = 0 // invalid weight, set it to 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pieces = append(pieces, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pieces) > 0 {
|
||||||
|
slices.SortStableFunc(pieces, func(a, b piece) int { return b.weight - a.weight })
|
||||||
|
|
||||||
|
return mimeTypeToPreferredFormat(pieces[0].mimeType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return unknownFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
// mimeTypeToPreferredFormat converts a MIME type to a preferred format, using non-string comparison.
|
||||||
|
func mimeTypeToPreferredFormat(mimeType string) preferredFormat {
|
||||||
|
switch value := strings.ToLower(mimeType); {
|
||||||
|
case strings.Contains(value, "/json"): // application/json text/json
|
||||||
|
return jsonFormat
|
||||||
|
case strings.Contains(value, "/xml"): // application/xml text/xml
|
||||||
|
return xmlFormat
|
||||||
|
case strings.Contains(value, "+xml"): // application/xhtml+xml
|
||||||
|
return xmlFormat
|
||||||
|
case strings.Contains(value, "/html"): // text/html
|
||||||
|
return htmlFormat
|
||||||
|
case strings.Contains(value, "/plain"): // text/plain
|
||||||
|
return plainTextFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
return unknownFormat
|
||||||
|
}
|
106
internal/http/handlers/error_page/format_test.go
Normal file
106
internal/http/handlers/error_page/format_test.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package error_page
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_detectPreferredFormatForClient(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for name, _tt := range map[string]struct {
|
||||||
|
giveHeaders http.Header
|
||||||
|
wantFormat preferredFormat
|
||||||
|
}{
|
||||||
|
"content type json": {
|
||||||
|
giveHeaders: http.Header{"Content-Type": {"application/jSoN"}},
|
||||||
|
wantFormat: jsonFormat,
|
||||||
|
},
|
||||||
|
"content type xml": {
|
||||||
|
giveHeaders: http.Header{"Content-Type": {"application/xml; charset=UTF-8"}},
|
||||||
|
wantFormat: xmlFormat,
|
||||||
|
},
|
||||||
|
"content type html": {
|
||||||
|
giveHeaders: http.Header{"Content-Type": {"text/hTmL; charset=utf-8"}},
|
||||||
|
wantFormat: htmlFormat,
|
||||||
|
},
|
||||||
|
"content type plain": {
|
||||||
|
giveHeaders: http.Header{"Content-Type": {"text/plaIN"}},
|
||||||
|
wantFormat: plainTextFormat,
|
||||||
|
},
|
||||||
|
|
||||||
|
"accept json": {
|
||||||
|
giveHeaders: http.Header{"Accept": {"application/jsoN,*/*;q=0.8"}},
|
||||||
|
wantFormat: jsonFormat,
|
||||||
|
},
|
||||||
|
"accept xml, depends on weight": {
|
||||||
|
giveHeaders: http.Header{"Accept": {"text/html;q=0.5,application/xhtml+xml;q=0.9,application/xml;q=1,*/*;q=0.8"}},
|
||||||
|
wantFormat: xmlFormat,
|
||||||
|
},
|
||||||
|
"accept json, depends on weight": {
|
||||||
|
giveHeaders: http.Header{"Accept": {"application/jsoN,*/*;q=0.8"}},
|
||||||
|
wantFormat: jsonFormat,
|
||||||
|
},
|
||||||
|
"accept xml": {
|
||||||
|
giveHeaders: http.Header{"Accept": {"application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"}},
|
||||||
|
wantFormat: xmlFormat,
|
||||||
|
},
|
||||||
|
"accept html": {
|
||||||
|
giveHeaders: http.Header{"Accept": {"text/html, application/xhtml+xml, application/xml;q=0.9, image/avif, image/webp, */*;q=0.8"}},
|
||||||
|
wantFormat: htmlFormat,
|
||||||
|
},
|
||||||
|
"accept plain": {
|
||||||
|
giveHeaders: http.Header{"Accept": {"text/plaiN,text/html,application/xml;q=0.9,,,*/*;q=0.8"}},
|
||||||
|
wantFormat: plainTextFormat,
|
||||||
|
},
|
||||||
|
"accept json, weighted values only": {
|
||||||
|
giveHeaders: http.Header{"Accept": {"application/jsoN;Q=0.1,text/html;q=1.1,application/xml;q=-1,*/*;q=0.8"}},
|
||||||
|
wantFormat: jsonFormat,
|
||||||
|
},
|
||||||
|
|
||||||
|
"x-format json, depends on weight": {
|
||||||
|
giveHeaders: http.Header{"X-Format": {"application/jsoN,*/*;q=0.8"}},
|
||||||
|
wantFormat: jsonFormat,
|
||||||
|
},
|
||||||
|
"x-format xml": {
|
||||||
|
giveHeaders: http.Header{"X-Format": {"application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"}},
|
||||||
|
wantFormat: xmlFormat,
|
||||||
|
},
|
||||||
|
|
||||||
|
"content type has priority over accept": {
|
||||||
|
giveHeaders: http.Header{"Content-Type": {"text/plain"}, "Accept": {"application/xml"}},
|
||||||
|
wantFormat: plainTextFormat,
|
||||||
|
},
|
||||||
|
"accept has priority over x-format": {
|
||||||
|
giveHeaders: http.Header{"Accept": {"application/xml"}, "X-Format": {"text/plain"}},
|
||||||
|
wantFormat: plainTextFormat,
|
||||||
|
},
|
||||||
|
|
||||||
|
"empty headers": {
|
||||||
|
giveHeaders: nil,
|
||||||
|
},
|
||||||
|
"empty content type": {
|
||||||
|
giveHeaders: http.Header{"Content-Type": {" "}},
|
||||||
|
},
|
||||||
|
"wrong content type": {
|
||||||
|
giveHeaders: http.Header{"Content-Type": {"multipart/form-data; boundary=something"}},
|
||||||
|
},
|
||||||
|
"wrong accept": {
|
||||||
|
giveHeaders: http.Header{"Accept": {";q=foobar,bar/baz;;;;;application/xml"}},
|
||||||
|
},
|
||||||
|
"none on invalid input": {
|
||||||
|
giveHeaders: http.Header{"Content-Type": {"foo/bar; charset=utf-8"}, "Accept": {"foo/bar; charset=utf-8"}},
|
||||||
|
},
|
||||||
|
"completely unknown": {
|
||||||
|
giveHeaders: http.Header{"Content-Type": {"😀"}, "Accept": {"😄"}, "X-Format": {"😍"}},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
tt := _tt
|
||||||
|
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.wantFormat, detectPreferredFormatForClient(tt.giveHeaders))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -3,9 +3,6 @@ package error_page
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gh.tarampamp.am/error-pages/internal/config"
|
"gh.tarampamp.am/error-pages/internal/config"
|
||||||
)
|
)
|
||||||
@ -14,9 +11,9 @@ func New(cfg *config.Config) http.Handler {
|
|||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var code uint16
|
var code uint16
|
||||||
|
|
||||||
if fromUrl, okUrl := ExtractCodeFromURL(r.URL.Path); okUrl {
|
if fromUrl, okUrl := extractCodeFromURL(r.URL.Path); okUrl {
|
||||||
code = fromUrl
|
code = fromUrl
|
||||||
} else if fromHeader, okHeaders := ExtractCodeFromHeaders(r.Header); okHeaders {
|
} else if fromHeader, okHeaders := extractCodeFromHeaders(r.Header); okHeaders {
|
||||||
code = fromHeader
|
code = fromHeader
|
||||||
} else {
|
} else {
|
||||||
code = cfg.DefaultCodeToRender
|
code = cfg.DefaultCodeToRender
|
||||||
@ -30,55 +27,38 @@ func New(cfg *config.Config) http.Handler {
|
|||||||
httpCode = http.StatusOK
|
httpCode = http.StatusOK
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8") // TODO: should depends on requested type
|
var format = detectPreferredFormatForClient(r.Header)
|
||||||
|
|
||||||
|
switch headerName := "Content-Type"; format {
|
||||||
|
case jsonFormat:
|
||||||
|
w.Header().Set(headerName, "application/json; charset=utf-8")
|
||||||
|
case xmlFormat:
|
||||||
|
w.Header().Set(headerName, "application/xml; charset=utf-8")
|
||||||
|
case htmlFormat:
|
||||||
|
w.Header().Set(headerName, "text/html; charset=utf-8")
|
||||||
|
case plainTextFormat:
|
||||||
|
w.Header().Set(headerName, "text/plain; charset=utf-8")
|
||||||
|
default:
|
||||||
|
w.Header().Set(headerName, "text/html; charset=utf-8")
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag
|
||||||
|
// disallow indexing of the error pages
|
||||||
|
w.Header().Set("X-Robots-Tag", "noindex")
|
||||||
|
|
||||||
|
if code >= 500 && code < 600 {
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
||||||
|
// tell the client (search crawler) to retry the request after 120 seconds, it makes sense for the 5xx errors
|
||||||
|
w.Header().Set("Retry-After", "120")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, proxyHeader := range cfg.ProxyHeaders {
|
||||||
|
if value := r.Header.Get(proxyHeader); value != "" {
|
||||||
|
w.Header().Set(proxyHeader, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
w.WriteHeader(httpCode)
|
w.WriteHeader(httpCode)
|
||||||
_, _ = w.Write([]byte(fmt.Sprintf("<html>error page for the code %d</html>", code)))
|
_, _ = w.Write([]byte(fmt.Sprintf("<html>error page for the code %d</html>", code)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractCodeFromURL extracts the error code from the given URL.
|
|
||||||
func ExtractCodeFromURL(url string) (uint16, bool) {
|
|
||||||
var parts = strings.SplitN(strings.TrimLeft(url, "/"), "/", 1)
|
|
||||||
|
|
||||||
if len(parts) == 0 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
fileName = parts[0]
|
|
||||||
ext = strings.ToLower(filepath.Ext(fileName)) // ".html", ".htm", ".%something%" or an empty string
|
|
||||||
)
|
|
||||||
|
|
||||||
if ext != "" && ext != ".html" && ext != ".htm" {
|
|
||||||
return 0, false
|
|
||||||
} else if ext != "" {
|
|
||||||
fileName = strings.TrimSuffix(fileName, ext)
|
|
||||||
}
|
|
||||||
|
|
||||||
if code, err := strconv.ParseUint(fileName, 10, 16); err == nil && code > 0 && code < 999 {
|
|
||||||
return uint16(code), true
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// URLContainsCode checks if the given URL contains an error code.
|
|
||||||
func URLContainsCode(url string) (ok bool) { _, ok = ExtractCodeFromURL(url); return } //nolint:nlreturn
|
|
||||||
|
|
||||||
// ExtractCodeFromHeaders extracts the error code from the given headers.
|
|
||||||
func ExtractCodeFromHeaders(headers http.Header) (uint16, bool) {
|
|
||||||
if value := headers.Get("X-Code"); len(value) > 0 && len(value) <= 3 {
|
|
||||||
if code, err := strconv.ParseUint(value, 10, 16); err == nil && code > 0 && code < 999 {
|
|
||||||
return uint16(code), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeadersContainCode checks if the given headers contain an error code.
|
|
||||||
func HeadersContainCode(headers http.Header) (ok bool) {
|
|
||||||
_, ok = ExtractCodeFromHeaders(headers)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user