path_replace.go 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. package httpbinding
  2. import (
  3. "bytes"
  4. "fmt"
  5. )
  6. const (
  7. uriTokenStart = '{'
  8. uriTokenStop = '}'
  9. uriTokenSkip = '+'
  10. )
  11. func bufCap(b []byte, n int) []byte {
  12. if cap(b) < n {
  13. return make([]byte, 0, n)
  14. }
  15. return b[0:0]
  16. }
  17. // replacePathElement replaces a single element in the path []byte.
  18. // Escape is used to control whether the value will be escaped using Amazon path escape style.
  19. func replacePathElement(path, fieldBuf []byte, key, val string, escape bool) ([]byte, []byte, error) {
  20. fieldBuf = bufCap(fieldBuf, len(key)+3) // { <key> [+] }
  21. fieldBuf = append(fieldBuf, uriTokenStart)
  22. fieldBuf = append(fieldBuf, key...)
  23. start := bytes.Index(path, fieldBuf)
  24. end := start + len(fieldBuf)
  25. if start < 0 || len(path[end:]) == 0 {
  26. // TODO what to do about error?
  27. return path, fieldBuf, fmt.Errorf("invalid path index, start=%d,end=%d. %s", start, end, path)
  28. }
  29. encodeSep := true
  30. if path[end] == uriTokenSkip {
  31. // '+' token means do not escape slashes
  32. encodeSep = false
  33. end++
  34. }
  35. if escape {
  36. val = EscapePath(val, encodeSep)
  37. }
  38. if path[end] != uriTokenStop {
  39. return path, fieldBuf, fmt.Errorf("invalid path element, does not contain token stop, %s", path)
  40. }
  41. end++
  42. fieldBuf = bufCap(fieldBuf, len(val))
  43. fieldBuf = append(fieldBuf, val...)
  44. keyLen := end - start
  45. valLen := len(fieldBuf)
  46. if keyLen == valLen {
  47. copy(path[start:], fieldBuf)
  48. return path, fieldBuf, nil
  49. }
  50. newLen := len(path) + (valLen - keyLen)
  51. if len(path) < newLen {
  52. path = path[:cap(path)]
  53. }
  54. if cap(path) < newLen {
  55. newURI := make([]byte, newLen)
  56. copy(newURI, path)
  57. path = newURI
  58. }
  59. // shift
  60. copy(path[start+valLen:], path[end:])
  61. path = path[:newLen]
  62. copy(path[start:], fieldBuf)
  63. return path, fieldBuf, nil
  64. }
  65. // EscapePath escapes part of a URL path in Amazon style.
  66. func EscapePath(path string, encodeSep bool) string {
  67. var buf bytes.Buffer
  68. for i := 0; i < len(path); i++ {
  69. c := path[i]
  70. if noEscape[c] || (c == '/' && !encodeSep) {
  71. buf.WriteByte(c)
  72. } else {
  73. fmt.Fprintf(&buf, "%%%02X", c)
  74. }
  75. }
  76. return buf.String()
  77. }
  78. var noEscape [256]bool
  79. func init() {
  80. for i := 0; i < len(noEscape); i++ {
  81. // AWS expects every character except these to be escaped
  82. noEscape[i] = (i >= 'A' && i <= 'Z') ||
  83. (i >= 'a' && i <= 'z') ||
  84. (i >= '0' && i <= '9') ||
  85. i == '-' ||
  86. i == '.' ||
  87. i == '_' ||
  88. i == '~'
  89. }
  90. }