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.

531 lines
12 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
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
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. "encoding/base64"
  8. "errors"
  9. "fmt"
  10. "io"
  11. "io/ioutil"
  12. "net/http"
  13. "os"
  14. "os/exec"
  15. "path"
  16. "path/filepath"
  17. "regexp"
  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 basicEncode(username, password string) string {
  28. auth := username + ":" + password
  29. return base64.StdEncoding.EncodeToString([]byte(auth))
  30. }
  31. func basicDecode(encoded string) (user string, name string, err error) {
  32. var s []byte
  33. s, err = base64.StdEncoding.DecodeString(encoded)
  34. if err != nil {
  35. return user, name, err
  36. }
  37. a := strings.Split(string(s), ":")
  38. if len(a) == 2 {
  39. user, name = a[0], a[1]
  40. } else {
  41. err = errors.New("decode failed")
  42. }
  43. return user, name, err
  44. }
  45. func authRequired(ctx *middleware.Context) {
  46. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
  47. ctx.Data["ErrorMsg"] = "no basic auth and digit auth"
  48. ctx.HTML(401, base.TplName("status/401"))
  49. }
  50. func Http(ctx *middleware.Context) {
  51. username := ctx.Params(":username")
  52. reponame := ctx.Params(":reponame")
  53. if strings.HasSuffix(reponame, ".git") {
  54. reponame = reponame[:len(reponame)-4]
  55. }
  56. var isPull bool
  57. service := ctx.Query("service")
  58. if service == "git-receive-pack" ||
  59. strings.HasSuffix(ctx.Req.URL.Path, "git-receive-pack") {
  60. isPull = false
  61. } else if service == "git-upload-pack" ||
  62. strings.HasSuffix(ctx.Req.URL.Path, "git-upload-pack") {
  63. isPull = true
  64. } else {
  65. isPull = (ctx.Req.Method == "GET")
  66. }
  67. repoUser, err := models.GetUserByName(username)
  68. if err != nil {
  69. if err == models.ErrUserNotExist {
  70. ctx.Handle(404, "GetUserByName", nil)
  71. } else {
  72. ctx.Handle(500, "GetUserByName", err)
  73. }
  74. return
  75. }
  76. repo, err := models.GetRepositoryByName(repoUser.Id, reponame)
  77. if err != nil {
  78. if err == models.ErrRepoNotExist {
  79. ctx.Handle(404, "GetRepositoryByName", nil)
  80. } else {
  81. ctx.Handle(500, "GetRepositoryByName", err)
  82. }
  83. return
  84. }
  85. // only public pull don't need auth
  86. isPublicPull := !repo.IsPrivate && isPull
  87. var askAuth = !isPublicPull || setting.Service.RequireSignInView
  88. var authUser *models.User
  89. var authUsername, passwd string
  90. // check access
  91. if askAuth {
  92. baHead := ctx.Req.Header.Get("Authorization")
  93. if baHead == "" {
  94. authRequired(ctx)
  95. return
  96. }
  97. auths := strings.Fields(baHead)
  98. // currently check basic auth
  99. // TODO: support digit auth
  100. if len(auths) != 2 || auths[0] != "Basic" {
  101. ctx.Handle(401, "no basic auth and digit auth", nil)
  102. return
  103. }
  104. authUsername, passwd, err = basicDecode(auths[1])
  105. if err != nil {
  106. ctx.Handle(401, "no basic auth and digit auth", nil)
  107. return
  108. }
  109. authUser, err = models.GetUserByName(authUsername)
  110. if err != nil {
  111. ctx.Handle(401, "no basic auth and digit auth", nil)
  112. return
  113. }
  114. newUser := &models.User{Passwd: passwd, Salt: authUser.Salt}
  115. newUser.EncodePasswd()
  116. if authUser.Passwd != newUser.Passwd {
  117. ctx.Handle(401, "no basic auth and digit auth", nil)
  118. return
  119. }
  120. if !isPublicPull {
  121. var tp = models.WRITABLE
  122. if isPull {
  123. tp = models.READABLE
  124. }
  125. has, err := models.HasAccess(authUsername, username+"/"+reponame, tp)
  126. if err != nil {
  127. ctx.Handle(401, "no basic auth and digit auth", nil)
  128. return
  129. } else if !has {
  130. if tp == models.READABLE {
  131. has, err = models.HasAccess(authUsername, username+"/"+reponame, models.WRITABLE)
  132. if err != nil || !has {
  133. ctx.Handle(401, "no basic auth and digit auth", nil)
  134. return
  135. }
  136. } else {
  137. ctx.Handle(401, "no basic auth and digit auth", nil)
  138. return
  139. }
  140. }
  141. }
  142. }
  143. var f func(rpc string, input []byte)
  144. f = func(rpc string, input []byte) {
  145. if rpc == "receive-pack" {
  146. var lastLine int64 = 0
  147. for {
  148. head := input[lastLine : lastLine+2]
  149. if head[0] == '0' && head[1] == '0' {
  150. size, err := strconv.ParseInt(string(input[lastLine+2:lastLine+4]), 16, 32)
  151. if err != nil {
  152. log.Error(4, "%v", err)
  153. return
  154. }
  155. if size == 0 {
  156. //fmt.Println(string(input[lastLine:]))
  157. break
  158. }
  159. line := input[lastLine : lastLine+size]
  160. idx := bytes.IndexRune(line, '\000')
  161. if idx > -1 {
  162. line = line[:idx]
  163. }
  164. fields := strings.Fields(string(line))
  165. if len(fields) >= 3 {
  166. oldCommitId := fields[0][4:]
  167. newCommitId := fields[1]
  168. refName := fields[2]
  169. models.Update(refName, oldCommitId, newCommitId, authUsername, username, reponame, authUser.Id)
  170. }
  171. lastLine = lastLine + size
  172. } else {
  173. break
  174. }
  175. }
  176. }
  177. }
  178. config := Config{setting.RepoRootPath, "git", true, true, f}
  179. handler := HttpBackend(&config)
  180. handler(ctx.Resp, ctx.Req)
  181. }
  182. type route struct {
  183. cr *regexp.Regexp
  184. method string
  185. handler func(handler)
  186. }
  187. type Config struct {
  188. ReposRoot string
  189. GitBinPath string
  190. UploadPack bool
  191. ReceivePack bool
  192. OnSucceed func(rpc string, input []byte)
  193. }
  194. type handler struct {
  195. *Config
  196. w http.ResponseWriter
  197. r *http.Request
  198. Dir string
  199. File string
  200. }
  201. var routes = []route{
  202. {regexp.MustCompile("(.*?)/git-upload-pack$"), "POST", serviceUploadPack},
  203. {regexp.MustCompile("(.*?)/git-receive-pack$"), "POST", serviceReceivePack},
  204. {regexp.MustCompile("(.*?)/info/refs$"), "GET", getInfoRefs},
  205. {regexp.MustCompile("(.*?)/HEAD$"), "GET", getTextFile},
  206. {regexp.MustCompile("(.*?)/objects/info/alternates$"), "GET", getTextFile},
  207. {regexp.MustCompile("(.*?)/objects/info/http-alternates$"), "GET", getTextFile},
  208. {regexp.MustCompile("(.*?)/objects/info/packs$"), "GET", getInfoPacks},
  209. {regexp.MustCompile("(.*?)/objects/info/[^/]*$"), "GET", getTextFile},
  210. {regexp.MustCompile("(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$"), "GET", getLooseObject},
  211. {regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$"), "GET", getPackFile},
  212. {regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$"), "GET", getIdxFile},
  213. }
  214. // Request handling function
  215. func HttpBackend(config *Config) http.HandlerFunc {
  216. return func(w http.ResponseWriter, r *http.Request) {
  217. for _, route := range routes {
  218. if m := route.cr.FindStringSubmatch(r.URL.Path); m != nil {
  219. if route.method != r.Method {
  220. renderMethodNotAllowed(w, r)
  221. return
  222. }
  223. file := strings.Replace(r.URL.Path, m[1]+"/", "", 1)
  224. dir, err := getGitDir(config, m[1])
  225. if err != nil {
  226. log.GitLogger.Error(4, err.Error())
  227. renderNotFound(w)
  228. return
  229. }
  230. hr := handler{config, w, r, dir, file}
  231. route.handler(hr)
  232. return
  233. }
  234. }
  235. renderNotFound(w)
  236. return
  237. }
  238. }
  239. // Actual command handling functions
  240. func serviceUploadPack(hr handler) {
  241. serviceRpc("upload-pack", hr)
  242. }
  243. func serviceReceivePack(hr handler) {
  244. serviceRpc("receive-pack", hr)
  245. }
  246. func serviceRpc(rpc string, hr handler) {
  247. w, r, dir := hr.w, hr.r, hr.Dir
  248. access := hasAccess(r, hr.Config, dir, rpc, true)
  249. if access == false {
  250. renderNoAccess(w)
  251. return
  252. }
  253. w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", rpc))
  254. w.WriteHeader(http.StatusOK)
  255. var input []byte
  256. var br io.Reader
  257. if hr.Config.OnSucceed != nil {
  258. input, _ = ioutil.ReadAll(r.Body)
  259. br = bytes.NewReader(input)
  260. } else {
  261. br = r.Body
  262. }
  263. args := []string{rpc, "--stateless-rpc", dir}
  264. cmd := exec.Command(hr.Config.GitBinPath, args...)
  265. cmd.Dir = dir
  266. cmd.Stdout = w
  267. cmd.Stdin = br
  268. err := cmd.Run()
  269. if err != nil {
  270. log.GitLogger.Error(4, err.Error())
  271. return
  272. }
  273. if hr.Config.OnSucceed != nil {
  274. hr.Config.OnSucceed(rpc, input)
  275. }
  276. }
  277. func getInfoRefs(hr handler) {
  278. w, r, dir := hr.w, hr.r, hr.Dir
  279. serviceName := getServiceType(r)
  280. access := hasAccess(r, hr.Config, dir, serviceName, false)
  281. if access {
  282. args := []string{serviceName, "--stateless-rpc", "--advertise-refs", "."}
  283. refs := gitCommand(hr.Config.GitBinPath, dir, args...)
  284. hdrNocache(w)
  285. w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", serviceName))
  286. w.WriteHeader(http.StatusOK)
  287. w.Write(packetWrite("# service=git-" + serviceName + "\n"))
  288. w.Write(packetFlush())
  289. w.Write(refs)
  290. } else {
  291. updateServerInfo(hr.Config.GitBinPath, dir)
  292. hdrNocache(w)
  293. sendFile("text/plain; charset=utf-8", hr)
  294. }
  295. }
  296. func getInfoPacks(hr handler) {
  297. hdrCacheForever(hr.w)
  298. sendFile("text/plain; charset=utf-8", hr)
  299. }
  300. func getLooseObject(hr handler) {
  301. hdrCacheForever(hr.w)
  302. sendFile("application/x-git-loose-object", hr)
  303. }
  304. func getPackFile(hr handler) {
  305. hdrCacheForever(hr.w)
  306. sendFile("application/x-git-packed-objects", hr)
  307. }
  308. func getIdxFile(hr handler) {
  309. hdrCacheForever(hr.w)
  310. sendFile("application/x-git-packed-objects-toc", hr)
  311. }
  312. func getTextFile(hr handler) {
  313. hdrNocache(hr.w)
  314. sendFile("text/plain", hr)
  315. }
  316. // Logic helping functions
  317. func sendFile(contentType string, hr handler) {
  318. w, r := hr.w, hr.r
  319. reqFile := path.Join(hr.Dir, hr.File)
  320. //fmt.Println("sendFile:", reqFile)
  321. f, err := os.Stat(reqFile)
  322. if os.IsNotExist(err) {
  323. renderNotFound(w)
  324. return
  325. }
  326. w.Header().Set("Content-Type", contentType)
  327. w.Header().Set("Content-Length", fmt.Sprintf("%d", f.Size()))
  328. w.Header().Set("Last-Modified", f.ModTime().Format(http.TimeFormat))
  329. http.ServeFile(w, r, reqFile)
  330. }
  331. func getGitDir(config *Config, fPath string) (string, error) {
  332. root := config.ReposRoot
  333. if root == "" {
  334. cwd, err := os.Getwd()
  335. if err != nil {
  336. log.GitLogger.Error(4, err.Error())
  337. return "", err
  338. }
  339. root = cwd
  340. }
  341. if !strings.HasSuffix(fPath, ".git") {
  342. fPath = fPath + ".git"
  343. }
  344. f := filepath.Join(root, fPath)
  345. if _, err := os.Stat(f); os.IsNotExist(err) {
  346. return "", err
  347. }
  348. return f, nil
  349. }
  350. func getServiceType(r *http.Request) string {
  351. serviceType := r.FormValue("service")
  352. if s := strings.HasPrefix(serviceType, "git-"); !s {
  353. return ""
  354. }
  355. return strings.Replace(serviceType, "git-", "", 1)
  356. }
  357. func hasAccess(r *http.Request, config *Config, dir string, rpc string, checkContentType bool) bool {
  358. if checkContentType {
  359. if r.Header.Get("Content-Type") != fmt.Sprintf("application/x-git-%s-request", rpc) {
  360. return false
  361. }
  362. }
  363. if !(rpc == "upload-pack" || rpc == "receive-pack") {
  364. return false
  365. }
  366. if rpc == "receive-pack" {
  367. return config.ReceivePack
  368. }
  369. if rpc == "upload-pack" {
  370. return config.UploadPack
  371. }
  372. return getConfigSetting(config.GitBinPath, rpc, dir)
  373. }
  374. func getConfigSetting(gitBinPath, serviceName string, dir string) bool {
  375. serviceName = strings.Replace(serviceName, "-", "", -1)
  376. setting := getGitConfig(gitBinPath, "http."+serviceName, dir)
  377. if serviceName == "uploadpack" {
  378. return setting != "false"
  379. }
  380. return setting == "true"
  381. }
  382. func getGitConfig(gitBinPath, configName string, dir string) string {
  383. args := []string{"config", configName}
  384. out := string(gitCommand(gitBinPath, dir, args...))
  385. return out[0 : len(out)-1]
  386. }
  387. func updateServerInfo(gitBinPath, dir string) []byte {
  388. args := []string{"update-server-info"}
  389. return gitCommand(gitBinPath, dir, args...)
  390. }
  391. func gitCommand(gitBinPath, dir string, args ...string) []byte {
  392. command := exec.Command(gitBinPath, args...)
  393. command.Dir = dir
  394. out, err := command.Output()
  395. if err != nil {
  396. log.GitLogger.Error(4, err.Error())
  397. }
  398. return out
  399. }
  400. // HTTP error response handling functions
  401. func renderMethodNotAllowed(w http.ResponseWriter, r *http.Request) {
  402. if r.Proto == "HTTP/1.1" {
  403. w.WriteHeader(http.StatusMethodNotAllowed)
  404. w.Write([]byte("Method Not Allowed"))
  405. } else {
  406. w.WriteHeader(http.StatusBadRequest)
  407. w.Write([]byte("Bad Request"))
  408. }
  409. }
  410. func renderNotFound(w http.ResponseWriter) {
  411. w.WriteHeader(http.StatusNotFound)
  412. w.Write([]byte("Not Found"))
  413. }
  414. func renderNoAccess(w http.ResponseWriter) {
  415. w.WriteHeader(http.StatusForbidden)
  416. w.Write([]byte("Forbidden"))
  417. }
  418. // Packet-line handling function
  419. func packetFlush() []byte {
  420. return []byte("0000")
  421. }
  422. func packetWrite(str string) []byte {
  423. s := strconv.FormatInt(int64(len(str)+4), 16)
  424. if len(s)%4 != 0 {
  425. s = strings.Repeat("0", 4-len(s)%4) + s
  426. }
  427. return []byte(s + str)
  428. }
  429. // Header writing functions
  430. func hdrNocache(w http.ResponseWriter) {
  431. w.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
  432. w.Header().Set("Pragma", "no-cache")
  433. w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
  434. }
  435. func hdrCacheForever(w http.ResponseWriter) {
  436. now := time.Now().Unix()
  437. expires := now + 31536000
  438. w.Header().Set("Date", fmt.Sprintf("%d", now))
  439. w.Header().Set("Expires", fmt.Sprintf("%d", expires))
  440. w.Header().Set("Cache-Control", "public, max-age=31536000")
  441. }