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