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 години
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  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 gogetTemplate = template.Must(template.New("").Parse(`
  51. <html>
  52. <head>
  53. <meta name="go-import" content="{{.GopkgRoot}} git https://{{.GopkgRoot}}">
  54. </head>
  55. <body>
  56. go get {{.GopkgPath}}
  57. </body>
  58. </html>
  59. `))
  60. type Repo struct {
  61. User string
  62. PackageName string
  63. SubPath string
  64. OldFormat bool
  65. MajorVersion Version
  66. AllVersions VersionList
  67. }
  68. // GitHubRoot returns the repository root at GitHub, without a schema.
  69. func (repo *Repo) GitHubRoot() string {
  70. if repo.User == "" {
  71. return "github.com/go-" + repo.PackageName + "/" + repo.PackageName
  72. } else {
  73. return "github.com/" + repo.User + "/" + repo.PackageName
  74. }
  75. }
  76. // GopkgRoot returns the package root at gopkg.in, without a schema.
  77. func (repo *Repo) GopkgRoot() string {
  78. return repo.GopkgVersionRoot(repo.MajorVersion)
  79. }
  80. // GopkgRoot returns the package path at gopkg.in, without a schema.
  81. func (repo *Repo) GopkgPath() string {
  82. return repo.GopkgVersionRoot(repo.MajorVersion) + repo.SubPath
  83. }
  84. // GopkgVerisonRoot returns the package root in gopkg.in for the
  85. // provided version, without a schema.
  86. func (repo *Repo) GopkgVersionRoot(version Version) string {
  87. version.Minor = -1
  88. version.Patch = -1
  89. v := version.String()
  90. if repo.OldFormat {
  91. if repo.User == "" {
  92. return "gopkg.in/" + v + "/" + repo.PackageName
  93. } else {
  94. return "gopkg.in/" + repo.User + "/" + v + "/" + repo.PackageName
  95. }
  96. } else {
  97. if repo.User == "" {
  98. return "gopkg.in/" + repo.PackageName + "." + v
  99. } else {
  100. return "gopkg.in/" + repo.User + "/" + repo.PackageName + "." + v
  101. }
  102. }
  103. }
  104. 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]*)*)$`)
  105. 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]*)*)$`)
  106. func handler(resp http.ResponseWriter, req *http.Request) {
  107. if req.URL.Path == "/health-check" {
  108. resp.Write([]byte("ok"))
  109. return
  110. }
  111. log.Printf("%s requested %s", req.RemoteAddr, req.URL)
  112. if req.URL.Path == "/" {
  113. resp.Header().Set("Location", "http://labix.org/gopkg.in")
  114. resp.WriteHeader(http.StatusTemporaryRedirect)
  115. return
  116. }
  117. m := patternNew.FindStringSubmatch(req.URL.Path)
  118. oldFormat := false
  119. if m == nil {
  120. m = patternOld.FindStringSubmatch(req.URL.Path)
  121. if m == nil {
  122. sendNotFound(resp, "Unsupported URL pattern; see the documentation at gopkg.in for details.")
  123. return
  124. }
  125. m[2], m[3] = m[3], m[2]
  126. oldFormat = true
  127. }
  128. if strings.Contains(m[3], ".") {
  129. sendNotFound(resp, "Import paths take the major version only (.%s instead of .%s); see docs at gopkg.in for the reasoning.",
  130. m[3][:strings.Index(m[3], ".")], m[3])
  131. return
  132. }
  133. repo := &Repo{
  134. User: m[1],
  135. PackageName: m[2],
  136. SubPath: m[4],
  137. OldFormat: oldFormat,
  138. }
  139. var ok bool
  140. repo.MajorVersion, ok = parseVersion(m[3])
  141. if !ok {
  142. sendNotFound(resp, "Version %q improperly considered invalid; please warn the service maintainers.", m[3])
  143. return
  144. }
  145. var err error
  146. var refs []byte
  147. refs, repo.AllVersions, err = hackedRefs(repo)
  148. switch err {
  149. case nil:
  150. // all ok
  151. case ErrNoRepo:
  152. sendNotFound(resp, "GitHub repository not found at https://%s", repo.GitHubRoot())
  153. return
  154. case ErrNoVersion:
  155. v := repo.MajorVersion.String()
  156. sendNotFound(resp, `GitHub repository at https://%s has no branch or tag "%s", "%s.N" or "%s.N.M"`, repo.GitHubRoot(), v, v, v)
  157. return
  158. default:
  159. resp.WriteHeader(http.StatusBadGateway)
  160. resp.Write([]byte(fmt.Sprintf("Cannot obtain refs from GitHub: %v", err)))
  161. return
  162. }
  163. if repo.SubPath == "/git-upload-pack" {
  164. resp.Header().Set("Location", "https://"+repo.GitHubRoot()+"/git-upload-pack")
  165. resp.WriteHeader(http.StatusMovedPermanently)
  166. return
  167. }
  168. if repo.SubPath == "/info/refs" {
  169. resp.Header().Set("Content-Type", "application/x-git-upload-pack-advertisement")
  170. resp.Write(refs)
  171. return
  172. }
  173. resp.Header().Set("Content-Type", "text/html")
  174. if req.FormValue("go-get") == "1" {
  175. // execute simple template when this is a go-get request
  176. err = gogetTemplate.Execute(resp, repo)
  177. if err != nil {
  178. log.Printf("error executing go get template: %s\n", err)
  179. }
  180. return
  181. }
  182. renderPackagePage(resp, req, repo)
  183. }
  184. func sendNotFound(resp http.ResponseWriter, msg string, args ...interface{}) {
  185. if len(args) > 0 {
  186. msg = fmt.Sprintf(msg, args...)
  187. }
  188. resp.WriteHeader(http.StatusNotFound)
  189. resp.Write([]byte(msg))
  190. }
  191. // TODO Timeouts for these http interactions. Use the new support coming in 1.3.
  192. const refsSuffix = ".git/info/refs?service=git-upload-pack"
  193. var ErrNoRepo = errors.New("repository not found in github")
  194. var ErrNoVersion = errors.New("version reference not found in github")
  195. func hackedRefs(repo *Repo) (data []byte, versions []Version, err error) {
  196. resp, err := http.Get("https://" + repo.GitHubRoot() + refsSuffix)
  197. if err != nil {
  198. return nil, nil, fmt.Errorf("cannot talk to GitHub: %v", err)
  199. }
  200. switch resp.StatusCode {
  201. case 200:
  202. defer resp.Body.Close()
  203. case 401, 404:
  204. return nil, nil, ErrNoRepo
  205. default:
  206. return nil, nil, fmt.Errorf("error from GitHub: %v", resp.Status)
  207. }
  208. data, err = ioutil.ReadAll(resp.Body)
  209. if err != nil {
  210. return nil, nil, fmt.Errorf("error reading from GitHub: %v", err)
  211. }
  212. var mrefi, mrefj int
  213. var vrefi, vrefj int
  214. var vrefv = InvalidVersion
  215. versions = make([]Version, 0)
  216. sdata := string(data)
  217. for i, j := 0, 0; i < len(data); i = j {
  218. size, err := strconv.ParseInt(sdata[i:i+4], 16, 32)
  219. if err != nil {
  220. return nil, nil, fmt.Errorf("cannot parse refs line size: %s", string(data[i:i+4]))
  221. }
  222. if size == 0 {
  223. size = 4
  224. }
  225. j = i + int(size)
  226. if j > len(sdata) {
  227. return nil, nil, fmt.Errorf("incomplete refs data received from GitHub")
  228. }
  229. if sdata[0] == '#' {
  230. continue
  231. }
  232. hashi := i + 4
  233. hashj := strings.IndexByte(sdata[hashi:j], ' ')
  234. if hashj < 0 || hashj != 40 {
  235. continue
  236. }
  237. hashj += hashi
  238. namei := hashj + 1
  239. namej := strings.IndexAny(sdata[namei:j], "\n\x00")
  240. if namej < 0 {
  241. namej = j
  242. } else {
  243. namej += namei
  244. }
  245. name := sdata[namei:namej]
  246. if name == "refs/heads/master" {
  247. mrefi = hashi
  248. mrefj = hashj
  249. }
  250. if strings.HasPrefix(name, "refs/heads/v") || strings.HasPrefix(name, "refs/tags/v") {
  251. if strings.HasSuffix(name, "^{}") {
  252. // Annotated tag is peeled off and overrides the same version just parsed.
  253. name = name[:len(name)-3]
  254. }
  255. v, ok := parseVersion(name[strings.IndexByte(name, 'v'):])
  256. if ok && repo.MajorVersion.Contains(v) && (v == vrefv || !vrefv.IsValid() || vrefv.Less(v)) {
  257. vrefv = v
  258. vrefi = hashi
  259. vrefj = hashj
  260. }
  261. if ok {
  262. versions = append(versions, v)
  263. }
  264. }
  265. }
  266. // If there were absolutely no versions, and v0 was requested, accept the master as-is.
  267. if len(versions) == 0 && repo.MajorVersion == (Version{0, -1, -1}) {
  268. return data, nil, nil
  269. }
  270. if mrefi == 0 || vrefi == 0 {
  271. return nil, nil, ErrNoVersion
  272. }
  273. copy(data[mrefi:mrefj], data[vrefi:vrefj])
  274. return data, versions, nil
  275. }