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 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
9 jaren geleden
10 jaren geleden
9 jaren geleden
9 jaren geleden
9 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
9 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
10 jaren geleden
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. package main
  2. import (
  3. "bytes"
  4. "errors"
  5. "flag"
  6. "fmt"
  7. "io/ioutil"
  8. "log"
  9. "net/http"
  10. "os"
  11. "regexp"
  12. "strconv"
  13. "strings"
  14. "text/template"
  15. "time"
  16. )
  17. var httpFlag = flag.String("http", ":8080", "Serve HTTP at given address")
  18. var httpsFlag = flag.String("https", "", "Serve HTTPS at given address")
  19. var certFlag = flag.String("cert", "", "Use the provided TLS certificate")
  20. var keyFlag = flag.String("key", "", "Use the provided TLS key")
  21. func main() {
  22. if err := run(); err != nil {
  23. fmt.Fprintf(os.Stderr, "error: %v\n", err)
  24. os.Exit(1)
  25. }
  26. }
  27. func run() error {
  28. flag.Parse()
  29. http.HandleFunc("/", handler)
  30. if *httpFlag == "" && *httpsFlag == "" {
  31. return fmt.Errorf("must provide -http and/or -https")
  32. }
  33. if (*httpsFlag != "" || *certFlag != "" || *keyFlag != "") && (*httpsFlag == "" || *certFlag == "" || *keyFlag == "") {
  34. return fmt.Errorf("-https -cert and -key must be used together")
  35. }
  36. ch := make(chan error, 2)
  37. if *httpFlag != "" {
  38. go func() {
  39. ch <- http.ListenAndServe(*httpFlag, nil)
  40. }()
  41. }
  42. if *httpsFlag != "" {
  43. go func() {
  44. ch <- http.ListenAndServeTLS(*httpsFlag, *certFlag, *keyFlag, nil)
  45. }()
  46. }
  47. return <-ch
  48. }
  49. var gogetTemplate = template.Must(template.New("").Parse(`
  50. <html>
  51. <head>
  52. <meta name="go-import" content="{{.GopkgRoot}} git https://{{.GopkgRoot}}">
  53. {{$root := .GitHubRoot}}{{$tree := .GitHubTree}}<meta name="go-source" content="{{.GopkgRoot}} _ https://{{$root}}/tree/{{$tree}}{/dir} https://{{$root}}/blob/{{$tree}}{/dir}/{file}#L{line}">
  54. </head>
  55. <body>
  56. go get {{.GopkgPath}}
  57. </body>
  58. </html>
  59. `))
  60. // Repo represents a source code repository on GitHub.
  61. type Repo struct {
  62. User string
  63. Name string
  64. SubPath string
  65. OldFormat bool // The old /v2/pkg format.
  66. MajorVersion Version
  67. // FullVersion is the best version in AllVersions that matches MajorVersion.
  68. // It defaults to InvalidVersion if there are no matches.
  69. FullVersion Version
  70. // AllVersions holds all versions currently available in the repository,
  71. // either coming from branch names or from tag names. Version zero (v0)
  72. // is only present in the list if it really exists in the repository.
  73. AllVersions VersionList
  74. }
  75. // SetVersions records in the relevant fields the details about which
  76. // package versions are available in the repository.
  77. func (repo *Repo) SetVersions(all []Version) {
  78. repo.AllVersions = all
  79. for _, v := range repo.AllVersions {
  80. if v.Major == repo.MajorVersion.Major && v.Unstable == repo.MajorVersion.Unstable && repo.FullVersion.Less(v) {
  81. repo.FullVersion = v
  82. }
  83. }
  84. }
  85. // GitHubRoot returns the repository root at GitHub, without a schema.
  86. func (repo *Repo) GitHubRoot() string {
  87. if repo.User == "" {
  88. return "github.com/go-" + repo.Name + "/" + repo.Name
  89. } else {
  90. return "github.com/" + repo.User + "/" + repo.Name
  91. }
  92. }
  93. // GitHubTree returns the repository tree name at GitHub for the selected version.
  94. func (repo *Repo) GitHubTree() string {
  95. if repo.FullVersion == InvalidVersion {
  96. return "master"
  97. }
  98. return repo.FullVersion.String()
  99. }
  100. // GopkgRoot returns the package root at gopkg.in, without a schema.
  101. func (repo *Repo) GopkgRoot() string {
  102. return repo.GopkgVersionRoot(repo.MajorVersion)
  103. }
  104. // GopkgPath returns the package path at gopkg.in, without a schema.
  105. func (repo *Repo) GopkgPath() string {
  106. return repo.GopkgVersionRoot(repo.MajorVersion) + repo.SubPath
  107. }
  108. // GopkgVersionRoot returns the package root in gopkg.in for the
  109. // provided version, without a schema.
  110. func (repo *Repo) GopkgVersionRoot(version Version) string {
  111. version.Minor = -1
  112. version.Patch = -1
  113. v := version.String()
  114. if repo.OldFormat {
  115. if repo.User == "" {
  116. return "gopkg.in/" + v + "/" + repo.Name
  117. } else {
  118. return "gopkg.in/" + repo.User + "/" + v + "/" + repo.Name
  119. }
  120. } else {
  121. if repo.User == "" {
  122. return "gopkg.in/" + repo.Name + "." + v
  123. } else {
  124. return "gopkg.in/" + repo.User + "/" + repo.Name + "." + v
  125. }
  126. }
  127. }
  128. var patternOld = regexp.MustCompile(`^/(?:([a-z0-9][-a-z0-9]+)/)?((?:v0|v[1-9][0-9]*)(?:\.0|\.[1-9][0-9]*){0,2}(-unstable)?)/([a-zA-Z][-a-zA-Z0-9]*)(?:\.git)?((?:/[a-zA-Z][-a-zA-Z0-9]*)*)$`)
  129. var patternNew = regexp.MustCompile(`^/(?:([a-zA-Z0-9][-a-zA-Z0-9]+)/)?([a-zA-Z][-.a-zA-Z0-9]*)\.((?:v0|v[1-9][0-9]*)(?:\.0|\.[1-9][0-9]*){0,2}(-unstable)?)(?:\.git)?((?:/[a-zA-Z0-9][-.a-zA-Z0-9]*)*)$`)
  130. func handler(resp http.ResponseWriter, req *http.Request) {
  131. if req.URL.Path == "/health-check" {
  132. resp.Write([]byte("ok"))
  133. return
  134. }
  135. log.Printf("%s requested %s", req.RemoteAddr, req.URL)
  136. if req.URL.Path == "/" {
  137. resp.Header().Set("Location", "http://labix.org/gopkg.in")
  138. resp.WriteHeader(http.StatusTemporaryRedirect)
  139. return
  140. }
  141. m := patternNew.FindStringSubmatch(req.URL.Path)
  142. oldFormat := false
  143. if m == nil {
  144. m = patternOld.FindStringSubmatch(req.URL.Path)
  145. if m == nil {
  146. sendNotFound(resp, "Unsupported URL pattern; see the documentation at gopkg.in for details.")
  147. return
  148. }
  149. // "/v2/name" <= "/name.v2"
  150. m[2], m[3] = m[3], m[2]
  151. oldFormat = true
  152. }
  153. if strings.Contains(m[3], ".") {
  154. sendNotFound(resp, "Import paths take the major version only (.%s instead of .%s); see docs at gopkg.in for the reasoning.",
  155. m[3][:strings.Index(m[3], ".")], m[3])
  156. return
  157. }
  158. repo := &Repo{
  159. User: m[1],
  160. Name: m[2],
  161. SubPath: m[5],
  162. OldFormat: oldFormat,
  163. FullVersion: InvalidVersion,
  164. }
  165. var ok bool
  166. repo.MajorVersion, ok = parseVersion(m[3])
  167. if !ok {
  168. sendNotFound(resp, "Version %q improperly considered invalid; please warn the service maintainers.", m[3])
  169. return
  170. }
  171. var changed []byte
  172. var versions VersionList
  173. original, err := fetchRefs(repo)
  174. if err == nil {
  175. changed, versions, err = changeRefs(original, repo.MajorVersion)
  176. repo.SetVersions(versions)
  177. }
  178. switch err {
  179. case nil:
  180. // all ok
  181. case ErrNoRepo:
  182. sendNotFound(resp, "GitHub repository not found at https://%s", repo.GitHubRoot())
  183. return
  184. case ErrNoVersion:
  185. major := repo.MajorVersion
  186. suffix := ""
  187. if major.Unstable {
  188. major.Unstable = false
  189. suffix = unstableSuffix
  190. }
  191. v := major.String()
  192. sendNotFound(resp, `GitHub repository at https://%s has no branch or tag "%s%s", "%s.N%s" or "%s.N.M%s"`, repo.GitHubRoot(), v, suffix, v, suffix, v, suffix)
  193. return
  194. default:
  195. resp.WriteHeader(http.StatusBadGateway)
  196. resp.Write([]byte(fmt.Sprintf("Cannot obtain refs from GitHub: %v", err)))
  197. return
  198. }
  199. if repo.SubPath == "/git-upload-pack" {
  200. resp.Header().Set("Location", "https://"+repo.GitHubRoot()+"/git-upload-pack")
  201. resp.WriteHeader(http.StatusMovedPermanently)
  202. return
  203. }
  204. if repo.SubPath == "/info/refs" {
  205. resp.Header().Set("Content-Type", "application/x-git-upload-pack-advertisement")
  206. resp.Write(changed)
  207. return
  208. }
  209. resp.Header().Set("Content-Type", "text/html")
  210. if req.FormValue("go-get") == "1" {
  211. // execute simple template when this is a go-get request
  212. err = gogetTemplate.Execute(resp, repo)
  213. if err != nil {
  214. log.Printf("error executing go get template: %s\n", err)
  215. }
  216. return
  217. }
  218. renderPackagePage(resp, req, repo)
  219. }
  220. func sendNotFound(resp http.ResponseWriter, msg string, args ...interface{}) {
  221. if len(args) > 0 {
  222. msg = fmt.Sprintf(msg, args...)
  223. }
  224. resp.WriteHeader(http.StatusNotFound)
  225. resp.Write([]byte(msg))
  226. }
  227. var httpClient = &http.Client{Timeout: 10 * time.Second}
  228. const refsSuffix = ".git/info/refs?service=git-upload-pack"
  229. var ErrNoRepo = errors.New("repository not found in GitHub")
  230. var ErrNoVersion = errors.New("version reference not found in GitHub")
  231. func fetchRefs(repo *Repo) (data []byte, err error) {
  232. resp, err := httpClient.Get("https://" + repo.GitHubRoot() + refsSuffix)
  233. if err != nil {
  234. return nil, fmt.Errorf("cannot talk to GitHub: %v", err)
  235. }
  236. defer resp.Body.Close()
  237. switch resp.StatusCode {
  238. case 200:
  239. // ok
  240. case 401, 404:
  241. return nil, ErrNoRepo
  242. default:
  243. return nil, fmt.Errorf("error from GitHub: %v", resp.Status)
  244. }
  245. data, err = ioutil.ReadAll(resp.Body)
  246. if err != nil {
  247. return nil, fmt.Errorf("error reading from GitHub: %v", err)
  248. }
  249. return data, err
  250. }
  251. func changeRefs(data []byte, major Version) (changed []byte, versions VersionList, err error) {
  252. var hlinei, hlinej int // HEAD reference line start/end
  253. var mlinei, mlinej int // master reference line start/end
  254. var vrefhash string
  255. var vrefname string
  256. var vrefv = InvalidVersion
  257. // Record all available versions, the locations of the master and HEAD lines,
  258. // and details of the best reference satisfying the requested major version.
  259. versions = make([]Version, 0)
  260. sdata := string(data)
  261. for i, j := 0, 0; i < len(data); i = j {
  262. size, err := strconv.ParseInt(sdata[i:i+4], 16, 32)
  263. if err != nil {
  264. return nil, nil, fmt.Errorf("cannot parse refs line size: %s", string(data[i:i+4]))
  265. }
  266. if size == 0 {
  267. size = 4
  268. }
  269. j = i + int(size)
  270. if j > len(sdata) {
  271. return nil, nil, fmt.Errorf("incomplete refs data received from GitHub")
  272. }
  273. if sdata[0] == '#' {
  274. continue
  275. }
  276. hashi := i + 4
  277. hashj := strings.IndexByte(sdata[hashi:j], ' ')
  278. if hashj < 0 || hashj != 40 {
  279. continue
  280. }
  281. hashj += hashi
  282. namei := hashj + 1
  283. namej := strings.IndexAny(sdata[namei:j], "\n\x00")
  284. if namej < 0 {
  285. namej = j
  286. } else {
  287. namej += namei
  288. }
  289. name := sdata[namei:namej]
  290. if name == "HEAD" {
  291. hlinei = i
  292. hlinej = j
  293. }
  294. if name == "refs/heads/master" {
  295. mlinei = i
  296. mlinej = j
  297. }
  298. if strings.HasPrefix(name, "refs/heads/v") || strings.HasPrefix(name, "refs/tags/v") {
  299. if strings.HasSuffix(name, "^{}") {
  300. // Annotated tag is peeled off and overrides the same version just parsed.
  301. name = name[:len(name)-3]
  302. }
  303. v, ok := parseVersion(name[strings.IndexByte(name, 'v'):])
  304. if ok && major.Contains(v) && (v == vrefv || !vrefv.IsValid() || vrefv.Less(v)) {
  305. vrefv = v
  306. vrefhash = sdata[hashi:hashj]
  307. vrefname = name
  308. }
  309. if ok {
  310. versions = append(versions, v)
  311. }
  312. }
  313. }
  314. // If there were absolutely no versions, and v0 was requested, accept the master as-is.
  315. if len(versions) == 0 && major == (Version{0, -1, -1, false}) {
  316. return data, nil, nil
  317. }
  318. // If the file has no HEAD line or the version was not found, report as unavailable.
  319. if hlinei == 0 || vrefhash == "" {
  320. return nil, nil, ErrNoVersion
  321. }
  322. var buf bytes.Buffer
  323. buf.Grow(len(data) + 256)
  324. // Copy the header as-is.
  325. buf.Write(data[:hlinei])
  326. // Extract the original capabilities.
  327. caps := ""
  328. if i := strings.Index(sdata[hlinei:hlinej], "\x00"); i > 0 {
  329. caps = strings.Replace(sdata[hlinei+i+1:hlinej-1], "symref=", "oldref=", -1)
  330. }
  331. // Insert the HEAD reference line with the right hash and a proper symref capability.
  332. var line string
  333. if strings.HasPrefix(vrefname, "refs/heads/") {
  334. if caps == "" {
  335. line = fmt.Sprintf("%s HEAD\x00symref=HEAD:%s\n", vrefhash, vrefname)
  336. } else {
  337. line = fmt.Sprintf("%s HEAD\x00symref=HEAD:%s %s\n", vrefhash, vrefname, caps)
  338. }
  339. } else {
  340. if caps == "" {
  341. line = fmt.Sprintf("%s HEAD\n", vrefhash)
  342. } else {
  343. line = fmt.Sprintf("%s HEAD\x00%s\n", vrefhash, caps)
  344. }
  345. }
  346. fmt.Fprintf(&buf, "%04x%s", 4+len(line), line)
  347. // Insert the master reference line.
  348. line = fmt.Sprintf("%s refs/heads/master\n", vrefhash)
  349. fmt.Fprintf(&buf, "%04x%s", 4+len(line), line)
  350. // Append the rest, dropping the original master line if necessary.
  351. if mlinei > 0 {
  352. buf.Write(data[hlinej:mlinei])
  353. buf.Write(data[mlinej:])
  354. } else {
  355. buf.Write(data[hlinej:])
  356. }
  357. return buf.Bytes(), versions, nil
  358. }