Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 

126 linhas
3.3 KiB

  1. // Copyright 2015 Google Inc. All rights reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package header
  15. import (
  16. "crypto/rand"
  17. "fmt"
  18. "io"
  19. "net/http"
  20. "regexp"
  21. "strings"
  22. "github.com/google/martian"
  23. )
  24. const viaLoopKey = "via.LoopDetection"
  25. var whitespace = regexp.MustCompile("[\t ]+")
  26. // ViaModifier is a header modifier that checks for proxy redirect loops.
  27. type ViaModifier struct {
  28. requestedBy string
  29. boundary string
  30. }
  31. // NewViaModifier returns a new Via modifier.
  32. func NewViaModifier(requestedBy string) *ViaModifier {
  33. return &ViaModifier{
  34. requestedBy: requestedBy,
  35. boundary: randomBoundary(),
  36. }
  37. }
  38. // ModifyRequest sets the Via header and provides loop-detection. If Via is
  39. // already present, it will be appended to the existing value. If a loop is
  40. // detected an error is added to the context and the request round trip is
  41. // skipped.
  42. //
  43. // http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-14#section-9.9
  44. func (m *ViaModifier) ModifyRequest(req *http.Request) error {
  45. via := fmt.Sprintf("%d.%d %s-%s", req.ProtoMajor, req.ProtoMinor, m.requestedBy, m.boundary)
  46. if v := req.Header.Get("Via"); v != "" {
  47. if m.hasLoop(v) {
  48. err := fmt.Errorf("via: detected request loop, header contains %s", via)
  49. ctx := martian.NewContext(req)
  50. ctx.Set(viaLoopKey, err)
  51. ctx.SkipRoundTrip()
  52. return err
  53. }
  54. via = fmt.Sprintf("%s, %s", v, via)
  55. }
  56. req.Header.Set("Via", via)
  57. return nil
  58. }
  59. // ModifyResponse sets the status code to 400 Bad Request if a loop was
  60. // detected in the request.
  61. func (m *ViaModifier) ModifyResponse(res *http.Response) error {
  62. ctx := martian.NewContext(res.Request)
  63. if err, _ := ctx.Get(viaLoopKey); err != nil {
  64. res.StatusCode = 400
  65. res.Status = http.StatusText(400)
  66. return err.(error)
  67. }
  68. return nil
  69. }
  70. // hasLoop parses via and attempts to match requestedBy against the contained
  71. // pseudonyms/host:port pairs.
  72. func (m *ViaModifier) hasLoop(via string) bool {
  73. for _, v := range strings.Split(via, ",") {
  74. parts := whitespace.Split(strings.TrimSpace(v), 3)
  75. // No pseudonym or host:port, assume there is no loop.
  76. if len(parts) < 2 {
  77. continue
  78. }
  79. if fmt.Sprintf("%s-%s", m.requestedBy, m.boundary) == parts[1] {
  80. return true
  81. }
  82. }
  83. return false
  84. }
  85. // SetBoundary sets the boundary string (random 10 character by default) used to
  86. // disabiguate Martians that are chained together with identical requestedBy values.
  87. // This should only be used for testing.
  88. func (m *ViaModifier) SetBoundary(boundary string) {
  89. m.boundary = boundary
  90. }
  91. // randomBoundary generates a 10 character string to ensure that Martians that
  92. // are chained together with the same requestedBy value do not collide. This func
  93. // panics if io.Readfull fails.
  94. func randomBoundary() string {
  95. var buf [10]byte
  96. _, err := io.ReadFull(rand.Reader, buf[:])
  97. if err != nil {
  98. panic(err)
  99. }
  100. return fmt.Sprintf("%x", buf[:])
  101. }