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.

565 lines
14 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
8 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
9 years ago
10 years ago
9 years ago
9 years ago
10 years ago
9 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  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. authUsername = authUser.Name
  124. }
  125. if !isPublicPull {
  126. var tp = models.ACCESS_MODE_WRITE
  127. if isPull {
  128. tp = models.ACCESS_MODE_READ
  129. }
  130. has, err := models.HasAccess(authUser, repo, tp)
  131. if err != nil {
  132. ctx.Handle(500, "HasAccess", err)
  133. return
  134. } else if !has {
  135. if tp == models.ACCESS_MODE_READ {
  136. has, err = models.HasAccess(authUser, repo, models.ACCESS_MODE_WRITE)
  137. if err != nil {
  138. ctx.Handle(500, "HasAccess2", err)
  139. return
  140. } else if !has {
  141. ctx.HandleText(403, "User permission denied")
  142. return
  143. }
  144. } else {
  145. ctx.HandleText(403, "User permission denied")
  146. return
  147. }
  148. }
  149. if !isPull && repo.IsMirror {
  150. ctx.HandleText(403, "mirror repository is read-only")
  151. return
  152. }
  153. }
  154. }
  155. callback := func(rpc string, input []byte) {
  156. if rpc != "receive-pack" || isWiki {
  157. return
  158. }
  159. var lastLine int64 = 0
  160. for {
  161. head := input[lastLine : lastLine+2]
  162. if head[0] == '0' && head[1] == '0' {
  163. size, err := strconv.ParseInt(string(input[lastLine+2:lastLine+4]), 16, 32)
  164. if err != nil {
  165. log.Error(4, "%v", err)
  166. return
  167. }
  168. if size == 0 {
  169. //fmt.Println(string(input[lastLine:]))
  170. break
  171. }
  172. line := input[lastLine : lastLine+size]
  173. idx := bytes.IndexRune(line, '\000')
  174. if idx > -1 {
  175. line = line[:idx]
  176. }
  177. fields := strings.Fields(string(line))
  178. if len(fields) >= 3 {
  179. oldCommitId := fields[0][4:]
  180. newCommitId := fields[1]
  181. refName := fields[2]
  182. // FIXME: handle error.
  183. if err = models.Update(refName, oldCommitId, newCommitId, authUsername, username, reponame, authUser.Id); err == nil {
  184. go models.HookQueue.Add(repo.ID)
  185. go models.AddTestPullRequestTask(repo.ID, strings.TrimPrefix(refName, "refs/heads/"))
  186. }
  187. }
  188. lastLine = lastLine + size
  189. } else {
  190. break
  191. }
  192. }
  193. }
  194. HTTPBackend(ctx, &Config{
  195. RepoRootPath: setting.RepoRootPath,
  196. GitBinPath: "git",
  197. UploadPack: true,
  198. ReceivePack: true,
  199. OnSucceed: callback,
  200. })(ctx.Resp, ctx.Req.Request)
  201. runtime.GC()
  202. }
  203. type Config struct {
  204. RepoRootPath string
  205. GitBinPath string
  206. UploadPack bool
  207. ReceivePack bool
  208. OnSucceed func(rpc string, input []byte)
  209. }
  210. type handler struct {
  211. *Config
  212. w http.ResponseWriter
  213. r *http.Request
  214. Dir string
  215. File string
  216. }
  217. type route struct {
  218. cr *regexp.Regexp
  219. method string
  220. handler func(handler)
  221. }
  222. var routes = []route{
  223. {regexp.MustCompile("(.*?)/git-upload-pack$"), "POST", serviceUploadPack},
  224. {regexp.MustCompile("(.*?)/git-receive-pack$"), "POST", serviceReceivePack},
  225. {regexp.MustCompile("(.*?)/info/refs$"), "GET", getInfoRefs},
  226. {regexp.MustCompile("(.*?)/HEAD$"), "GET", getTextFile},
  227. {regexp.MustCompile("(.*?)/objects/info/alternates$"), "GET", getTextFile},
  228. {regexp.MustCompile("(.*?)/objects/info/http-alternates$"), "GET", getTextFile},
  229. {regexp.MustCompile("(.*?)/objects/info/packs$"), "GET", getInfoPacks},
  230. {regexp.MustCompile("(.*?)/objects/info/[^/]*$"), "GET", getTextFile},
  231. {regexp.MustCompile("(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$"), "GET", getLooseObject},
  232. {regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$"), "GET", getPackFile},
  233. {regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$"), "GET", getIdxFile},
  234. }
  235. func getGitDir(config *Config, fPath string) (string, error) {
  236. root := config.RepoRootPath
  237. if len(root) == 0 {
  238. cwd, err := os.Getwd()
  239. if err != nil {
  240. log.GitLogger.Error(4, err.Error())
  241. return "", err
  242. }
  243. root = cwd
  244. }
  245. if !strings.HasSuffix(fPath, ".git") {
  246. fPath = fPath + ".git"
  247. }
  248. f := filepath.Join(root, fPath)
  249. if _, err := os.Stat(f); os.IsNotExist(err) {
  250. return "", err
  251. }
  252. return f, nil
  253. }
  254. // Request handling function
  255. func HTTPBackend(ctx *middleware.Context, config *Config) http.HandlerFunc {
  256. return func(w http.ResponseWriter, r *http.Request) {
  257. for _, route := range routes {
  258. r.URL.Path = strings.ToLower(r.URL.Path) // blue: In case some repo name has upper case name
  259. if m := route.cr.FindStringSubmatch(r.URL.Path); m != nil {
  260. if route.method != r.Method {
  261. renderMethodNotAllowed(w, r)
  262. return
  263. }
  264. file := strings.Replace(r.URL.Path, m[1]+"/", "", 1)
  265. dir, err := getGitDir(config, m[1])
  266. if err != nil {
  267. log.GitLogger.Error(4, err.Error())
  268. ctx.Handle(404, "HTTPBackend", err)
  269. return
  270. }
  271. route.handler(handler{config, w, r, dir, file})
  272. return
  273. }
  274. }
  275. ctx.Handle(404, "HTTPBackend", nil)
  276. return
  277. }
  278. }
  279. // Actual command handling functions
  280. func serviceUploadPack(hr handler) {
  281. serviceRpc("upload-pack", hr)
  282. }
  283. func serviceReceivePack(hr handler) {
  284. serviceRpc("receive-pack", hr)
  285. }
  286. func serviceRpc(rpc string, hr handler) {
  287. w, r, dir := hr.w, hr.r, hr.Dir
  288. defer r.Body.Close()
  289. if !hasAccess(r, hr.Config, dir, rpc, true) {
  290. renderNoAccess(w)
  291. return
  292. }
  293. w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", rpc))
  294. var (
  295. reqBody = r.Body
  296. input []byte
  297. br io.Reader
  298. err error
  299. )
  300. // Handle GZIP.
  301. if r.Header.Get("Content-Encoding") == "gzip" {
  302. reqBody, err = gzip.NewReader(reqBody)
  303. if err != nil {
  304. log.GitLogger.Error(2, "fail to create gzip reader: %v", err)
  305. w.WriteHeader(http.StatusInternalServerError)
  306. return
  307. }
  308. }
  309. if hr.Config.OnSucceed != nil {
  310. input, err = ioutil.ReadAll(reqBody)
  311. if err != nil {
  312. log.GitLogger.Error(2, "fail to read request body: %v", err)
  313. w.WriteHeader(http.StatusInternalServerError)
  314. return
  315. }
  316. br = bytes.NewReader(input)
  317. } else {
  318. br = reqBody
  319. }
  320. args := []string{rpc, "--stateless-rpc", dir}
  321. cmd := exec.Command(hr.Config.GitBinPath, args...)
  322. cmd.Dir = dir
  323. cmd.Stdout = w
  324. cmd.Stdin = br
  325. if err := cmd.Run(); err != nil {
  326. log.GitLogger.Error(2, "fail to serve RPC(%s): %v", rpc, err)
  327. w.WriteHeader(http.StatusInternalServerError)
  328. return
  329. }
  330. if hr.Config.OnSucceed != nil {
  331. hr.Config.OnSucceed(rpc, input)
  332. }
  333. }
  334. func getInfoRefs(hr handler) {
  335. w, r, dir := hr.w, hr.r, hr.Dir
  336. serviceName := getServiceType(r)
  337. access := hasAccess(r, hr.Config, dir, serviceName, false)
  338. if access {
  339. args := []string{serviceName, "--stateless-rpc", "--advertise-refs", "."}
  340. refs := gitCommand(hr.Config.GitBinPath, dir, args...)
  341. hdrNocache(w)
  342. w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", serviceName))
  343. w.WriteHeader(http.StatusOK)
  344. w.Write(packetWrite("# service=git-" + serviceName + "\n"))
  345. w.Write(packetFlush())
  346. w.Write(refs)
  347. } else {
  348. updateServerInfo(hr.Config.GitBinPath, dir)
  349. hdrNocache(w)
  350. sendFile("text/plain; charset=utf-8", hr)
  351. }
  352. }
  353. func getInfoPacks(hr handler) {
  354. hdrCacheForever(hr.w)
  355. sendFile("text/plain; charset=utf-8", hr)
  356. }
  357. func getLooseObject(hr handler) {
  358. hdrCacheForever(hr.w)
  359. sendFile("application/x-git-loose-object", hr)
  360. }
  361. func getPackFile(hr handler) {
  362. hdrCacheForever(hr.w)
  363. sendFile("application/x-git-packed-objects", hr)
  364. }
  365. func getIdxFile(hr handler) {
  366. hdrCacheForever(hr.w)
  367. sendFile("application/x-git-packed-objects-toc", hr)
  368. }
  369. func getTextFile(hr handler) {
  370. hdrNocache(hr.w)
  371. sendFile("text/plain", hr)
  372. }
  373. // Logic helping functions
  374. func sendFile(contentType string, hr handler) {
  375. w, r := hr.w, hr.r
  376. reqFile := path.Join(hr.Dir, hr.File)
  377. f, err := os.Stat(reqFile)
  378. if os.IsNotExist(err) {
  379. renderNotFound(w)
  380. return
  381. }
  382. w.Header().Set("Content-Type", contentType)
  383. w.Header().Set("Content-Length", fmt.Sprintf("%d", f.Size()))
  384. w.Header().Set("Last-Modified", f.ModTime().Format(http.TimeFormat))
  385. http.ServeFile(w, r, reqFile)
  386. }
  387. func getServiceType(r *http.Request) string {
  388. serviceType := r.FormValue("service")
  389. if s := strings.HasPrefix(serviceType, "git-"); !s {
  390. return ""
  391. }
  392. return strings.Replace(serviceType, "git-", "", 1)
  393. }
  394. func hasAccess(r *http.Request, config *Config, dir string, rpc string, checkContentType bool) bool {
  395. if checkContentType {
  396. if r.Header.Get("Content-Type") != fmt.Sprintf("application/x-git-%s-request", rpc) {
  397. return false
  398. }
  399. }
  400. if !(rpc == "upload-pack" || rpc == "receive-pack") {
  401. return false
  402. }
  403. if rpc == "receive-pack" {
  404. return config.ReceivePack
  405. }
  406. if rpc == "upload-pack" {
  407. return config.UploadPack
  408. }
  409. return getConfigSetting(config.GitBinPath, rpc, dir)
  410. }
  411. func getConfigSetting(gitBinPath, serviceName string, dir string) bool {
  412. serviceName = strings.Replace(serviceName, "-", "", -1)
  413. setting := getGitConfig(gitBinPath, "http."+serviceName, dir)
  414. if serviceName == "uploadpack" {
  415. return setting != "false"
  416. }
  417. return setting == "true"
  418. }
  419. func getGitConfig(gitBinPath, configName string, dir string) string {
  420. args := []string{"config", configName}
  421. out := string(gitCommand(gitBinPath, dir, args...))
  422. return out[0 : len(out)-1]
  423. }
  424. func updateServerInfo(gitBinPath, dir string) []byte {
  425. args := []string{"update-server-info"}
  426. return gitCommand(gitBinPath, dir, args...)
  427. }
  428. // FIXME: use process module
  429. func gitCommand(gitBinPath, dir string, args ...string) []byte {
  430. command := exec.Command(gitBinPath, args...)
  431. command.Dir = dir
  432. out, err := command.Output()
  433. if err != nil {
  434. log.GitLogger.Error(4, err.Error())
  435. }
  436. return out
  437. }
  438. // HTTP error response handling functions
  439. func renderMethodNotAllowed(w http.ResponseWriter, r *http.Request) {
  440. if r.Proto == "HTTP/1.1" {
  441. w.WriteHeader(http.StatusMethodNotAllowed)
  442. w.Write([]byte("Method Not Allowed"))
  443. } else {
  444. w.WriteHeader(http.StatusBadRequest)
  445. w.Write([]byte("Bad Request"))
  446. }
  447. }
  448. func renderNotFound(w http.ResponseWriter) {
  449. w.WriteHeader(http.StatusNotFound)
  450. w.Write([]byte("Not Found"))
  451. }
  452. func renderNoAccess(w http.ResponseWriter) {
  453. w.WriteHeader(http.StatusForbidden)
  454. w.Write([]byte("Forbidden"))
  455. }
  456. // Packet-line handling function
  457. func packetFlush() []byte {
  458. return []byte("0000")
  459. }
  460. func packetWrite(str string) []byte {
  461. s := strconv.FormatInt(int64(len(str)+4), 16)
  462. if len(s)%4 != 0 {
  463. s = strings.Repeat("0", 4-len(s)%4) + s
  464. }
  465. return []byte(s + str)
  466. }
  467. // Header writing functions
  468. func hdrNocache(w http.ResponseWriter) {
  469. w.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
  470. w.Header().Set("Pragma", "no-cache")
  471. w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
  472. }
  473. func hdrCacheForever(w http.ResponseWriter) {
  474. now := time.Now().Unix()
  475. expires := now + 31536000
  476. w.Header().Set("Date", fmt.Sprintf("%d", now))
  477. w.Header().Set("Expires", fmt.Sprintf("%d", expires))
  478. w.Header().Set("Cache-Control", "public, max-age=31536000")
  479. }