You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

http.go 14 kB

11 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
9 years ago
11 years ago
9 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
9 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
9 years ago
11 years ago
9 years ago
11 years ago
9 years ago
9 years ago
9 years ago
11 years ago
9 years ago
9 years ago
11 years ago
11 years ago
10 years ago
11 years ago
11 years ago
11 years ago
9 years ago
11 years ago
11 years ago
11 years ago
11 years ago
9 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
11 years ago
10 years ago
11 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package repo
  5. import (
  6. "bytes"
  7. "compress/gzip"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "net/http"
  12. "os"
  13. "os/exec"
  14. "path"
  15. "path/filepath"
  16. "regexp"
  17. "runtime"
  18. "strconv"
  19. "strings"
  20. "time"
  21. "github.com/gogits/gogs/models"
  22. "github.com/gogits/gogs/modules/base"
  23. "github.com/gogits/gogs/modules/log"
  24. "github.com/gogits/gogs/modules/middleware"
  25. "github.com/gogits/gogs/modules/setting"
  26. )
  27. func authRequired(ctx *middleware.Context) {
  28. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
  29. ctx.Data["ErrorMsg"] = "no basic auth and digit auth"
  30. ctx.Error(401)
  31. }
  32. func HTTP(ctx *middleware.Context) {
  33. username := ctx.Params(":username")
  34. reponame := strings.TrimSuffix(ctx.Params(":reponame"), ".git")
  35. var isPull bool
  36. service := ctx.Query("service")
  37. if service == "git-receive-pack" ||
  38. strings.HasSuffix(ctx.Req.URL.Path, "git-receive-pack") {
  39. isPull = false
  40. } else if service == "git-upload-pack" ||
  41. strings.HasSuffix(ctx.Req.URL.Path, "git-upload-pack") {
  42. isPull = true
  43. } else {
  44. isPull = (ctx.Req.Method == "GET")
  45. }
  46. isWiki := false
  47. if strings.HasSuffix(reponame, ".wiki") {
  48. isWiki = true
  49. reponame = reponame[:len(reponame)-5]
  50. }
  51. repoUser, err := models.GetUserByName(username)
  52. if err != nil {
  53. if models.IsErrUserNotExist(err) {
  54. ctx.Handle(404, "GetUserByName", nil)
  55. } else {
  56. ctx.Handle(500, "GetUserByName", err)
  57. }
  58. return
  59. }
  60. repo, err := models.GetRepositoryByName(repoUser.Id, reponame)
  61. if err != nil {
  62. if models.IsErrRepoNotExist(err) {
  63. ctx.Handle(404, "GetRepositoryByName", nil)
  64. } else {
  65. ctx.Handle(500, "GetRepositoryByName", err)
  66. }
  67. return
  68. }
  69. // Only public pull don't need auth.
  70. isPublicPull := !repo.IsPrivate && isPull
  71. var (
  72. askAuth = !isPublicPull || setting.Service.RequireSignInView
  73. authUser *models.User
  74. authUsername string
  75. authPasswd string
  76. )
  77. // check access
  78. if askAuth {
  79. authHead := ctx.Req.Header.Get("Authorization")
  80. if len(authHead) == 0 {
  81. authRequired(ctx)
  82. return
  83. }
  84. auths := strings.Fields(authHead)
  85. // currently check basic auth
  86. // TODO: support digit auth
  87. // FIXME: middlewares/context.go did basic auth check already,
  88. // maybe could use that one.
  89. if len(auths) != 2 || auths[0] != "Basic" {
  90. ctx.HandleText(401, "no basic auth and digit auth")
  91. return
  92. }
  93. authUsername, authPasswd, err = base.BasicAuthDecode(auths[1])
  94. if err != nil {
  95. ctx.HandleText(401, "no basic auth and digit auth")
  96. return
  97. }
  98. authUser, err = models.UserSignIn(authUsername, authPasswd)
  99. if err != nil {
  100. if !models.IsErrUserNotExist(err) {
  101. ctx.Handle(500, "UserSignIn error: %v", err)
  102. return
  103. }
  104. // Assume username now is a token.
  105. token, err := models.GetAccessTokenBySHA(authUsername)
  106. if err != nil {
  107. if models.IsErrAccessTokenNotExist(err) {
  108. ctx.HandleText(401, "invalid token")
  109. } else {
  110. ctx.Handle(500, "GetAccessTokenBySha", err)
  111. }
  112. return
  113. }
  114. token.Updated = time.Now()
  115. if err = models.UpdateAccessToken(token); err != nil {
  116. ctx.Handle(500, "UpdateAccessToken", err)
  117. }
  118. authUser, err = models.GetUserByID(token.UID)
  119. if err != nil {
  120. ctx.Handle(500, "GetUserByID", err)
  121. return
  122. }
  123. }
  124. if !isPublicPull {
  125. var tp = models.ACCESS_MODE_WRITE
  126. if isPull {
  127. tp = models.ACCESS_MODE_READ
  128. }
  129. has, err := models.HasAccess(authUser, repo, tp)
  130. if err != nil {
  131. ctx.Handle(500, "HasAccess", err)
  132. return
  133. } else if !has {
  134. if tp == models.ACCESS_MODE_READ {
  135. has, err = models.HasAccess(authUser, repo, models.ACCESS_MODE_WRITE)
  136. if err != nil {
  137. ctx.Handle(500, "HasAccess2", err)
  138. return
  139. } else if !has {
  140. ctx.HandleText(403, "User permission denied")
  141. return
  142. }
  143. } else {
  144. ctx.HandleText(403, "User permission denied")
  145. return
  146. }
  147. }
  148. if !isPull && repo.IsMirror {
  149. ctx.HandleText(403, "mirror repository is read-only")
  150. return
  151. }
  152. }
  153. }
  154. callback := func(rpc string, input []byte) {
  155. if rpc != "receive-pack" || isWiki {
  156. return
  157. }
  158. var lastLine int64 = 0
  159. for {
  160. head := input[lastLine : lastLine+2]
  161. if head[0] == '0' && head[1] == '0' {
  162. size, err := strconv.ParseInt(string(input[lastLine+2:lastLine+4]), 16, 32)
  163. if err != nil {
  164. log.Error(4, "%v", err)
  165. return
  166. }
  167. if size == 0 {
  168. //fmt.Println(string(input[lastLine:]))
  169. break
  170. }
  171. line := input[lastLine : lastLine+size]
  172. idx := bytes.IndexRune(line, '\000')
  173. if idx > -1 {
  174. line = line[:idx]
  175. }
  176. fields := strings.Fields(string(line))
  177. if len(fields) >= 3 {
  178. oldCommitId := fields[0][4:]
  179. newCommitId := fields[1]
  180. refName := fields[2]
  181. // FIXME: handle error.
  182. if err = models.PushUpdate(models.PushUpdateOptions{
  183. RefName: refName,
  184. OldCommitID: oldCommitId,
  185. NewCommitID: newCommitId,
  186. PusherID: authUser.Id,
  187. PusherName: authUser.Name,
  188. RepoUserName: username,
  189. RepoName: reponame,
  190. }); err == nil {
  191. go models.HookQueue.Add(repo.ID)
  192. go models.AddTestPullRequestTask(repo.ID, strings.TrimPrefix(refName, "refs/heads/"))
  193. }
  194. }
  195. lastLine = lastLine + size
  196. } else {
  197. break
  198. }
  199. }
  200. }
  201. HTTPBackend(ctx, &Config{
  202. RepoRootPath: setting.RepoRootPath,
  203. GitBinPath: "git",
  204. UploadPack: true,
  205. ReceivePack: true,
  206. OnSucceed: callback,
  207. })(ctx.Resp, ctx.Req.Request)
  208. runtime.GC()
  209. }
  210. type Config struct {
  211. RepoRootPath string
  212. GitBinPath string
  213. UploadPack bool
  214. ReceivePack bool
  215. OnSucceed func(rpc string, input []byte)
  216. }
  217. type handler struct {
  218. *Config
  219. w http.ResponseWriter
  220. r *http.Request
  221. Dir string
  222. File string
  223. }
  224. type route struct {
  225. cr *regexp.Regexp
  226. method string
  227. handler func(handler)
  228. }
  229. var routes = []route{
  230. {regexp.MustCompile("(.*?)/git-upload-pack$"), "POST", serviceUploadPack},
  231. {regexp.MustCompile("(.*?)/git-receive-pack$"), "POST", serviceReceivePack},
  232. {regexp.MustCompile("(.*?)/info/refs$"), "GET", getInfoRefs},
  233. {regexp.MustCompile("(.*?)/HEAD$"), "GET", getTextFile},
  234. {regexp.MustCompile("(.*?)/objects/info/alternates$"), "GET", getTextFile},
  235. {regexp.MustCompile("(.*?)/objects/info/http-alternates$"), "GET", getTextFile},
  236. {regexp.MustCompile("(.*?)/objects/info/packs$"), "GET", getInfoPacks},
  237. {regexp.MustCompile("(.*?)/objects/info/[^/]*$"), "GET", getTextFile},
  238. {regexp.MustCompile("(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$"), "GET", getLooseObject},
  239. {regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$"), "GET", getPackFile},
  240. {regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$"), "GET", getIdxFile},
  241. }
  242. func getGitDir(config *Config, fPath string) (string, error) {
  243. root := config.RepoRootPath
  244. if len(root) == 0 {
  245. cwd, err := os.Getwd()
  246. if err != nil {
  247. log.GitLogger.Error(4, err.Error())
  248. return "", err
  249. }
  250. root = cwd
  251. }
  252. if !strings.HasSuffix(fPath, ".git") {
  253. fPath = fPath + ".git"
  254. }
  255. f := filepath.Join(root, fPath)
  256. if _, err := os.Stat(f); os.IsNotExist(err) {
  257. return "", err
  258. }
  259. return f, nil
  260. }
  261. // Request handling function
  262. func HTTPBackend(ctx *middleware.Context, config *Config) http.HandlerFunc {
  263. return func(w http.ResponseWriter, r *http.Request) {
  264. for _, route := range routes {
  265. r.URL.Path = strings.ToLower(r.URL.Path) // blue: In case some repo name has upper case name
  266. if m := route.cr.FindStringSubmatch(r.URL.Path); m != nil {
  267. if route.method != r.Method {
  268. renderMethodNotAllowed(w, r)
  269. return
  270. }
  271. file := strings.Replace(r.URL.Path, m[1]+"/", "", 1)
  272. dir, err := getGitDir(config, m[1])
  273. if err != nil {
  274. log.GitLogger.Error(4, err.Error())
  275. ctx.Handle(404, "HTTPBackend", err)
  276. return
  277. }
  278. route.handler(handler{config, w, r, dir, file})
  279. return
  280. }
  281. }
  282. ctx.Handle(404, "HTTPBackend", nil)
  283. return
  284. }
  285. }
  286. // Actual command handling functions
  287. func serviceUploadPack(hr handler) {
  288. serviceRpc("upload-pack", hr)
  289. }
  290. func serviceReceivePack(hr handler) {
  291. serviceRpc("receive-pack", hr)
  292. }
  293. func serviceRpc(rpc string, hr handler) {
  294. w, r, dir := hr.w, hr.r, hr.Dir
  295. defer r.Body.Close()
  296. if !hasAccess(r, hr.Config, dir, rpc, true) {
  297. renderNoAccess(w)
  298. return
  299. }
  300. w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", rpc))
  301. var (
  302. reqBody = r.Body
  303. input []byte
  304. br io.Reader
  305. err error
  306. )
  307. // Handle GZIP.
  308. if r.Header.Get("Content-Encoding") == "gzip" {
  309. reqBody, err = gzip.NewReader(reqBody)
  310. if err != nil {
  311. log.GitLogger.Error(2, "fail to create gzip reader: %v", err)
  312. w.WriteHeader(http.StatusInternalServerError)
  313. return
  314. }
  315. }
  316. if hr.Config.OnSucceed != nil {
  317. input, err = ioutil.ReadAll(reqBody)
  318. if err != nil {
  319. log.GitLogger.Error(2, "fail to read request body: %v", err)
  320. w.WriteHeader(http.StatusInternalServerError)
  321. return
  322. }
  323. br = bytes.NewReader(input)
  324. } else {
  325. br = reqBody
  326. }
  327. args := []string{rpc, "--stateless-rpc", dir}
  328. cmd := exec.Command(hr.Config.GitBinPath, args...)
  329. cmd.Dir = dir
  330. cmd.Stdout = w
  331. cmd.Stdin = br
  332. if err := cmd.Run(); err != nil {
  333. log.GitLogger.Error(2, "fail to serve RPC(%s): %v", rpc, err)
  334. w.WriteHeader(http.StatusInternalServerError)
  335. return
  336. }
  337. if hr.Config.OnSucceed != nil {
  338. hr.Config.OnSucceed(rpc, input)
  339. }
  340. }
  341. func getInfoRefs(hr handler) {
  342. w, r, dir := hr.w, hr.r, hr.Dir
  343. serviceName := getServiceType(r)
  344. access := hasAccess(r, hr.Config, dir, serviceName, false)
  345. if access {
  346. args := []string{serviceName, "--stateless-rpc", "--advertise-refs", "."}
  347. refs := gitCommand(hr.Config.GitBinPath, dir, args...)
  348. hdrNocache(w)
  349. w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", serviceName))
  350. w.WriteHeader(http.StatusOK)
  351. w.Write(packetWrite("# service=git-" + serviceName + "\n"))
  352. w.Write(packetFlush())
  353. w.Write(refs)
  354. } else {
  355. updateServerInfo(hr.Config.GitBinPath, dir)
  356. hdrNocache(w)
  357. sendFile("text/plain; charset=utf-8", hr)
  358. }
  359. }
  360. func getInfoPacks(hr handler) {
  361. hdrCacheForever(hr.w)
  362. sendFile("text/plain; charset=utf-8", hr)
  363. }
  364. func getLooseObject(hr handler) {
  365. hdrCacheForever(hr.w)
  366. sendFile("application/x-git-loose-object", hr)
  367. }
  368. func getPackFile(hr handler) {
  369. hdrCacheForever(hr.w)
  370. sendFile("application/x-git-packed-objects", hr)
  371. }
  372. func getIdxFile(hr handler) {
  373. hdrCacheForever(hr.w)
  374. sendFile("application/x-git-packed-objects-toc", hr)
  375. }
  376. func getTextFile(hr handler) {
  377. hdrNocache(hr.w)
  378. sendFile("text/plain", hr)
  379. }
  380. // Logic helping functions
  381. func sendFile(contentType string, hr handler) {
  382. w, r := hr.w, hr.r
  383. reqFile := path.Join(hr.Dir, hr.File)
  384. f, err := os.Stat(reqFile)
  385. if os.IsNotExist(err) {
  386. renderNotFound(w)
  387. return
  388. }
  389. w.Header().Set("Content-Type", contentType)
  390. w.Header().Set("Content-Length", fmt.Sprintf("%d", f.Size()))
  391. w.Header().Set("Last-Modified", f.ModTime().Format(http.TimeFormat))
  392. http.ServeFile(w, r, reqFile)
  393. }
  394. func getServiceType(r *http.Request) string {
  395. serviceType := r.FormValue("service")
  396. if s := strings.HasPrefix(serviceType, "git-"); !s {
  397. return ""
  398. }
  399. return strings.Replace(serviceType, "git-", "", 1)
  400. }
  401. func hasAccess(r *http.Request, config *Config, dir string, rpc string, checkContentType bool) bool {
  402. if checkContentType {
  403. if r.Header.Get("Content-Type") != fmt.Sprintf("application/x-git-%s-request", rpc) {
  404. return false
  405. }
  406. }
  407. if !(rpc == "upload-pack" || rpc == "receive-pack") {
  408. return false
  409. }
  410. if rpc == "receive-pack" {
  411. return config.ReceivePack
  412. }
  413. if rpc == "upload-pack" {
  414. return config.UploadPack
  415. }
  416. return getConfigSetting(config.GitBinPath, rpc, dir)
  417. }
  418. func getConfigSetting(gitBinPath, serviceName string, dir string) bool {
  419. serviceName = strings.Replace(serviceName, "-", "", -1)
  420. setting := getGitConfig(gitBinPath, "http."+serviceName, dir)
  421. if serviceName == "uploadpack" {
  422. return setting != "false"
  423. }
  424. return setting == "true"
  425. }
  426. func getGitConfig(gitBinPath, configName string, dir string) string {
  427. args := []string{"config", configName}
  428. out := string(gitCommand(gitBinPath, dir, args...))
  429. return out[0 : len(out)-1]
  430. }
  431. func updateServerInfo(gitBinPath, dir string) []byte {
  432. args := []string{"update-server-info"}
  433. return gitCommand(gitBinPath, dir, args...)
  434. }
  435. // FIXME: use process module
  436. func gitCommand(gitBinPath, dir string, args ...string) []byte {
  437. command := exec.Command(gitBinPath, args...)
  438. command.Dir = dir
  439. out, err := command.Output()
  440. if err != nil {
  441. log.GitLogger.Error(4, err.Error())
  442. }
  443. return out
  444. }
  445. // HTTP error response handling functions
  446. func renderMethodNotAllowed(w http.ResponseWriter, r *http.Request) {
  447. if r.Proto == "HTTP/1.1" {
  448. w.WriteHeader(http.StatusMethodNotAllowed)
  449. w.Write([]byte("Method Not Allowed"))
  450. } else {
  451. w.WriteHeader(http.StatusBadRequest)
  452. w.Write([]byte("Bad Request"))
  453. }
  454. }
  455. func renderNotFound(w http.ResponseWriter) {
  456. w.WriteHeader(http.StatusNotFound)
  457. w.Write([]byte("Not Found"))
  458. }
  459. func renderNoAccess(w http.ResponseWriter) {
  460. w.WriteHeader(http.StatusForbidden)
  461. w.Write([]byte("Forbidden"))
  462. }
  463. // Packet-line handling function
  464. func packetFlush() []byte {
  465. return []byte("0000")
  466. }
  467. func packetWrite(str string) []byte {
  468. s := strconv.FormatInt(int64(len(str)+4), 16)
  469. if len(s)%4 != 0 {
  470. s = strings.Repeat("0", 4-len(s)%4) + s
  471. }
  472. return []byte(s + str)
  473. }
  474. // Header writing functions
  475. func hdrNocache(w http.ResponseWriter) {
  476. w.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
  477. w.Header().Set("Pragma", "no-cache")
  478. w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
  479. }
  480. func hdrCacheForever(w http.ResponseWriter) {
  481. now := time.Now().Unix()
  482. expires := now + 31536000
  483. w.Header().Set("Date", fmt.Sprintf("%d", now))
  484. w.Header().Set("Expires", fmt.Sprintf("%d", expires))
  485. w.Header().Set("Cache-Control", "public, max-age=31536000")
  486. }