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