gitea源码

sanitize.go 2.4KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package util
  4. import (
  5. "bytes"
  6. "unicode"
  7. )
  8. type sanitizedError struct {
  9. err error
  10. }
  11. func (err sanitizedError) Error() string {
  12. return SanitizeCredentialURLs(err.err.Error())
  13. }
  14. func (err sanitizedError) Unwrap() error {
  15. return err.err
  16. }
  17. // SanitizeErrorCredentialURLs wraps the error and make sure the returned error message doesn't contain sensitive credentials in URLs
  18. func SanitizeErrorCredentialURLs(err error) error {
  19. return sanitizedError{err: err}
  20. }
  21. const userPlaceholder = "sanitized-credential"
  22. var schemeSep = []byte("://")
  23. // SanitizeCredentialURLs remove all credentials in URLs (starting with "scheme://") for the input string: "https://user:pass@domain.com" => "https://sanitized-credential@domain.com"
  24. func SanitizeCredentialURLs(s string) string {
  25. bs := UnsafeStringToBytes(s)
  26. schemeSepPos := bytes.Index(bs, schemeSep)
  27. if schemeSepPos == -1 || bytes.IndexByte(bs[schemeSepPos:], '@') == -1 {
  28. return s // fast return if there is no URL scheme or no userinfo
  29. }
  30. out := make([]byte, 0, len(bs)+len(userPlaceholder))
  31. for schemeSepPos != -1 {
  32. schemeSepPos += 3 // skip the "://"
  33. sepAtPos := -1 // the possible '@' position: "https://foo@[^here]host"
  34. sepEndPos := schemeSepPos // the possible end position: "The https://host[^here] in log for test"
  35. sepLoop:
  36. for ; sepEndPos < len(bs); sepEndPos++ {
  37. c := bs[sepEndPos]
  38. if ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || ('0' <= c && c <= '9') {
  39. continue
  40. }
  41. switch c {
  42. case '@':
  43. sepAtPos = sepEndPos
  44. case '-', '.', '_', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '%':
  45. continue // due to RFC 3986, userinfo can contain - . _ ~ ! $ & ' ( ) * + , ; = : and any percent-encoded chars
  46. default:
  47. break sepLoop // if it is an invalid char for URL (eg: space, '/', and others), stop the loop
  48. }
  49. }
  50. // if there is '@', and the string is like "s://u@h", then hide the "u" part
  51. if sepAtPos != -1 && (schemeSepPos >= 4 && unicode.IsLetter(rune(bs[schemeSepPos-4]))) && sepAtPos-schemeSepPos > 0 && sepEndPos-sepAtPos > 0 {
  52. out = append(out, bs[:schemeSepPos]...)
  53. out = append(out, userPlaceholder...)
  54. out = append(out, bs[sepAtPos:sepEndPos]...)
  55. } else {
  56. out = append(out, bs[:sepEndPos]...)
  57. }
  58. bs = bs[sepEndPos:]
  59. schemeSepPos = bytes.Index(bs, schemeSep)
  60. }
  61. out = append(out, bs...)
  62. return UnsafeBytesToString(out)
  63. }