url.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. package ice
  2. import (
  3. "net"
  4. "net/url"
  5. "strconv"
  6. )
  7. // SchemeType indicates the type of server used in the ice.URL structure.
  8. type SchemeType int
  9. // Unknown defines default public constant to use for "enum" like struct
  10. // comparisons when no value was defined.
  11. const Unknown = iota
  12. const (
  13. // SchemeTypeSTUN indicates the URL represents a STUN server.
  14. SchemeTypeSTUN SchemeType = iota + 1
  15. // SchemeTypeSTUNS indicates the URL represents a STUNS (secure) server.
  16. SchemeTypeSTUNS
  17. // SchemeTypeTURN indicates the URL represents a TURN server.
  18. SchemeTypeTURN
  19. // SchemeTypeTURNS indicates the URL represents a TURNS (secure) server.
  20. SchemeTypeTURNS
  21. )
  22. // NewSchemeType defines a procedure for creating a new SchemeType from a raw
  23. // string naming the scheme type.
  24. func NewSchemeType(raw string) SchemeType {
  25. switch raw {
  26. case "stun":
  27. return SchemeTypeSTUN
  28. case "stuns":
  29. return SchemeTypeSTUNS
  30. case "turn":
  31. return SchemeTypeTURN
  32. case "turns":
  33. return SchemeTypeTURNS
  34. default:
  35. return SchemeType(Unknown)
  36. }
  37. }
  38. func (t SchemeType) String() string {
  39. switch t {
  40. case SchemeTypeSTUN:
  41. return "stun"
  42. case SchemeTypeSTUNS:
  43. return "stuns"
  44. case SchemeTypeTURN:
  45. return "turn"
  46. case SchemeTypeTURNS:
  47. return "turns"
  48. default:
  49. return ErrUnknownType.Error()
  50. }
  51. }
  52. // ProtoType indicates the transport protocol type that is used in the ice.URL
  53. // structure.
  54. type ProtoType int
  55. const (
  56. // ProtoTypeUDP indicates the URL uses a UDP transport.
  57. ProtoTypeUDP ProtoType = iota + 1
  58. // ProtoTypeTCP indicates the URL uses a TCP transport.
  59. ProtoTypeTCP
  60. )
  61. // NewProtoType defines a procedure for creating a new ProtoType from a raw
  62. // string naming the transport protocol type.
  63. func NewProtoType(raw string) ProtoType {
  64. switch raw {
  65. case "udp":
  66. return ProtoTypeUDP
  67. case "tcp":
  68. return ProtoTypeTCP
  69. default:
  70. return ProtoType(Unknown)
  71. }
  72. }
  73. func (t ProtoType) String() string {
  74. switch t {
  75. case ProtoTypeUDP:
  76. return "udp"
  77. case ProtoTypeTCP:
  78. return "tcp"
  79. default:
  80. return ErrUnknownType.Error()
  81. }
  82. }
  83. // URL represents a STUN (rfc7064) or TURN (rfc7065) URL
  84. type URL struct {
  85. Scheme SchemeType
  86. Host string
  87. Port int
  88. Username string
  89. Password string
  90. Proto ProtoType
  91. }
  92. // ParseURL parses a STUN or TURN urls following the ABNF syntax described in
  93. // https://tools.ietf.org/html/rfc7064 and https://tools.ietf.org/html/rfc7065
  94. // respectively.
  95. func ParseURL(raw string) (*URL, error) { //nolint:gocognit
  96. rawParts, err := url.Parse(raw)
  97. if err != nil {
  98. return nil, err
  99. }
  100. var u URL
  101. u.Scheme = NewSchemeType(rawParts.Scheme)
  102. if u.Scheme == SchemeType(Unknown) {
  103. return nil, ErrSchemeType
  104. }
  105. var rawPort string
  106. if u.Host, rawPort, err = net.SplitHostPort(rawParts.Opaque); err != nil {
  107. if e, ok := err.(*net.AddrError); ok {
  108. if e.Err == "missing port in address" {
  109. nextRawURL := u.Scheme.String() + ":" + rawParts.Opaque
  110. switch {
  111. case u.Scheme == SchemeTypeSTUN || u.Scheme == SchemeTypeTURN:
  112. nextRawURL += ":3478"
  113. if rawParts.RawQuery != "" {
  114. nextRawURL += "?" + rawParts.RawQuery
  115. }
  116. return ParseURL(nextRawURL)
  117. case u.Scheme == SchemeTypeSTUNS || u.Scheme == SchemeTypeTURNS:
  118. nextRawURL += ":5349"
  119. if rawParts.RawQuery != "" {
  120. nextRawURL += "?" + rawParts.RawQuery
  121. }
  122. return ParseURL(nextRawURL)
  123. }
  124. }
  125. }
  126. return nil, err
  127. }
  128. if u.Host == "" {
  129. return nil, ErrHost
  130. }
  131. if u.Port, err = strconv.Atoi(rawPort); err != nil {
  132. return nil, ErrPort
  133. }
  134. switch u.Scheme {
  135. case SchemeTypeSTUN:
  136. qArgs, err := url.ParseQuery(rawParts.RawQuery)
  137. if err != nil || len(qArgs) > 0 {
  138. return nil, ErrSTUNQuery
  139. }
  140. u.Proto = ProtoTypeUDP
  141. case SchemeTypeSTUNS:
  142. qArgs, err := url.ParseQuery(rawParts.RawQuery)
  143. if err != nil || len(qArgs) > 0 {
  144. return nil, ErrSTUNQuery
  145. }
  146. u.Proto = ProtoTypeTCP
  147. case SchemeTypeTURN:
  148. proto, err := parseProto(rawParts.RawQuery)
  149. if err != nil {
  150. return nil, err
  151. }
  152. u.Proto = proto
  153. if u.Proto == ProtoType(Unknown) {
  154. u.Proto = ProtoTypeUDP
  155. }
  156. case SchemeTypeTURNS:
  157. proto, err := parseProto(rawParts.RawQuery)
  158. if err != nil {
  159. return nil, err
  160. }
  161. u.Proto = proto
  162. if u.Proto == ProtoType(Unknown) {
  163. u.Proto = ProtoTypeTCP
  164. }
  165. }
  166. return &u, nil
  167. }
  168. func parseProto(raw string) (ProtoType, error) {
  169. qArgs, err := url.ParseQuery(raw)
  170. if err != nil || len(qArgs) > 1 {
  171. return ProtoType(Unknown), ErrInvalidQuery
  172. }
  173. var proto ProtoType
  174. if rawProto := qArgs.Get("transport"); rawProto != "" {
  175. if proto = NewProtoType(rawProto); proto == ProtoType(0) {
  176. return ProtoType(Unknown), ErrProtoType
  177. }
  178. return proto, nil
  179. }
  180. if len(qArgs) > 0 {
  181. return ProtoType(Unknown), ErrInvalidQuery
  182. }
  183. return proto, nil
  184. }
  185. func (u URL) String() string {
  186. rawURL := u.Scheme.String() + ":" + net.JoinHostPort(u.Host, strconv.Itoa(u.Port))
  187. if u.Scheme == SchemeTypeTURN || u.Scheme == SchemeTypeTURNS {
  188. rawURL += "?transport=" + u.Proto.String()
  189. }
  190. return rawURL
  191. }
  192. // IsSecure returns whether the this URL's scheme describes secure scheme or not.
  193. func (u URL) IsSecure() bool {
  194. return u.Scheme == SchemeTypeSTUNS || u.Scheme == SchemeTypeTURNS
  195. }