Stable APIs for Go. https://go.code.as
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. package main
  2. import (
  3. "errors"
  4. "flag"
  5. "fmt"
  6. "io/ioutil"
  7. "log"
  8. "net/http"
  9. "os"
  10. "regexp"
  11. "strconv"
  12. "strings"
  13. "text/template"
  14. )
  15. var httpFlag = flag.String("http", ":8080", "Serve HTTP at given address")
  16. var httpsFlag = flag.String("https", "", "Serve HTTPS at given address")
  17. var certFlag = flag.String("cert", "", "Use the provided TLS certificate")
  18. var keyFlag = flag.String("key", "", "Use the provided TLS key")
  19. func main() {
  20. if err := run(); err != nil {
  21. fmt.Fprintf(os.Stderr, "error: %v\n", err)
  22. os.Exit(1)
  23. }
  24. }
  25. func run() error {
  26. flag.Parse()
  27. if len(flag.Args()) > 0 {
  28. return fmt.Errorf("too many arguments: %s", flag.Args()[0])
  29. }
  30. http.HandleFunc("/", handler)
  31. if *httpFlag == "" && *httpsFlag == "" {
  32. return fmt.Errorf("must provide -http and/or -https")
  33. }
  34. if (*httpsFlag != "" || *certFlag != "" || *keyFlag != "") && (*httpsFlag == "" || *certFlag == "" || *keyFlag == "") {
  35. return fmt.Errorf("-https -cert and -key must be used together")
  36. }
  37. ch := make(chan error, 2)
  38. if *httpFlag != "" {
  39. go func() {
  40. ch <- http.ListenAndServe(*httpFlag, nil)
  41. }()
  42. }
  43. if *httpsFlag != "" {
  44. go func() {
  45. ch <- http.ListenAndServeTLS(*httpsFlag, *certFlag, *keyFlag, nil)
  46. }()
  47. }
  48. return <-ch
  49. }
  50. var tmpl = template.Must(template.New("").Parse(`
  51. <html>
  52. <head>
  53. <meta name="go-import" content="{{.PkgRoot}} git {{.GitRoot}}">
  54. </head>
  55. <body>
  56. <script>
  57. window.location = "http://godoc.org/{{.PkgPath}}" + window.location.hash;
  58. </script>
  59. </body>
  60. </html>
  61. `))
  62. type Repo struct {
  63. GitRoot string
  64. HubRoot string
  65. PkgRoot string
  66. PkgPath string
  67. Version Version
  68. }
  69. var patternOld = regexp.MustCompile(`^/([a-z0-9][-a-z0-9]+/)?((?:v0|v[1-9][0-9]*)(?:\.0|\.[1-9][0-9]*){0,2})/([a-zA-Z][-a-zA-Z0-9]*)(?:\.git)?((?:/[a-zA-Z][-a-zA-Z0-9]*)*)$`)
  70. var patternNew = regexp.MustCompile(`^/([a-z0-9][-a-z0-9]+/)?([a-zA-Z][-a-zA-Z0-9]*)\.((?:v0|v[1-9][0-9]*)(?:\.0|\.[1-9][0-9]*){0,2})(?:\.git)?((?:/[a-zA-Z][-a-zA-Z0-9]*)*)$`)
  71. func handler(resp http.ResponseWriter, req *http.Request) {
  72. if req.URL.Path == "/health-check" {
  73. resp.Write([]byte("ok"))
  74. return
  75. }
  76. log.Printf("%s requested %s", req.RemoteAddr, req.URL)
  77. if req.URL.Path == "/" {
  78. resp.Header().Set("Location", "http://labix.org/gopkg.in")
  79. resp.WriteHeader(http.StatusTemporaryRedirect)
  80. return
  81. }
  82. m := patternNew.FindStringSubmatch(req.URL.Path)
  83. compat := false
  84. if m == nil {
  85. m = patternOld.FindStringSubmatch(req.URL.Path)
  86. if m == nil {
  87. sendNotFound(resp, "Unsupported URL pattern; see the documentation at gopkg.in for details.")
  88. return
  89. }
  90. m[2], m[3] = m[3], m[2]
  91. compat = true
  92. }
  93. if strings.Contains(m[3], ".") {
  94. sendNotFound(resp, "Import paths take the major version only (.%s instead of .%s); see docs at gopkg.in for the reasoning.",
  95. m[3][:strings.Index(m[3], ".")], m[3])
  96. return
  97. }
  98. var repo *Repo
  99. if m[1] == "" {
  100. repo = &Repo{
  101. GitRoot: "https://github.com/go-" + m[2] + "/" + m[2],
  102. PkgRoot: "gopkg.in/" + m[2] + "." + m[3],
  103. PkgPath: "gopkg.in/" + m[2] + "." + m[3] + m[4],
  104. }
  105. if compat {
  106. repo.PkgRoot = "gopkg.in/" + m[3] + "/" + m[2]
  107. repo.PkgPath = "gopkg.in/" + m[3] + "/" + m[2] + m[4]
  108. }
  109. } else {
  110. repo = &Repo{
  111. GitRoot: "https://github.com/" + m[1] + m[2],
  112. PkgRoot: "gopkg.in/" + m[1] + m[2] + "." + m[3],
  113. PkgPath: "gopkg.in/" + m[1] + m[2] + "." + m[3] + m[4],
  114. }
  115. if compat {
  116. repo.PkgRoot = "gopkg.in/" + m[1] + m[3] + "/" + m[2]
  117. repo.PkgPath = "gopkg.in/" + m[1] + m[3] + "/" + m[2] + m[4]
  118. }
  119. }
  120. var ok bool
  121. repo.Version, ok = parseVersion(m[3])
  122. if !ok {
  123. sendNotFound(resp, "Version %q improperly considered invalid; please warn the service maintainers.", m[3])
  124. return
  125. }
  126. repo.HubRoot = repo.GitRoot
  127. // Run this concurrently to avoid waiting later.
  128. nameVersioned := nameHasVersion(repo)
  129. refs, err := hackedRefs(repo)
  130. switch err {
  131. case nil:
  132. repo.GitRoot = "https://" + repo.PkgRoot
  133. case ErrNoRepo:
  134. if <-nameVersioned {
  135. v := repo.Version.String()
  136. repo.GitRoot += "-" + v
  137. repo.HubRoot += "-" + v
  138. break
  139. }
  140. sendNotFound(resp, "GitHub repository not found at %s", repo.HubRoot)
  141. return
  142. case ErrNoVersion:
  143. v := repo.Version.String()
  144. if repo.Version.Minor == -1 {
  145. sendNotFound(resp, `GitHub repository at %s has no branch or tag "%s", "%s.N" or "%s.N.M"`, repo.HubRoot, v, v, v)
  146. } else if repo.Version.Patch == -1 {
  147. sendNotFound(resp, `GitHub repository at %s has no branch or tag "%s" or "%s.N"`, repo.HubRoot, v, v)
  148. } else {
  149. sendNotFound(resp, `GitHub repository at %s has no branch or tag "%s"`, repo.HubRoot, v)
  150. }
  151. return
  152. default:
  153. resp.WriteHeader(http.StatusBadGateway)
  154. resp.Write([]byte(fmt.Sprintf("Cannot obtain refs from GitHub: %v", err)))
  155. return
  156. }
  157. if m[4] == "/git-upload-pack" {
  158. resp.Header().Set("Location", repo.HubRoot+"/git-upload-pack")
  159. resp.WriteHeader(http.StatusMovedPermanently)
  160. return
  161. }
  162. if m[4] == "/info/refs" {
  163. resp.Header().Set("Content-Type", "application/x-git-upload-pack-advertisement")
  164. resp.Write(refs)
  165. return
  166. }
  167. resp.Header().Set("Content-Type", "text/html")
  168. tmpl.Execute(resp, repo)
  169. }
  170. func sendNotFound(resp http.ResponseWriter, msg string, args ...interface{}) {
  171. if len(args) > 0 {
  172. msg = fmt.Sprintf(msg, args...)
  173. }
  174. resp.WriteHeader(http.StatusNotFound)
  175. resp.Write([]byte(msg))
  176. }
  177. // TODO Timeouts for these http interactions. Use the new support coming in 1.3.
  178. const refsSuffix = ".git/info/refs?service=git-upload-pack"
  179. // Obsolete. Drop this.
  180. func nameHasVersion(repo *Repo) chan bool {
  181. ch := make(chan bool, 1)
  182. go func() {
  183. resp, err := http.Head(repo.HubRoot + "-" + repo.Version.String() + refsSuffix)
  184. if err != nil {
  185. ch <- false
  186. }
  187. if resp.Body != nil {
  188. resp.Body.Close()
  189. }
  190. ch <- resp.StatusCode == 200
  191. }()
  192. return ch
  193. }
  194. var ErrNoRepo = errors.New("repository not found in github")
  195. var ErrNoVersion = errors.New("version reference not found in github")
  196. func hackedRefs(repo *Repo) (data []byte, err error) {
  197. resp, err := http.Get(repo.HubRoot + refsSuffix)
  198. if err != nil {
  199. return nil, fmt.Errorf("cannot talk to GitHub: %v", err)
  200. }
  201. switch resp.StatusCode {
  202. case 200:
  203. defer resp.Body.Close()
  204. case 401, 404:
  205. return nil, ErrNoRepo
  206. default:
  207. return nil, fmt.Errorf("error from GitHub: %v", resp.Status)
  208. }
  209. data, err = ioutil.ReadAll(resp.Body)
  210. if err != nil {
  211. return nil, fmt.Errorf("error reading from GitHub: %v", err)
  212. }
  213. var mrefi, mrefj int
  214. var vrefi, vrefj int
  215. var vrefv = InvalidVersion
  216. var unversioned = true
  217. sdata := string(data)
  218. for i, j := 0, 0; i < len(data); i = j {
  219. size, err := strconv.ParseInt(sdata[i:i+4], 16, 32)
  220. if err != nil {
  221. return nil, fmt.Errorf("cannot parse refs line size: %s", string(data[i:i+4]))
  222. }
  223. if size == 0 {
  224. size = 4
  225. }
  226. j = i + int(size)
  227. if j > len(sdata) {
  228. return nil, fmt.Errorf("incomplete refs data received from GitHub")
  229. }
  230. if sdata[0] == '#' {
  231. continue
  232. }
  233. hashi := i + 4
  234. hashj := strings.IndexByte(sdata[hashi:j], ' ')
  235. if hashj < 0 || hashj != 40 {
  236. continue
  237. }
  238. hashj += hashi
  239. namei := hashj + 1
  240. namej := strings.IndexAny(sdata[namei:j], "\n\x00")
  241. if namej < 0 {
  242. namej = j
  243. } else {
  244. namej += namei
  245. }
  246. name := sdata[namei:namej]
  247. if name == "refs/heads/master" {
  248. mrefi = hashi
  249. mrefj = hashj
  250. }
  251. if strings.HasPrefix(name, "refs/heads/v") || strings.HasPrefix(name, "refs/tags/v") {
  252. v, ok := parseVersion(name[strings.IndexByte(name, 'v'):])
  253. if ok && repo.Version.Contains(v) && (!vrefv.IsValid() || vrefv.Less(v)) {
  254. vrefv = v
  255. vrefi = hashi
  256. vrefj = hashj
  257. }
  258. if ok {
  259. unversioned = false
  260. }
  261. }
  262. }
  263. // If there were absolutely no versions, and v0 was requested, accept the master as-is.
  264. if unversioned && repo.Version == (Version{0, -1, -1}) {
  265. return data, nil
  266. }
  267. if mrefi == 0 || vrefi == 0 {
  268. return nil, ErrNoVersion
  269. }
  270. copy(data[mrefi:mrefj], data[vrefi:vrefj])
  271. return data, nil
  272. }