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
10 years ago
11 years ago
11 years ago
9 years ago
11 years ago
9 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
9 years ago
11 years ago
9 years ago
11 years ago
9 years ago
9 years ago
9 years ago
9 years ago
11 years ago
9 years ago
9 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
9 years ago
9 years ago
9 years ago
9 years ago
9 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
10 years ago
10 years ago
10 years ago
10 years ago
11 years ago
10 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
11 years ago
11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  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. "regexp"
  16. "runtime"
  17. "strconv"
  18. "strings"
  19. "time"
  20. "code.gitea.io/git"
  21. "code.gitea.io/gitea/models"
  22. "code.gitea.io/gitea/modules/base"
  23. "code.gitea.io/gitea/modules/context"
  24. "code.gitea.io/gitea/modules/log"
  25. "code.gitea.io/gitea/modules/setting"
  26. )
  27. // HTTP implmentation git smart HTTP protocol
  28. func HTTP(ctx *context.Context) {
  29. username := ctx.Params(":username")
  30. reponame := strings.TrimSuffix(ctx.Params(":reponame"), ".git")
  31. var isPull bool
  32. service := ctx.Query("service")
  33. if service == "git-receive-pack" ||
  34. strings.HasSuffix(ctx.Req.URL.Path, "git-receive-pack") {
  35. isPull = false
  36. } else if service == "git-upload-pack" ||
  37. strings.HasSuffix(ctx.Req.URL.Path, "git-upload-pack") {
  38. isPull = true
  39. } else {
  40. isPull = (ctx.Req.Method == "GET")
  41. }
  42. isWiki := false
  43. if strings.HasSuffix(reponame, ".wiki") {
  44. isWiki = true
  45. reponame = reponame[:len(reponame)-5]
  46. }
  47. repoUser, err := models.GetUserByName(username)
  48. if err != nil {
  49. if models.IsErrUserNotExist(err) {
  50. ctx.Handle(http.StatusNotFound, "GetUserByName", nil)
  51. } else {
  52. ctx.Handle(http.StatusInternalServerError, "GetUserByName", err)
  53. }
  54. return
  55. }
  56. repo, err := models.GetRepositoryByName(repoUser.ID, reponame)
  57. if err != nil {
  58. if models.IsErrRepoNotExist(err) {
  59. ctx.Handle(http.StatusNotFound, "GetRepositoryByName", nil)
  60. } else {
  61. ctx.Handle(http.StatusInternalServerError, "GetRepositoryByName", err)
  62. }
  63. return
  64. }
  65. // Only public pull don't need auth.
  66. isPublicPull := !repo.IsPrivate && isPull
  67. var (
  68. askAuth = !isPublicPull || setting.Service.RequireSignInView
  69. authUser *models.User
  70. authUsername string
  71. authPasswd string
  72. )
  73. // check access
  74. if askAuth {
  75. if setting.Service.EnableReverseProxyAuth {
  76. authUsername = ctx.Req.Header.Get(setting.ReverseProxyAuthUser)
  77. if len(authUsername) == 0 {
  78. ctx.HandleText(401, "reverse proxy login error. authUsername empty")
  79. return
  80. }
  81. authUser, err = models.GetUserByName(authUsername)
  82. if err != nil {
  83. ctx.HandleText(401, "reverse proxy login error, got error while running GetUserByName")
  84. return
  85. }
  86. } else {
  87. authHead := ctx.Req.Header.Get("Authorization")
  88. if len(authHead) == 0 {
  89. ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
  90. ctx.Error(http.StatusUnauthorized)
  91. return
  92. }
  93. auths := strings.Fields(authHead)
  94. // currently check basic auth
  95. // TODO: support digit auth
  96. // FIXME: middlewares/context.go did basic auth check already,
  97. // maybe could use that one.
  98. if len(auths) != 2 || auths[0] != "Basic" {
  99. ctx.HandleText(http.StatusUnauthorized, "no basic auth and digit auth")
  100. return
  101. }
  102. authUsername, authPasswd, err = base.BasicAuthDecode(auths[1])
  103. if err != nil {
  104. ctx.HandleText(http.StatusUnauthorized, "no basic auth and digit auth")
  105. return
  106. }
  107. authUser, err = models.UserSignIn(authUsername, authPasswd)
  108. if err != nil {
  109. if !models.IsErrUserNotExist(err) {
  110. ctx.Handle(http.StatusInternalServerError, "UserSignIn error: %v", err)
  111. return
  112. }
  113. // Assume username now is a token.
  114. token, err := models.GetAccessTokenBySHA(authUsername)
  115. if err != nil {
  116. if models.IsErrAccessTokenNotExist(err) || models.IsErrAccessTokenEmpty(err) {
  117. ctx.HandleText(http.StatusUnauthorized, "invalid token")
  118. } else {
  119. ctx.Handle(http.StatusInternalServerError, "GetAccessTokenBySha", err)
  120. }
  121. return
  122. }
  123. token.Updated = time.Now()
  124. if err = models.UpdateAccessToken(token); err != nil {
  125. ctx.Handle(http.StatusInternalServerError, "UpdateAccessToken", err)
  126. }
  127. authUser, err = models.GetUserByID(token.UID)
  128. if err != nil {
  129. ctx.Handle(http.StatusInternalServerError, "GetUserByID", err)
  130. return
  131. }
  132. }
  133. if !isPublicPull {
  134. var tp = models.AccessModeWrite
  135. if isPull {
  136. tp = models.AccessModeRead
  137. }
  138. has, err := models.HasAccess(authUser, repo, tp)
  139. if err != nil {
  140. ctx.Handle(http.StatusInternalServerError, "HasAccess", err)
  141. return
  142. } else if !has {
  143. if tp == models.AccessModeRead {
  144. has, err = models.HasAccess(authUser, repo, models.AccessModeWrite)
  145. if err != nil {
  146. ctx.Handle(http.StatusInternalServerError, "HasAccess2", err)
  147. return
  148. } else if !has {
  149. ctx.HandleText(http.StatusForbidden, "User permission denied")
  150. return
  151. }
  152. } else {
  153. ctx.HandleText(http.StatusForbidden, "User permission denied")
  154. return
  155. }
  156. }
  157. if !isPull && repo.IsMirror {
  158. ctx.HandleText(http.StatusForbidden, "mirror repository is read-only")
  159. return
  160. }
  161. }
  162. }
  163. }
  164. callback := func(rpc string, input []byte) {
  165. if rpc != "receive-pack" || isWiki {
  166. return
  167. }
  168. var lastLine int64
  169. for {
  170. head := input[lastLine : lastLine+2]
  171. if head[0] == '0' && head[1] == '0' {
  172. size, err := strconv.ParseInt(string(input[lastLine+2:lastLine+4]), 16, 32)
  173. if err != nil {
  174. log.Error(4, "%v", err)
  175. return
  176. }
  177. if size == 0 {
  178. //fmt.Println(string(input[lastLine:]))
  179. break
  180. }
  181. line := input[lastLine : lastLine+size]
  182. idx := bytes.IndexRune(line, '\000')
  183. if idx > -1 {
  184. line = line[:idx]
  185. }
  186. fields := strings.Fields(string(line))
  187. if len(fields) >= 3 {
  188. oldCommitID := fields[0][4:]
  189. newCommitID := fields[1]
  190. refFullName := fields[2]
  191. // FIXME: handle error.
  192. if err = models.PushUpdate(models.PushUpdateOptions{
  193. RefFullName: refFullName,
  194. OldCommitID: oldCommitID,
  195. NewCommitID: newCommitID,
  196. PusherID: authUser.ID,
  197. PusherName: authUser.Name,
  198. RepoUserName: username,
  199. RepoName: reponame,
  200. }); err == nil {
  201. go models.AddTestPullRequestTask(authUser, repo.ID, strings.TrimPrefix(refFullName, git.BranchPrefix), true)
  202. }
  203. }
  204. lastLine = lastLine + size
  205. } else {
  206. break
  207. }
  208. }
  209. }
  210. HTTPBackend(ctx, &serviceConfig{
  211. UploadPack: true,
  212. ReceivePack: true,
  213. OnSucceed: callback,
  214. })(ctx.Resp, ctx.Req.Request)
  215. runtime.GC()
  216. }
  217. type serviceConfig struct {
  218. UploadPack bool
  219. ReceivePack bool
  220. OnSucceed func(rpc string, input []byte)
  221. }
  222. type serviceHandler struct {
  223. cfg *serviceConfig
  224. w http.ResponseWriter
  225. r *http.Request
  226. dir string
  227. file string
  228. }
  229. func (h *serviceHandler) setHeaderNoCache() {
  230. h.w.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
  231. h.w.Header().Set("Pragma", "no-cache")
  232. h.w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
  233. }
  234. func (h *serviceHandler) setHeaderCacheForever() {
  235. now := time.Now().Unix()
  236. expires := now + 31536000
  237. h.w.Header().Set("Date", fmt.Sprintf("%d", now))
  238. h.w.Header().Set("Expires", fmt.Sprintf("%d", expires))
  239. h.w.Header().Set("Cache-Control", "public, max-age=31536000")
  240. }
  241. func (h *serviceHandler) sendFile(contentType string) {
  242. reqFile := path.Join(h.dir, h.file)
  243. fi, err := os.Stat(reqFile)
  244. if os.IsNotExist(err) {
  245. h.w.WriteHeader(http.StatusNotFound)
  246. return
  247. }
  248. h.w.Header().Set("Content-Type", contentType)
  249. h.w.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size()))
  250. h.w.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat))
  251. http.ServeFile(h.w, h.r, reqFile)
  252. }
  253. type route struct {
  254. reg *regexp.Regexp
  255. method string
  256. handler func(serviceHandler)
  257. }
  258. var routes = []route{
  259. {regexp.MustCompile("(.*?)/git-upload-pack$"), "POST", serviceUploadPack},
  260. {regexp.MustCompile("(.*?)/git-receive-pack$"), "POST", serviceReceivePack},
  261. {regexp.MustCompile("(.*?)/info/refs$"), "GET", getInfoRefs},
  262. {regexp.MustCompile("(.*?)/HEAD$"), "GET", getTextFile},
  263. {regexp.MustCompile("(.*?)/objects/info/alternates$"), "GET", getTextFile},
  264. {regexp.MustCompile("(.*?)/objects/info/http-alternates$"), "GET", getTextFile},
  265. {regexp.MustCompile("(.*?)/objects/info/packs$"), "GET", getInfoPacks},
  266. {regexp.MustCompile("(.*?)/objects/info/[^/]*$"), "GET", getTextFile},
  267. {regexp.MustCompile("(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$"), "GET", getLooseObject},
  268. {regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$"), "GET", getPackFile},
  269. {regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$"), "GET", getIdxFile},
  270. }
  271. // FIXME: use process module
  272. func gitCommand(dir string, args ...string) []byte {
  273. cmd := exec.Command("git", args...)
  274. cmd.Dir = dir
  275. out, err := cmd.Output()
  276. if err != nil {
  277. log.GitLogger.Error(4, fmt.Sprintf("%v - %s", err, out))
  278. }
  279. return out
  280. }
  281. func getGitConfig(option, dir string) string {
  282. out := string(gitCommand(dir, "config", option))
  283. return out[0 : len(out)-1]
  284. }
  285. func getConfigSetting(service, dir string) bool {
  286. service = strings.Replace(service, "-", "", -1)
  287. setting := getGitConfig("http."+service, dir)
  288. if service == "uploadpack" {
  289. return setting != "false"
  290. }
  291. return setting == "true"
  292. }
  293. func hasAccess(service string, h serviceHandler, checkContentType bool) bool {
  294. if checkContentType {
  295. if h.r.Header.Get("Content-Type") != fmt.Sprintf("application/x-git-%s-request", service) {
  296. return false
  297. }
  298. }
  299. if !(service == "upload-pack" || service == "receive-pack") {
  300. return false
  301. }
  302. if service == "receive-pack" {
  303. return h.cfg.ReceivePack
  304. }
  305. if service == "upload-pack" {
  306. return h.cfg.UploadPack
  307. }
  308. return getConfigSetting(service, h.dir)
  309. }
  310. func serviceRPC(h serviceHandler, service string) {
  311. defer h.r.Body.Close()
  312. if !hasAccess(service, h, true) {
  313. h.w.WriteHeader(http.StatusUnauthorized)
  314. return
  315. }
  316. h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service))
  317. var (
  318. reqBody = h.r.Body
  319. input []byte
  320. br io.Reader
  321. err error
  322. )
  323. // Handle GZIP.
  324. if h.r.Header.Get("Content-Encoding") == "gzip" {
  325. reqBody, err = gzip.NewReader(reqBody)
  326. if err != nil {
  327. log.GitLogger.Error(2, "fail to create gzip reader: %v", err)
  328. h.w.WriteHeader(http.StatusInternalServerError)
  329. return
  330. }
  331. }
  332. if h.cfg.OnSucceed != nil {
  333. input, err = ioutil.ReadAll(reqBody)
  334. if err != nil {
  335. log.GitLogger.Error(2, "fail to read request body: %v", err)
  336. h.w.WriteHeader(http.StatusInternalServerError)
  337. return
  338. }
  339. br = bytes.NewReader(input)
  340. } else {
  341. br = reqBody
  342. }
  343. cmd := exec.Command("git", service, "--stateless-rpc", h.dir)
  344. cmd.Dir = h.dir
  345. cmd.Stdout = h.w
  346. cmd.Stdin = br
  347. if err := cmd.Run(); err != nil {
  348. log.GitLogger.Error(2, "fail to serve RPC(%s): %v", service, err)
  349. h.w.WriteHeader(http.StatusInternalServerError)
  350. return
  351. }
  352. if h.cfg.OnSucceed != nil {
  353. h.cfg.OnSucceed(service, input)
  354. }
  355. }
  356. func serviceUploadPack(h serviceHandler) {
  357. serviceRPC(h, "upload-pack")
  358. }
  359. func serviceReceivePack(h serviceHandler) {
  360. serviceRPC(h, "receive-pack")
  361. }
  362. func getServiceType(r *http.Request) string {
  363. serviceType := r.FormValue("service")
  364. if !strings.HasPrefix(serviceType, "git-") {
  365. return ""
  366. }
  367. return strings.Replace(serviceType, "git-", "", 1)
  368. }
  369. func updateServerInfo(dir string) []byte {
  370. return gitCommand(dir, "update-server-info")
  371. }
  372. func packetWrite(str string) []byte {
  373. s := strconv.FormatInt(int64(len(str)+4), 16)
  374. if len(s)%4 != 0 {
  375. s = strings.Repeat("0", 4-len(s)%4) + s
  376. }
  377. return []byte(s + str)
  378. }
  379. func getInfoRefs(h serviceHandler) {
  380. h.setHeaderNoCache()
  381. if hasAccess(getServiceType(h.r), h, false) {
  382. service := getServiceType(h.r)
  383. refs := gitCommand(h.dir, service, "--stateless-rpc", "--advertise-refs", ".")
  384. h.w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", service))
  385. h.w.WriteHeader(http.StatusOK)
  386. h.w.Write(packetWrite("# service=git-" + service + "\n"))
  387. h.w.Write([]byte("0000"))
  388. h.w.Write(refs)
  389. } else {
  390. updateServerInfo(h.dir)
  391. h.sendFile("text/plain; charset=utf-8")
  392. }
  393. }
  394. func getTextFile(h serviceHandler) {
  395. h.setHeaderNoCache()
  396. h.sendFile("text/plain")
  397. }
  398. func getInfoPacks(h serviceHandler) {
  399. h.setHeaderCacheForever()
  400. h.sendFile("text/plain; charset=utf-8")
  401. }
  402. func getLooseObject(h serviceHandler) {
  403. h.setHeaderCacheForever()
  404. h.sendFile("application/x-git-loose-object")
  405. }
  406. func getPackFile(h serviceHandler) {
  407. h.setHeaderCacheForever()
  408. h.sendFile("application/x-git-packed-objects")
  409. }
  410. func getIdxFile(h serviceHandler) {
  411. h.setHeaderCacheForever()
  412. h.sendFile("application/x-git-packed-objects-toc")
  413. }
  414. func getGitRepoPath(subdir string) (string, error) {
  415. if !strings.HasSuffix(subdir, ".git") {
  416. subdir += ".git"
  417. }
  418. fpath := path.Join(setting.RepoRootPath, subdir)
  419. if _, err := os.Stat(fpath); os.IsNotExist(err) {
  420. return "", err
  421. }
  422. return fpath, nil
  423. }
  424. // HTTPBackend middleware for git smart HTTP protocol
  425. func HTTPBackend(ctx *context.Context, cfg *serviceConfig) http.HandlerFunc {
  426. return func(w http.ResponseWriter, r *http.Request) {
  427. for _, route := range routes {
  428. r.URL.Path = strings.ToLower(r.URL.Path) // blue: In case some repo name has upper case name
  429. if m := route.reg.FindStringSubmatch(r.URL.Path); m != nil {
  430. if setting.Repository.DisableHTTPGit {
  431. w.WriteHeader(http.StatusForbidden)
  432. w.Write([]byte("Interacting with repositories by HTTP protocol is not allowed"))
  433. return
  434. }
  435. if route.method != r.Method {
  436. if r.Proto == "HTTP/1.1" {
  437. w.WriteHeader(http.StatusMethodNotAllowed)
  438. w.Write([]byte("Method Not Allowed"))
  439. } else {
  440. w.WriteHeader(http.StatusBadRequest)
  441. w.Write([]byte("Bad Request"))
  442. }
  443. return
  444. }
  445. file := strings.Replace(r.URL.Path, m[1]+"/", "", 1)
  446. dir, err := getGitRepoPath(m[1])
  447. if err != nil {
  448. log.GitLogger.Error(4, err.Error())
  449. ctx.Handle(http.StatusNotFound, "HTTPBackend", err)
  450. return
  451. }
  452. route.handler(serviceHandler{cfg, w, r, dir, file})
  453. return
  454. }
  455. }
  456. ctx.Handle(http.StatusNotFound, "HTTPBackend", nil)
  457. return
  458. }
  459. }