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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  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. "strconv"
  18. "strings"
  19. "time"
  20. "github.com/gogits/gogs/models"
  21. "github.com/gogits/gogs/modules/base"
  22. "github.com/gogits/gogs/modules/log"
  23. "github.com/gogits/gogs/modules/middleware"
  24. "github.com/gogits/gogs/modules/setting"
  25. )
  26. func authRequired(ctx *middleware.Context) {
  27. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
  28. ctx.Data["ErrorMsg"] = "no basic auth and digit auth"
  29. ctx.HTML(401, base.TplName("status/401"))
  30. }
  31. func Http(ctx *middleware.Context) {
  32. username := ctx.Params(":username")
  33. reponame := ctx.Params(":reponame")
  34. if strings.HasSuffix(reponame, ".git") {
  35. reponame = reponame[:len(reponame)-4]
  36. }
  37. var isPull bool
  38. service := ctx.Query("service")
  39. if service == "git-receive-pack" ||
  40. strings.HasSuffix(ctx.Req.URL.Path, "git-receive-pack") {
  41. isPull = false
  42. } else if service == "git-upload-pack" ||
  43. strings.HasSuffix(ctx.Req.URL.Path, "git-upload-pack") {
  44. isPull = true
  45. } else {
  46. isPull = (ctx.Req.Method == "GET")
  47. }
  48. repoUser, err := models.GetUserByName(username)
  49. if err != nil {
  50. if err == models.ErrUserNotExist {
  51. ctx.Handle(404, "GetUserByName", nil)
  52. } else {
  53. ctx.Handle(500, "GetUserByName", err)
  54. }
  55. return
  56. }
  57. repo, err := models.GetRepositoryByName(repoUser.Id, reponame)
  58. if err != nil {
  59. if err == models.ErrRepoNotExist {
  60. ctx.Handle(404, "GetRepositoryByName", nil)
  61. } else {
  62. ctx.Handle(500, "GetRepositoryByName", err)
  63. }
  64. return
  65. }
  66. // only public pull don't need auth
  67. isPublicPull := !repo.IsPrivate && isPull
  68. var askAuth = !isPublicPull || setting.Service.RequireSignInView
  69. var authUser *models.User
  70. var authUsername, passwd string
  71. // check access
  72. if askAuth {
  73. baHead := ctx.Req.Header.Get("Authorization")
  74. if baHead == "" {
  75. authRequired(ctx)
  76. return
  77. }
  78. auths := strings.Fields(baHead)
  79. // currently check basic auth
  80. // TODO: support digit auth
  81. // FIXME: middlewares/context.go did basic auth check already
  82. if len(auths) != 2 || auths[0] != "Basic" {
  83. ctx.Handle(401, "no basic auth and digit auth", nil)
  84. return
  85. }
  86. authUsername, passwd, err = base.BasicAuthDecode(auths[1])
  87. if err != nil {
  88. ctx.Handle(401, "no basic auth and digit auth", nil)
  89. return
  90. }
  91. authUser, err = models.GetUserByName(authUsername)
  92. if err != nil {
  93. ctx.Handle(401, "no basic auth and digit auth", nil)
  94. return
  95. }
  96. newUser := &models.User{Passwd: passwd, Salt: authUser.Salt}
  97. newUser.EncodePasswd()
  98. if authUser.Passwd != newUser.Passwd {
  99. ctx.Handle(401, "no basic auth and digit auth", nil)
  100. return
  101. }
  102. if !isPublicPull {
  103. var tp = models.WRITABLE
  104. if isPull {
  105. tp = models.READABLE
  106. }
  107. has, err := models.HasAccess(authUsername, username+"/"+reponame, tp)
  108. if err != nil {
  109. ctx.Handle(401, "no basic auth and digit auth", nil)
  110. return
  111. } else if !has {
  112. if tp == models.READABLE {
  113. has, err = models.HasAccess(authUsername, username+"/"+reponame, models.WRITABLE)
  114. if err != nil || !has {
  115. ctx.Handle(401, "no basic auth and digit auth", nil)
  116. return
  117. }
  118. } else {
  119. ctx.Handle(401, "no basic auth and digit auth", nil)
  120. return
  121. }
  122. }
  123. }
  124. }
  125. var f func(rpc string, input []byte)
  126. f = func(rpc string, input []byte) {
  127. if rpc == "receive-pack" {
  128. var lastLine int64 = 0
  129. for {
  130. head := input[lastLine : lastLine+2]
  131. if head[0] == '0' && head[1] == '0' {
  132. size, err := strconv.ParseInt(string(input[lastLine+2:lastLine+4]), 16, 32)
  133. if err != nil {
  134. log.Error(4, "%v", err)
  135. return
  136. }
  137. if size == 0 {
  138. //fmt.Println(string(input[lastLine:]))
  139. break
  140. }
  141. line := input[lastLine : lastLine+size]
  142. idx := bytes.IndexRune(line, '\000')
  143. if idx > -1 {
  144. line = line[:idx]
  145. }
  146. fields := strings.Fields(string(line))
  147. if len(fields) >= 3 {
  148. oldCommitId := fields[0][4:]
  149. newCommitId := fields[1]
  150. refName := fields[2]
  151. models.Update(refName, oldCommitId, newCommitId, authUsername, username, reponame, authUser.Id)
  152. }
  153. lastLine = lastLine + size
  154. } else {
  155. break
  156. }
  157. }
  158. }
  159. }
  160. config := Config{setting.RepoRootPath, "git", true, true, f}
  161. handler := HttpBackend(&config)
  162. handler(ctx.Resp, ctx.Req.Request)
  163. }
  164. type route struct {
  165. cr *regexp.Regexp
  166. method string
  167. handler func(handler)
  168. }
  169. type Config struct {
  170. ReposRoot string
  171. GitBinPath string
  172. UploadPack bool
  173. ReceivePack bool
  174. OnSucceed func(rpc string, input []byte)
  175. }
  176. type handler struct {
  177. *Config
  178. w http.ResponseWriter
  179. r *http.Request
  180. Dir string
  181. File string
  182. }
  183. var routes = []route{
  184. {regexp.MustCompile("(.*?)/git-upload-pack$"), "POST", serviceUploadPack},
  185. {regexp.MustCompile("(.*?)/git-receive-pack$"), "POST", serviceReceivePack},
  186. {regexp.MustCompile("(.*?)/info/refs$"), "GET", getInfoRefs},
  187. {regexp.MustCompile("(.*?)/HEAD$"), "GET", getTextFile},
  188. {regexp.MustCompile("(.*?)/objects/info/alternates$"), "GET", getTextFile},
  189. {regexp.MustCompile("(.*?)/objects/info/http-alternates$"), "GET", getTextFile},
  190. {regexp.MustCompile("(.*?)/objects/info/packs$"), "GET", getInfoPacks},
  191. {regexp.MustCompile("(.*?)/objects/info/[^/]*$"), "GET", getTextFile},
  192. {regexp.MustCompile("(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$"), "GET", getLooseObject},
  193. {regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$"), "GET", getPackFile},
  194. {regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$"), "GET", getIdxFile},
  195. }
  196. // Request handling function
  197. func HttpBackend(config *Config) http.HandlerFunc {
  198. return func(w http.ResponseWriter, r *http.Request) {
  199. for _, route := range routes {
  200. if m := route.cr.FindStringSubmatch(r.URL.Path); m != nil {
  201. if route.method != r.Method {
  202. renderMethodNotAllowed(w, r)
  203. return
  204. }
  205. file := strings.Replace(r.URL.Path, m[1]+"/", "", 1)
  206. dir, err := getGitDir(config, m[1])
  207. if err != nil {
  208. log.GitLogger.Error(4, err.Error())
  209. renderNotFound(w)
  210. return
  211. }
  212. hr := handler{config, w, r, dir, file}
  213. route.handler(hr)
  214. return
  215. }
  216. }
  217. renderNotFound(w)
  218. return
  219. }
  220. }
  221. // Actual command handling functions
  222. func serviceUploadPack(hr handler) {
  223. serviceRpc("upload-pack", hr)
  224. }
  225. func serviceReceivePack(hr handler) {
  226. serviceRpc("receive-pack", hr)
  227. }
  228. func serviceRpc(rpc string, hr handler) {
  229. w, r, dir := hr.w, hr.r, hr.Dir
  230. access := hasAccess(r, hr.Config, dir, rpc, true)
  231. if access == false {
  232. renderNoAccess(w)
  233. return
  234. }
  235. w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", rpc))
  236. var (
  237. reqBody = r.Body
  238. input []byte
  239. br io.Reader
  240. err error
  241. )
  242. // Handle GZIP.
  243. if r.Header.Get("Content-Encoding") == "gzip" {
  244. reqBody, err = gzip.NewReader(reqBody)
  245. if err != nil {
  246. log.GitLogger.Error(2, "fail to create gzip reader: %v", err)
  247. w.WriteHeader(http.StatusInternalServerError)
  248. return
  249. }
  250. }
  251. if hr.Config.OnSucceed != nil {
  252. input, err = ioutil.ReadAll(reqBody)
  253. if err != nil {
  254. log.GitLogger.Error(2, "fail to read request body: %v", err)
  255. w.WriteHeader(http.StatusInternalServerError)
  256. return
  257. }
  258. br = bytes.NewReader(input)
  259. } else {
  260. br = reqBody
  261. }
  262. args := []string{rpc, "--stateless-rpc", dir}
  263. cmd := exec.Command(hr.Config.GitBinPath, args...)
  264. cmd.Dir = dir
  265. cmd.Stdout = w
  266. cmd.Stdin = br
  267. if err := cmd.Run(); err != nil {
  268. log.GitLogger.Error(2, "fail to serve RPC(%s): %v", rpc, err)
  269. w.WriteHeader(http.StatusInternalServerError)
  270. return
  271. }
  272. if hr.Config.OnSucceed != nil {
  273. hr.Config.OnSucceed(rpc, input)
  274. }
  275. w.WriteHeader(http.StatusOK)
  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. }