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