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
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
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
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
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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  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. r.URL.Path = strings.ToLower(r.URL.Path) // blue: In case some repo name has upper case name
  203. if m := route.cr.FindStringSubmatch(r.URL.Path); m != nil {
  204. if route.method != r.Method {
  205. renderMethodNotAllowed(w, r)
  206. return
  207. }
  208. file := strings.Replace(r.URL.Path, m[1]+"/", "", 1)
  209. dir, err := getGitDir(config, m[1])
  210. if err != nil {
  211. log.GitLogger.Error(4, err.Error())
  212. renderNotFound(w)
  213. return
  214. }
  215. hr := handler{config, w, r, dir, file}
  216. route.handler(hr)
  217. return
  218. }
  219. }
  220. renderNotFound(w)
  221. return
  222. }
  223. }
  224. // Actual command handling functions
  225. func serviceUploadPack(hr handler) {
  226. serviceRpc("upload-pack", hr)
  227. }
  228. func serviceReceivePack(hr handler) {
  229. serviceRpc("receive-pack", hr)
  230. }
  231. func serviceRpc(rpc string, hr handler) {
  232. w, r, dir := hr.w, hr.r, hr.Dir
  233. access := hasAccess(r, hr.Config, dir, rpc, true)
  234. if access == false {
  235. renderNoAccess(w)
  236. return
  237. }
  238. w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", rpc))
  239. var (
  240. reqBody = r.Body
  241. input []byte
  242. br io.Reader
  243. err error
  244. )
  245. // Handle GZIP.
  246. if r.Header.Get("Content-Encoding") == "gzip" {
  247. reqBody, err = gzip.NewReader(reqBody)
  248. if err != nil {
  249. log.GitLogger.Error(2, "fail to create gzip reader: %v", err)
  250. w.WriteHeader(http.StatusInternalServerError)
  251. return
  252. }
  253. }
  254. if hr.Config.OnSucceed != nil {
  255. input, err = ioutil.ReadAll(reqBody)
  256. if err != nil {
  257. log.GitLogger.Error(2, "fail to read request body: %v", err)
  258. w.WriteHeader(http.StatusInternalServerError)
  259. return
  260. }
  261. br = bytes.NewReader(input)
  262. } else {
  263. br = reqBody
  264. }
  265. args := []string{rpc, "--stateless-rpc", dir}
  266. cmd := exec.Command(hr.Config.GitBinPath, args...)
  267. cmd.Dir = dir
  268. cmd.Stdout = w
  269. cmd.Stdin = br
  270. if err := cmd.Run(); err != nil {
  271. log.GitLogger.Error(2, "fail to serve RPC(%s): %v", rpc, err)
  272. w.WriteHeader(http.StatusInternalServerError)
  273. return
  274. }
  275. if hr.Config.OnSucceed != nil {
  276. hr.Config.OnSucceed(rpc, input)
  277. }
  278. w.WriteHeader(http.StatusOK)
  279. }
  280. func getInfoRefs(hr handler) {
  281. w, r, dir := hr.w, hr.r, hr.Dir
  282. serviceName := getServiceType(r)
  283. access := hasAccess(r, hr.Config, dir, serviceName, false)
  284. if access {
  285. args := []string{serviceName, "--stateless-rpc", "--advertise-refs", "."}
  286. refs := gitCommand(hr.Config.GitBinPath, dir, args...)
  287. hdrNocache(w)
  288. w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", serviceName))
  289. w.WriteHeader(http.StatusOK)
  290. w.Write(packetWrite("# service=git-" + serviceName + "\n"))
  291. w.Write(packetFlush())
  292. w.Write(refs)
  293. } else {
  294. updateServerInfo(hr.Config.GitBinPath, dir)
  295. hdrNocache(w)
  296. sendFile("text/plain; charset=utf-8", hr)
  297. }
  298. }
  299. func getInfoPacks(hr handler) {
  300. hdrCacheForever(hr.w)
  301. sendFile("text/plain; charset=utf-8", hr)
  302. }
  303. func getLooseObject(hr handler) {
  304. hdrCacheForever(hr.w)
  305. sendFile("application/x-git-loose-object", hr)
  306. }
  307. func getPackFile(hr handler) {
  308. hdrCacheForever(hr.w)
  309. sendFile("application/x-git-packed-objects", hr)
  310. }
  311. func getIdxFile(hr handler) {
  312. hdrCacheForever(hr.w)
  313. sendFile("application/x-git-packed-objects-toc", hr)
  314. }
  315. func getTextFile(hr handler) {
  316. hdrNocache(hr.w)
  317. sendFile("text/plain", hr)
  318. }
  319. // Logic helping functions
  320. func sendFile(contentType string, hr handler) {
  321. w, r := hr.w, hr.r
  322. reqFile := path.Join(hr.Dir, hr.File)
  323. // fmt.Println("sendFile:", reqFile)
  324. f, err := os.Stat(reqFile)
  325. if os.IsNotExist(err) {
  326. renderNotFound(w)
  327. return
  328. }
  329. w.Header().Set("Content-Type", contentType)
  330. w.Header().Set("Content-Length", fmt.Sprintf("%d", f.Size()))
  331. w.Header().Set("Last-Modified", f.ModTime().Format(http.TimeFormat))
  332. http.ServeFile(w, r, reqFile)
  333. }
  334. func getGitDir(config *Config, fPath string) (string, error) {
  335. root := config.ReposRoot
  336. if root == "" {
  337. cwd, err := os.Getwd()
  338. if err != nil {
  339. log.GitLogger.Error(4, err.Error())
  340. return "", err
  341. }
  342. root = cwd
  343. }
  344. if !strings.HasSuffix(fPath, ".git") {
  345. fPath = fPath + ".git"
  346. }
  347. f := filepath.Join(root, fPath)
  348. if _, err := os.Stat(f); os.IsNotExist(err) {
  349. return "", err
  350. }
  351. return f, nil
  352. }
  353. func getServiceType(r *http.Request) string {
  354. serviceType := r.FormValue("service")
  355. if s := strings.HasPrefix(serviceType, "git-"); !s {
  356. return ""
  357. }
  358. return strings.Replace(serviceType, "git-", "", 1)
  359. }
  360. func hasAccess(r *http.Request, config *Config, dir string, rpc string, checkContentType bool) bool {
  361. if checkContentType {
  362. if r.Header.Get("Content-Type") != fmt.Sprintf("application/x-git-%s-request", rpc) {
  363. return false
  364. }
  365. }
  366. if !(rpc == "upload-pack" || rpc == "receive-pack") {
  367. return false
  368. }
  369. if rpc == "receive-pack" {
  370. return config.ReceivePack
  371. }
  372. if rpc == "upload-pack" {
  373. return config.UploadPack
  374. }
  375. return getConfigSetting(config.GitBinPath, rpc, dir)
  376. }
  377. func getConfigSetting(gitBinPath, serviceName string, dir string) bool {
  378. serviceName = strings.Replace(serviceName, "-", "", -1)
  379. setting := getGitConfig(gitBinPath, "http."+serviceName, dir)
  380. if serviceName == "uploadpack" {
  381. return setting != "false"
  382. }
  383. return setting == "true"
  384. }
  385. func getGitConfig(gitBinPath, configName string, dir string) string {
  386. args := []string{"config", configName}
  387. out := string(gitCommand(gitBinPath, dir, args...))
  388. return out[0 : len(out)-1]
  389. }
  390. func updateServerInfo(gitBinPath, dir string) []byte {
  391. args := []string{"update-server-info"}
  392. return gitCommand(gitBinPath, dir, args...)
  393. }
  394. func gitCommand(gitBinPath, dir string, args ...string) []byte {
  395. command := exec.Command(gitBinPath, args...)
  396. command.Dir = dir
  397. out, err := command.Output()
  398. if err != nil {
  399. log.GitLogger.Error(4, err.Error())
  400. }
  401. return out
  402. }
  403. // HTTP error response handling functions
  404. func renderMethodNotAllowed(w http.ResponseWriter, r *http.Request) {
  405. if r.Proto == "HTTP/1.1" {
  406. w.WriteHeader(http.StatusMethodNotAllowed)
  407. w.Write([]byte("Method Not Allowed"))
  408. } else {
  409. w.WriteHeader(http.StatusBadRequest)
  410. w.Write([]byte("Bad Request"))
  411. }
  412. }
  413. func renderNotFound(w http.ResponseWriter) {
  414. w.WriteHeader(http.StatusNotFound)
  415. w.Write([]byte("Not Found"))
  416. }
  417. func renderNoAccess(w http.ResponseWriter) {
  418. w.WriteHeader(http.StatusForbidden)
  419. w.Write([]byte("Forbidden"))
  420. }
  421. // Packet-line handling function
  422. func packetFlush() []byte {
  423. return []byte("0000")
  424. }
  425. func packetWrite(str string) []byte {
  426. s := strconv.FormatInt(int64(len(str)+4), 16)
  427. if len(s)%4 != 0 {
  428. s = strings.Repeat("0", 4-len(s)%4) + s
  429. }
  430. return []byte(s + str)
  431. }
  432. // Header writing functions
  433. func hdrNocache(w http.ResponseWriter) {
  434. w.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
  435. w.Header().Set("Pragma", "no-cache")
  436. w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
  437. }
  438. func hdrCacheForever(w http.ResponseWriter) {
  439. now := time.Now().Unix()
  440. expires := now + 31536000
  441. w.Header().Set("Date", fmt.Sprintf("%d", now))
  442. w.Header().Set("Expires", fmt.Sprintf("%d", expires))
  443. w.Header().Set("Cache-Control", "public, max-age=31536000")
  444. }