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